サウンドの設計 (音情報管理設計)
前回にて音の発生タイミングと種類の概要を考えてみましたが、今回はもう少し具体的なレベル(プログラムでいうクラス化等)で検討してみたいと思います。
ここでポイントなのは、前回でも述べたようにサウンドの設計というのは、映像の設計に比べてハードウェア、OS、ライブラリ、言語により設計がかなり変化してしまうことが挙げられます。
そして本ブログの現時点では「Java」と「XNA(C#)」の標準ライブラリを用いての解説となるので、次回はこれらの実装について具体的に検討できればと思っています。
ちなみに私個人の印象ですが、設計の難易度は「Java > XNA」という考えがあり、難易度の高いもの程、設計が大変(面倒)になります。
しかし、ここではできるだけ抽象化したフレームワーク設計を行い、言語が変化してもできるだけ設計が変化しないようなものを目指して行ければと思います。
ということで、検討してみましょう。
【プリミティブな音情報】
前回でも説明しましたが、サウンドには効果音とBGMという物に分類が行え、且つこれらの属性によりデータフォーマットが違うケースが多いということを説明しました。
そこで以下のプリミティブな音情報をクラス化します。
1.音の名前を判別するID。
2.音の種別(SE, BGM)。
3.音のデータフォーマット(WAVE, MIDI, MP3等)。
4.音のファイルの存在する場所。
5.ループ回数。
6.音声発声開始位置。
7.音声発声終了位置。
このうち「3」は言語がサポートしている物に依存します。
次に「4」はファイル名そのままの場合もありますし、サウンドファイルを読み込んだ際に取得できるハンドルであったりします。これは言語のサウンドライブラリの仕様により改変すると良いと思います。今回の設計ではファイル名のフルパスを入れて置くという設計となります。
そして、「6」「7」に関しては、サウンドファイルが1つで全てを表す仕様のもの(例えはCDで1トラックに全ての曲が連続して収録されている場合等)や、MIDIファイルのようなトラック先頭には無音を入れる(べき)仕様(これはMIDIモジュールの初期化を行うためとされています)のファイルであるが、先頭の無音をスキップ(できる場合はしたい)したいケースを考慮しての設計となります。
単純なコーディング例(Java)
【プリミティブな音管理】
音の情報が定義できたので、次は音の管理です。管理といっても発声の実施が主となり、サウンドデバイスの管理や設計により音情報の管理を行います。
後者の音情報の管理というのは、「ゲーム上の音情報を全て内包し、タスクから呼び出しを容易にする」事を意味し、タスクから発声の際にワザワザ上述の7種類程の情報を渡さないで呼び出させる必要のないようにできる仕組みの事となります。
例えば音管理を経由して戦闘機から弾を発射するSEを発声させたい時に、以下のコーディングを行う場合(以下は例えです)、
// 現時点で発声させるための情報を渡す
SoundItem se = new SoundItem(
SoundItem.SE,
SoundItem.WAV,
"Sounds/ShipMissile.wav");
m_soundManager.play(se);
と記述よりも、
// ID 指定
m_soundManager.play(SoundID.ShipMissile);
の方が分り易いので、このように先に音管理の中でデータベースを持つという設計をする方法もあります。
しかしながら、ある程度柔軟に設計をしたいと考えるので、今回は上述の2つのインターフェースを持ったものを検討します。
音情報はどこかのタイミング(主にゲームの開始になるとは思います)で音管理に全て登録され、タスクからは音管理にリクエストを行えば良いだけになるように設計します。
今回の設計では、音情報はマップを用いて管理してみましょう。
単純なコーディング例(Java)
というような感じで、音情報と音管理を設計してみました。
ソースから見て判るとおり、音管理内の肝心な発声を行う個所が完全に抜けている状況です。
これ以降の設計は言語依存となってくるので、まずはJavaの方の実装を次回より検討して行こうかと思います。
ここでポイントなのは、前回でも述べたようにサウンドの設計というのは、映像の設計に比べてハードウェア、OS、ライブラリ、言語により設計がかなり変化してしまうことが挙げられます。
そして本ブログの現時点では「Java」と「XNA(C#)」の標準ライブラリを用いての解説となるので、次回はこれらの実装について具体的に検討できればと思っています。
ちなみに私個人の印象ですが、設計の難易度は「Java > XNA」という考えがあり、難易度の高いもの程、設計が大変(面倒)になります。
しかし、ここではできるだけ抽象化したフレームワーク設計を行い、言語が変化してもできるだけ設計が変化しないようなものを目指して行ければと思います。
ということで、検討してみましょう。
【プリミティブな音情報】
前回でも説明しましたが、サウンドには効果音とBGMという物に分類が行え、且つこれらの属性によりデータフォーマットが違うケースが多いということを説明しました。
そこで以下のプリミティブな音情報をクラス化します。
1.音の名前を判別するID。
2.音の種別(SE, BGM)。
3.音のデータフォーマット(WAVE, MIDI, MP3等)。
4.音のファイルの存在する場所。
5.ループ回数。
6.音声発声開始位置。
7.音声発声終了位置。
このうち「3」は言語がサポートしている物に依存します。
次に「4」はファイル名そのままの場合もありますし、サウンドファイルを読み込んだ際に取得できるハンドルであったりします。これは言語のサウンドライブラリの仕様により改変すると良いと思います。今回の設計ではファイル名のフルパスを入れて置くという設計となります。
そして、「6」「7」に関しては、サウンドファイルが1つで全てを表す仕様のもの(例えはCDで1トラックに全ての曲が連続して収録されている場合等)や、MIDIファイルのようなトラック先頭には無音を入れる(べき)仕様(これはMIDIモジュールの初期化を行うためとされています)のファイルであるが、先頭の無音をスキップ(できる場合はしたい)したいケースを考慮しての設計となります。
単純なコーディング例(Java)
// 音情報 public class SoundItem { // 音声種別 public enum Type { SE, BGM, } // 音声ファイル種別(これは状況により改変) public enum DataFormatType { WAV, MIDI, MP3, } // ID public int m_id; // 種別 (enum Type) public Type m_type; // データフォーマット種別 (enum DataFormatType) public DataFormatType m_dataFormatType; // ファイル public String m_filename; // ループ回数 (0で無限) public int m_loopCount = 0; // ループ再生回数 public int m_playCount = 0; // 再生開始シーク位置 public long m_startSeekPoint = 0; // 再生終了位置 (-1で最後まで) public long m_endSeekPoint = -1; } |
【プリミティブな音管理】
音の情報が定義できたので、次は音の管理です。管理といっても発声の実施が主となり、サウンドデバイスの管理や設計により音情報の管理を行います。
後者の音情報の管理というのは、「ゲーム上の音情報を全て内包し、タスクから呼び出しを容易にする」事を意味し、タスクから発声の際にワザワザ上述の7種類程の情報を渡さないで呼び出させる必要のないようにできる仕組みの事となります。
例えば音管理を経由して戦闘機から弾を発射するSEを発声させたい時に、以下のコーディングを行う場合(以下は例えです)、
// 現時点で発声させるための情報を渡す
SoundItem se = new SoundItem(
SoundItem.SE,
SoundItem.WAV,
"Sounds/ShipMissile.wav");
m_soundManager.play(se);
と記述よりも、
// ID 指定
m_soundManager.play(SoundID.ShipMissile);
の方が分り易いので、このように先に音管理の中でデータベースを持つという設計をする方法もあります。
しかしながら、ある程度柔軟に設計をしたいと考えるので、今回は上述の2つのインターフェースを持ったものを検討します。
音情報はどこかのタイミング(主にゲームの開始になるとは思います)で音管理に全て登録され、タスクからは音管理にリクエストを行えば良いだけになるように設計します。
今回の設計では、音情報はマップを用いて管理してみましょう。
単純なコーディング例(Java)
// 音管理 public class SoundManager extends HashMap<Integer, SoundItem> { //---- // 音の許可 //---- // システムとして public boolean m_enableSystem = true; // SE public boolean m_enableSE = true; // BGM public boolean m_enableBGM = true; // デバイス // SE デバイス //SoundDeviceSE m_deviceSE; // BGM デバイス //SoundDeviceBGM m_deviceBGM; // 初期化を行います public boolean initialize() { // デバイスの初期化を行います return true; } // 項目を追加します public void add( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename) { add(id, type, dataFormatType, filename, 1, 0, -1); } // 項目を追加します public void add( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename, int loopCount) { add(id, type, dataFormatType, filename, loopCount, 0, -1); } // 項目を追加します public void add( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename, int loopCount, long seekStartPoint, long seekEndPoint) { // システムとして音が許可されていない場合は抜けます if(m_enableSystem == false) { return; } // 既に登録されいているアイテムを削除します remove(id); // アイテム情報を設定します SoundItem item = new SoundItem(); item.m_id = id; item.m_type = type; item.m_dataFormatType = dataFormatType; item.m_filename = filename; item.m_loopCount = loopCount; item.m_startSeekPoint = seekStartPoint; item.m_endSeekPoint = seekEndPoint; // アイテム登録 put(id, item); return; } // 音を再生します public void play(int id) { SoundItem item = get(id); if(item == null) { return; } play( item.m_id, item.m_type, item.m_dataFormatType, item.m_filename, item.m_loopCount, item.m_startSeekPoint, item.m_endSeekPoint); } // 音を再生します public void play(int id, int loopCount) { SoundItem item = get(id); if(item == null) { return; } play( item.m_id, item.m_type, item.m_dataFormatType, item.m_filename, loopCount, item.m_startSeekPoint, item.m_endSeekPoint); } // 音を再生します public void play( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename, int loopCount, long seekStartPoint, long seekEndPoint) { // システムとして音が許可されていない場合は抜けます if(m_enableSystem == false) { return; } // 種別毎にサウンドデバイスにリクエストします if(type == SoundItem.Type.SE) { if(m_enableSE == true) { //m_deviceSE.pause(item.id, ...); } } else { if(m_enableBGM == true) { //m_deviceBGM.pause(item.id, ...); } } } // 音を一時停止します public void pause(int id) { SoundItem item = get(id); if(item == null) { return; } pause(item.m_id, item.m_type, item.m_dataFormatType, item.m_filename); } // 音を一時停止します public void pause( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename) { // 種別毎にサウンドデバイスにリクエストします if(type == SoundItem.Type.SE) { //m_deviceSE.pause(item.id, ...); } else { //m_deviceBGM.pause(item.id, ...); } } // 音を停止します public void stop(int id) { SoundItem item = get(id); if(item == null) { return; } stop(item.m_id, item.m_type, item.m_dataFormatType, item.m_filename); } // 音を停止します public void stop( int id, SoundItem.Type type, SoundItem.DataFormatType dataFormatType, String filename) { // 種別毎にサウンドデバイスにリクエストします if(type == SoundItem.Type.SE) { //m_deviceSE.stop(item.id, ...); } else { //m_deviceBGM.stop(item.id, ...); } } // 音を全て一時停止します public void stopPause() { // 登録されているアイテムを全て走査 Iterator it = keySet().iterator(); while(it.hasNext() == true) { SoundItem item = get(it.next()); // 種別毎にサウンドデバイスにリクエストします if(item.m_type == SoundItem.Type.SE) { //m_deviceSE.pause(item.id, ...); } else { //m_deviceBGM.pause(item.id, ...); } } // もしくは一気に...(これはライブラリによります) //m_deviceSE.pause(); //m_deviceBGM.pause(); } // 音を全て停止します public void stopAll() { // 登録されているアイテムを全て走査 Iterator it = keySet().iterator(); while(it.hasNext() == true) { SoundItem item = get(it.next()); // 種別毎にサウンドデバイスにリクエストします if(item.m_type == SoundItem.Type.SE) { //m_deviceSE.stop(item.id, ...); } else { //m_deviceBGM.stop(item.id, ...); } } // もしくは一気に...(これはライブラリによります) //m_deviceSE.stop(); //m_deviceBGM.stop(); } } |
というような感じで、音情報と音管理を設計してみました。
ソースから見て判るとおり、音管理内の肝心な発声を行う個所が完全に抜けている状況です。
これ以降の設計は言語依存となってくるので、まずはJavaの方の実装を次回より検討して行こうかと思います。
コメント
コメントの投稿
« 昔のゲームの想い出 [0048] 「凄ノ王伝説」 [ハドソン] [1989] [PCエンジン] l Home l 昔のゲームの想い出 [0047] 「ハイパーオリンピック」 [コナミ] [1983] [アーケード] »