いや、やっぱり音がないと寂しいかな〜と。
あと、データをリソースにして利用するようにすれば、ファイル転送が楽になったり、 ソースが MML で埋め尽くされて見づらくなることが無くなったり、色々幸せなので。
簡単に音を鳴らすだけなら、マニュアル P96 (\doc\appendix\soundil.html) に載っているソースほぼそのままで OK です。
#include <sys/bios.h>
#include <il_sound.h>
SoundIL soundIL;
extern BYTE *_heap; /* 実体はシステムが確保 */
void main()
{
/* ドライバ (soundIL) が存在しない場合は抜ける */
if( open_sound_il( &soundIL ) != E_FS_SUCCESS ) return;
/* この2つでサウンドドライバを初期化 */
sounddrv_init();
sound_open();
/* 渡された文字列 (MML) をリソースに変換し、_heap へ格納 */
/* 第 3 引数が 0 の時は BGM として変換 */
parse_mml( _heap, "CDEFG", 0 );
/* 演奏開始 */
/* 第 2 引数が PLAY_SINGLE で 1 回、PLAY_LOOP でループ再生 */
bgm_play( _heap, PLAY_LOOP );
key_wait();
/* 演奏停止 */
bgm_stop();
/* この 2 つでサウンドドライバを停止・解放 */
sound_close();
sounddrv_release();
}
これで「CDEFG」の音階が鳴るわけですな。ドレミファソ。
なお、実行するときには sound.il を /rom0/ に転送しておいて下さい。(当然か)
parse_mml() とか bgm_play() とか、soundIL 内の関数の詳しい仕様はマニュアル
(または \doc\appendix\il_sound.html) に載ってます。
_heap ってのはシステムの方でどこかに確保されている領域らしく、 extern 宣言さえしておけば、それ以外は特に意識しなくても大丈夫です。
で、上の「MML を実行時に変換して再生」という方法は、サウンドプレイヤーとか、 MML エディタみたいな物を作るときには都合が良いんですが、変換時に CPU パワーを食ったり、 ソースの中に MML が突然出現したりと、気になるところもあるわけです。
そこで、MML をリソースに変換して .fx ファイルにくっつけ、 MML 解析のオーバーヘッドを減らしたりなんだりしてみようと。
まず、sndcnv で MML をリソースに変換します。
> sndcnv <MMLファイル名>
拡張子 .fr のリソースファイルができあがるはずなので、
これをリソースとして .fx ファイルにくっつけます。
.cf ファイルに、
resource: <リソースファイル名>
と書いてやると、指定したファイルを、mkfent が、
.fx ファイルの生成時にリソースとして含めてくれます。
リソースファイルはスペースで区切って複数指定できます。
リソースにアクセスするコードですが、まず、resource.h を #include します。
で、これまたシステムで指定されているポインタ変数 _resource を extern 宣言します。
extern res_t far *_resource;
これもやっぱり実体はシステムの方で確保されているらしく、
extern 宣言さえすれば後は気にする必要はありません。
res_t 構造体は、resource.h で定義されているので参照して下さい。
この _resource の指す先に、変換済みの MML が格納されているわけですから、 parse_mml() とかは呼ばずに、いきなり
bgm_play( _resource, PLAY_LOOP );
としてやれば BGM が演奏されます。
2 つ以上のリソースを結合させた場合は、
という片方向リスト (のような形) になっているので、順次手繰って参照します。
「_resource->size」というのは、次のリソースまでの segment 数を表します。
( マニュアル P97 に、「個々のリソースは、mkfent でアペンドする際に
パラグラフ境界に先頭が位置するようパディングされますので……」とある )
ちなみに、segment と offset はともに WORD 値で、
segment << 4 + offset で一つの far アドレスが生成されます。
「far アドレス」というのは、20bit で表される 1 つのアドレスというわけです。
「int far *」というふうに宣言された far ポインタで指されます。
far ポインタは DWORD 値で、上位 WORD に segment 値、
下位 WORD に offset 値を格納しています。
また、同様に、far 宣言されていないポインタで指されるのが「near アドレス」です。
これは 16bit で表されるアドレス値です。
名前の通り、far アドレスに比べて狭い領域 (〜 64KB) までしか表せません。
多分、offset 値に相当するんだろうと思います。
ちなみにこの segment と offset という概念、intel の特許です。どうでも良いんですが。
リソースにアクセスするコードは以下の通り。
#include <resource.h>
extern res_t far *_resource; /* 実体はシステムが確保 */
static res_t far *rsc = NULL; /* リソース取得位置保存用 */
/* リソースの取得位置を初期化 */
void init_resource( void )
{
rsc = _resource; /* _resource って const にすべきだと思う */
}
/* リソースを取得 */
/*
* type: 取得すべきリソースの種類 (RESID_〜 マクロ (resource.h) )
* RESID_IMAGE (0x4D42): 'BM': 画像
* RESID_MUSIC (0x444D): 'MD': MML
* RESID_SE (0x4553): 'SE': SE
* RESID_INST (0x4953): 'SI': 音色
* RESID_VOICE (0x4F56): 'VO': PCM
*/
res_t far *get_resource( WORD type )
{
res_t far *rslt;
while ( rsc != NULL )
{
/* リソースでなかったら抜ける */
/* RESID: リソースのマジックナンバー ('FR'): resource.h) */
if ( rsc->magic != RESID )
break;
/* 現在のリソースを指している */
rslt = rsc;
/* 次のリソースを指させる */
rsc = MK_FP( FP_SEG( rsc ) + rsc->size, FP_OFF( rsc ) );
if ( rslt->type == type ) /* 指定したリソースのタイプと合致 */
{
/* segment 境界のバグ対策 */
rslt = MK_FP( FP_SEG( rslt ) + ( FP_OFF( rslt ) >> 4 ),
FP_OFF( rslt ) & 0x00f );
return rslt;
}
}
return NULL;
}
……殆どすまうぐさんのページからのパクりです。 御免なさい(苦笑
最初に init_resource() を呼んでおいて、次に get_resource() を、
欲しいリソースの種類を指定して呼んでやると、
そのリソースを指している far ポインタが返ってきます。
結合したリソースの順番が分かっていれば、もう少しコードを省略できますが、
こっちの方が何となく安心なのでそのまま採用です。
で、「segment 境界のバグ対策」というのが気になるわけです。
まず、offset と segment で 1 つの far アドレスを指すことは上で説明しましたが、これで考えると、
i) segment = 1FFF, offset = 000F segment 1FFF +) offset 000F ---------------- address 1FFFF
ii) segment = 1000, offset = FFFF segment 1000 +) offset FFFF ---------------- address 1FFFF
と、segment 値と offset 値が異なっても、同じアドレスを指すことが出来ます。
さらに、offset 値は near ポインタの値として使われ ( 多分 ) ます。
で、offset 値が大きな値になっている状態でデータを読もうとすると、
値がオーバーフローを起こして上手く動作しなかったりします。
つまり、どこかにリソースを near ポインタで参照するコードがあったときに、
誤動作する可能性が出てくるわけですな。
これを回避するため、offset 値の上位 12bit を segment 値の方に移してしまい、
offset 値 ( near ポインタ ) がオーバーフローしないようにすることにします。
これが「segment 境界のバグ対策」というところでやっている内容です。
near ポインタは 16bit ですから、64KB 範囲のメモリを参照することが出来ます。
64KB を越えるファイルは WW に転送できません ( の筈 ) から、これで大丈夫なわけです。
サンプルです。 BGM とリソースのサンプル
実行すると BGM として「ドレミファソラシド」( 1 番目のリソース ) が流れます。
どれかキーを押すと「ドシラソファミレド」( 2 番目のリソース ) に変わります。
.cf ファイルとか、色々弄ってみてやって下さい。
何だか難しそうな感じだったサウンド・リソース関係ですが、
すまうぐさんのページのお陰でかなりはかどりました。
# というか、このページを見ないと出来なかったと思う(苦笑
分かり易い解説に感謝です。