人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

サウンドエンジン

WAVEの再生(2004/10/03)で紹介したAudioClipを使えばJavaで音を鳴らせます。しかし、 WAVEファイルの同時再生をしたり、連続再生をしたり、ほかのアプリケーションでBGMを鳴らしていたりすると音が鳴らない場合があります。これらの問題を解決するのがJava Sound APIです。

Java Sound APIはけっこう複雑なAPIでサウンド1つ鳴らすのも結構大変です。このサウンドエンジンを使うとJava Sound APIの知識なしに簡単にサウンドを再生できるようになります。サウンドエンジンは現在MIDIとWAVE対応です。

  • MidiEngine(MIDI ファイル再生用)
  • WaveEngine(WAVEファイル再生用)
  • DataClip(WAVEデータを格納)

の3つからなっています。この3つをコピーすればいろんなアプリケーションで簡単に音が鳴らせます。再配布・改造何でも自由です。

sound_engine.jar

MIDIを再生する

細かい実装は置いといてサウンドエンジンの使い方だけ説明します。まずはMIDIファイルの再生です。MIDIはBGMで使います。MIDIの再生手順です。

  1. MidiEngine.load(ファイル名)でMIDIファイルをロード
  2. MidiEngine.play(番号)で再生
  3. MidiEngine.stop()で停止

むちゃくちゃ簡単ですね。クラス名.メソッドとなっている点に注意してください。load()もplay()もstaticメソッドなのでMidiEngineのオブジェクトを作る必要がないのです。具体例です。

    // MIDIをロード
    try {
        MidiEngine.load("midi/lovesong.mid");
    } catch (MidiUnavailableException e) {
        e.printStackTrace();
    } catch (InvalidMidiDataException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 0番目のMIDIを再生
    MidiEngine.play(0);
    
    // 再生中のMIDIを停止
    MidiEngine.stop();

余計なものがぐちゃぐちゃついてますがこれらは仕方ないのでつけてください。いろいろ例外があるのですべてcatchしてやる必要があります。ただ本当に大事なのは赤色の部分です。play(0)の0は0番目にロードしたMIDIを再生するという意味です。load()で好きなだけ(最大256曲)ロードしておいてから play()で番号を指定するとその曲が再生されます。ロードされた順に番号がつきます。play()すると前の曲は停止してから新しい曲が再生されます。自分で停止する必要はありません。stop()は曲を停止します。あと再生が終了したらあたまに戻ってループ再生されます。ゲームで使う場合はこのほうがいいでしょう。

WAVEを再生する

次にWAVEファイルです。WAVEは効果音で使います。MIDIと違って少し複雑です。WAVEの再生手順です。

  1. WaveEngine.load(ファイル名)でWAVEファイルをロード
  2. ゲームループ内でWaveEngine.render()
  3. WaveEngine.play(番号)で再生
  4. WaveEngine.stop(番号)で停止

MidiEngineと同様WaveEngineもオブジェクトを作る必要はありません。WAVEファイルの再生はplay()で始まりますが、ゲームループ内でrender()を呼び続ける必要があります。ゲームループ内でWAVEファイルから少しずつデータを読みとって再生するためです。ゲームループを作らないと再生できないのでちょっと面倒ですが、ゲームならたいていループは作るはずなので問題ないと思います。あとstop()で番号を指定する点に注意してください。MIDIの場合は1つの曲しか再生できず現在再生中の曲を停止すればよいので番号はいりませんでした。一方、WAVEはいくつもの効果音を同時に鳴らすことができるのでどのWAVEの再生を停止するか番号で指定する必要があります。具体例です。

    // WAVEファイル名
    private static final String[] waveNames =
            {"attack.wav", "spell.wav", "escape.wav"};
    
    // WAVEをロード
    for (int i=0; i<waveNames.length; i++) {
        try {
            WaveEngine.load("wave/" + waveNames[i]);
        } catch (UnsupportedAudioFileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }
    
    // WAVEを再生
    WaveEngine.play(0);
    WaveEngine.play(1);
    
    // 1番目のWAVEを停止
    WaveEngine.stop(1);

WAVEのファイル名を配列に入れておいてループで読み込んでます。ゲームループは下のようになってます。

    public void run() {
        while (true) {
            // サウンドのレンダリング(WAVEのみ必要)
            WaveEngine.render();
    
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

サウンドエンジン(改良版)

sound_engine2.jar

名前を指定して曲を登録・再生できるように改良しました。たとえば、

    midiEngine.load("castle", "bgm/castle.mid");
    waveEngine.load("treasure", "sound/treasure.wav");

    midiEngine.play("castle");
    waveEngine.play("treasure");

といった感じです。またWaveEngineでClipを使うように変更したのでレンダリングは不要になりました。ただ上の例を見るとわかりますがオブジェクトを作る必要があります。上のJARファイルはダブルクリックしても実行できません。解凍してWaveEngine.java、MidiEngine.javaをご自分のプログラムに追加するか、JARファイルをインクルードしてご利用ください。

追記

  • MIDI を再生するときに直前に再生していたMIDIによってメロディが変わってしまうバグがあります。直そうと思ったのですがどうも原因がわかりません・・・もしわかった方は教えていただけるとうれしいです。
  • 藍川翡翠さんに原因を教えてもらいました。使用しているMIDIファイルにGM、XG、GSリセット命令が入っていない場合にメロディが変わってしまうようです。MIDIを鳴らすときに強制的にリセット命令を入れたいのですが今度はその方法が・・・
  • Hetさんに下記のバグ報告をいただきました。MidiEngineのMIDI登録数の最大値チェックが抜けていました。適宜修正していただければと思います。
Het   2010/07/04 09:42
いつも参考にさせてもらっています。
今更ながら、バグ報告をしても宜しいでしょうか?
sound_engine2のMidiEngine.java内、counter変数は初期化後ずっと0のままなので、
92行目のcounterをmidiMap.size()に置き換えるべきかと思います。
sound_engineの方は最大値チェック自体が無いですね。