May.20.2000
ゲーム用途向けサウンドマネージャーの製作。
ゲーム用途向けのサウンドマネージャーを設計し、製作します。
ゲームプログラムにおいて、サウンドを持つことは一層の表現力を持つことであり、
既にサウンド無しのゲームというのは考えられないと言っても過言ではないと
思います。
そういったサウンドを持ったゲームを作るために、サウンドを管理し、
簡易なコールでそれを鳴らしてくれるプログラムを作成します。
その作業内容はサウンドドライバーといったファウンデーションクラスでは
なく、アプリケーションでもなく、その間を取り持つ立場にあることから
サウンドマネージャーと呼びます。
サウンドマネージャーはアプリケーションとはシンクロナスに動作し、
サウンドドライバーを監視・コントロールします。アプリケーション側から
短いリクエストコマンドを受け取ったら、それを演奏しますが演奏中は
アプリケーション側の進行を妨げません。
手短のCDプレイヤーを思い浮かべてください。利用者はCDプレイヤーの
再生ボタンを押すことにより曲を聞くことができます。演奏はCDプレイヤーが
行いますので、利用者は本を読んだり食事をしたり別なことが行えます。
演奏を止めるには停止ボタンを押すだけです。この、勝手に演奏してくれる
CDプレイヤーがサウンドマネージャーにあたり、ボタンを押す行為が
アプリケーション側からのリクエストにあたります。
主にゲームにおいて、サウンドに対するアプリケーション側の負担が
少なくなるようにサウンドを管理するマネージャーということです。
PCMサウンドの再生するには波形データをひっきりなしに途切れなく
サウンドデバイスへと流しこまなくてはなりません。
アプリケーション側でこれをやろうとすると実に頻繁にサウンドアクセスを
しなければならなくて複雑なプログラムとなりがちです。
そこでサウンド部分はなるべくアプリケーション本体とは分離させ、
自立して動くように設計します。昔のアーケードゲームにはゲーム制御用
CPU とは別にサウンド専用CPUが載っていました、それと同じ理屈です。
シンクロナスに動作させるためにサウンドマネージャーを子プロセスと
して fork します。fork して独立して動くサウンドマネージャーと
アプリケーションとの連絡のためにソケット(パイプ)を繋ぎ、リクエストは
ここを通して行うことにします。
この構成は、
わたなべごう氏
作の
「XLVNS」
のサウンド周りを参考にさせていただきました。
参考と言うよりそのままだったりするんですけど。
プロセスの fork やソケット(パイプ)の生成については省略します。
他の UNIX System 系プログラミング教材を御参照ください。
(また、これ系の本が書店にあんまし置いて無いんだ)
---- 該当部分の抜きだし
/* --- 通信用ソケットの作成 */ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sock_fd) < 0) { perror("ERROR sound: socketpair can't make.\n"); return(-1); } /* --- プロセスフォーク */ pid = fork(); if (pid < 0) { perror("sound fork error.\n"); close(sock_fd[0]); close(sock_fd[1]); return(-1); } else if (pid) { /* -- 親プロセスなのでソケットfdを持ってリターン */ close(sock_fd[1]); return(sock_fd[0]); } /* --- 子プロセス始動 */ close(sock_fd[0]); #ifdef DEBUG fprintf(stderr, "Limo Sound Manager is starting now ..\n"); #endif---- ココマデ
Jun.28.2000
socketpair で AF_LOCAL/PF_LOCAL でなく AF_UNIX/PF_UNSPEC にしないと
互換性の問題で OS によっては通らないとの指摘をごう氏から受けていた
ものを(ようやく)修正。
さて実際にサウンドマネージャーに何をさせたいのかを考えます。
機能の設計と実装ですが、この「何をさせるか」というのを始めに考える事が
今回最大のトピックです。プログラム自体はそんなにすごいものじゃないと
思いますし。
ゲームを作る上でどの様な機能があったら良いのかなという部分(機能要求)は
プログラムだけでは見えてこないところです。要するに仕様設計なんで
すが、大抵の場合経験が物をいいます。あとは予測能力…。
今回サウンドデバイスは PCM(dsp) と CD-DA を扱います。
デバイスへのアクセスは
EsounD と
libcdaudio
を用います。OS や環境によって異なるデバイス周りのアクセスに対し
個別に対応するコードを用意するのではなく、そういった対策が既に
行われている既存ライブラリを用いる事によって手間とコードを減らそう
というのが意図です。それに下手に自前で作るよりも出来の良い
既存ライブラリを用いた方が全体として大きなメリットでもあるわけで。
表現したい音声形式として、
PCM が扱う音声ファイルは無圧縮PCM形式の(Windows)WAV ファイルとします。 これは開発側として用意しやすい形式であると共に EsounD で鳴らすに 不都合無く、いくつかの周波数を用意できるからです。
アプリケーションからサウンドマネージャーへ、音声発呼のリクエストを
依頼する際のリクエストコマンドを決定します。
このリクエストコマンドを決定するということは、サウンドマネージャーの
細かい挙動を設計するということでもあります。
プログラムを書くよりもなによりも、まず最初にした事は今回製作する
サウンドマネージャーのリクエストコマンド表を書くことでした。
以下のファイルがそのコマンド表です。
リクエストコマンド一覧
頭一文字が出力デバイスの指定だったり終了コードだったりするのは、
ごう氏のコードを参考にしたなごりです。 :-)
これらコマンドをソケット(パイプ)に流し込むだけで後はサウンドマネージャー
が演奏してくれます。アプリケーションにとってはかなり負荷が減って
いると思います。
コマンドは一度に複数列挙して指定する事が可能です。ただし現在の
インプリメントでは 32トークンという制限がありますので、
適度な所で切らないとならないかもしれません。
コマンド群の中で BGM と CD-DA に READY というリクエストがあります。
これは演奏要求をした直後にポーズをかけるというもので C PLAY 1 C PAUSE
と連続でコマンドを送信するのと等価です。
一見無意味なようですが実は CD-DA を BGM に使うには大きな意味を
持ちます。
CD は演奏する際回転します。一見当り前の事ですがこれが扱い難い原因の
一つでもあります。停止状態に演奏要求を出した場合、CD プレイヤーは
スピンドルを回転させ、トラックを読み取り演奏を開始します。この、
スピンドルの立ち上がりというのが結構遅く、演奏要求から実際に音が
出力されるまで結構な時間待たされます。その空白時間というのが
ゲームにとってはかなりマイナスに作用してしまうのです。
そこであらかじめスピンドルを回転させておき、いつでも演奏可能状態と
したうえでゲームの進行とシンクロして BGM が流れるようリクエスト
したあと即座に PAUSE 状態にしておきます。あとはゲームで「ここだ!」
というタイミングに合わせて RESUME を発呼すればばっちりとゲーム
画面(演出)に合わせた BGM 演奏を行えます。
格闘ゲームや落ち物パズルにおける「れでぃー・ごー☆」という場面を
思い浮かべてもらうとわかりやすいと思います。
PCM による BGM および SE にはそれぞれ最大発音数があり、1発音の事を
BGM では「トラック」 SE では「チャンネル」と呼びます。呼称は違いますが
実装的には同じものです。デフォルトでは 2トラック 4チャンネルとなって
おり、この状態では BGM が 2曲、SE が 4音同時に鳴らせるわけです。
リクエストを発呼した際、演奏中のトラック(チャンネル)だった場合
演奏を中止して新しい演奏を開始します。既に演奏中でも、それが別の
トラック(チャンネル)だった場合、演奏は重なり同時発音されます。
SE とかは重なって欲しい音と重なって欲しくない音の 2パターンが存在する
ものですので、その場合チャンネルを使い分けることによってコントロール
します。
PCM BGM や SE では WAV ファイルを読み込みそれを音声として出力します。
それら WAV ファイル群を管理し、WAV ファイルを通し番号で管理するため
サウンドマネージャーはサウンドリストを要求し、それを読み込みます。
ようするに BGM と SE に使う WAV ファイルの一覧です。
実際のサウンドリスト例
書式は
今回は鳴らす SE や BGM の数だけ WAV ファイルを用意しなくてはなりません。 たくさんのファイルが必要になります。これらをアーカイブしたり、その アーカイブから利用するといった形態に関しては考慮していません。 ストリームで読み込んで出力していくからというのが理由の一つですが。
アプリケーション側から利用するインターフェース関数はとりあえず
サウンドリストのパスと EsounDのホスト(ローカルなら null)を CDROM デバイス
のパス(/dev/cdrom とか)を持たせて lm_sound_init を起動。起動成功時は
サウンドマネージャーとのソケット(パイプ)ファイルディスクリプタを持って
帰って来ます。EsounD がいなかったり、CD がセットされていなくても
起動して帰って来ますが、それらは使えなくなりリクエストが無効になります。
lm_sound_close に lm_sound_init が持って来たファイルディスクリプタを
持たせてコールすることによりサウンドマネージャーを停止、開放します。
lm_sound_request はリクエストを容易に行うために用意したおまけです。
lm_sound_init が持って来たファイルディスクリプタと、リクエストコマンド
の文字列を渡します。不正なリクエストはファイルマネージャーの中で
暗黙のうちに無視されます。
既知の不具合事項です
ライブラリとサンプルのコードです。
利用と配布の条件として GPL(Ver.2) に準じます。(LGPL ではありません)
lm_sound_man.tar.gz (10902byte)
今回は面白そうなサンプルを用意できませんでした、簡素な Makefile と
取り敢えず鳴らすテストコードだけです。
各人テストの為の WAV ファイルを用意して、sound_list.dat にそのファイル名
を記述してください。プログラムを起動すると入力待ちになりますので
コンソールからリクエストコマンドを直に打ち込んでサウンドマネージャー
に渡してください。リクエストに応じてサウンドマネージャーが音を鳴らして
くれると思います。
終了するには「E」と一文字だけ入力します、数秒ウエイトを取った後に
サンプルの実行を終了します。
プログラム内容についても色々と解説したい気分ですが、ここまででも大分
長くなってしまったので省略しておきます。
実際のコードが全てを語ってくれることでしょう :-)