monoの開発ブログ

HSPTV部門でMIDIによる効果音再生

winmm.dllを用いてMIDIを制御する方法はHSPプログラムコンテスト界隈では割と知られたテクニックのようですが、自分用メモも兼ねてまとめてみます。

MIDIを扱う上での注意点

MIDIは多くの人に利用されており、インターネット上にある情報はプログラマ向けではないことがほとんどです。そうした一般向けの情報ではさまざまな部分で番号が1から始まっている(1 origin)いますが、プログラムからMIDIを制御する場合には、1を引いて0から始まる番号(0 origin)で指定する必要があります。

初期化と終了

初期化はmidiOutOpen関数、終了はmidiOutClose関数で行います。midiOutOpen関数には引数が5つありますが、第2引数以降は記述を省略することによって0を渡しています。なお、HSPTV部門ではプログラム終了時にプロセスが終了するとは限らないため、必ず終了処理を行わなければなりません。

#uselib "winmm"#func midiOutOpen     "midiOutOpen"     var, int, int, int, int#func midiOutShortMsg "midiOutShortMsg" int, int#func midiOutClose    "midiOutClose"    int  midiOutOpen midi  onexit *close  // ...*close  midiOutClose midi  end

メッセージの送信

MIDIの制御はすべてmidiOutShortMsg関数でメッセージを送信して行います。メッセージの生成にはマクロを利用すると便利です。今回定義したMIDI_MSGマクロでは、第1引数のメッセージを第2引数のチャンネルに対して第3・第4引数のデータとともに送信するための値を生成できます。ただし、HSPでは定数の演算がコンパイル時に行われないため、容量の増加を抑えるためには#constを使ってコンパイル時に計算させる必要があります。

#define ctyle MIDI_MSG(%1, %2, %3, %4) \  (((%1) + (%2)) | ((%3) << 8) | ((%4) << 16))#const TEMP MIDI_MSG(message, channel, low, high)midiOutShortMsg midi, TEMP#undef TEMP

音色の設定

音色の設定にはプログラムチェンジというメッセージを利用します。以下の例ではチャンネル1の楽器をアコースティックピアノに設定しています。指定できる楽器の種類はこちらの一覧をみると分かります。ただし、はじめに述べたように楽器の番号が1 originになっているため、表にあるNo.から1引いた値を指定する必要があることに注意してください。

#const PROGRAM_CHANGE 0xc0#const ACOUSTIC_PIANO 0#const TEMP MIDI_MSG(PROGRAM_CHANGE, 0, ACOUSTIC_PIANO, 0)midiOutShortMsg midi, TEMP#undef TEMP

再生と停止

再生にはノートオン、停止にはノートオフというメッセージを利用します。なお、楽器によってはノートオフしなくても勝手に停止してくれるものもあります。

ノートオンメッセージでは、MIDI_MSGマクロの第3引数にノート番号(音階)、第4引数にベロシティ(強さ)を指定します。ノート番号は中央のC(ド)を60として0から127の範囲で指定できます。ベロシティは0から127の範囲で指定でき、0を指定した場合にはノートオフと同じ扱いになります。

ノートオフメッセージでは、MIDI_MSGマクロの第3引数にノート番号(音階)、第4引数にベロシティ(強さ)を指定します。ノート番号はノートオンの時に利用した値を指定します。ベロシティは0から127の範囲で指定できますが、あまり使われていないようです。

#const NOTE_ON  0x90#const NOTE_OFF 0x80#const TEMP MIDI_MSG(NOTE_ON, 0, 60, 127)midiOutShortMsg midi, TEMP#undef TEMPwait 100#const TEMP MIDI_MSG(NOTE_OFF, 0, 60, 0)midiOutShortMsg midi, TEMP#undef TEMP

パーカッションの再生

チャンネル10はパーカッション用になっており、ノート番号で楽器を指定して再生できます。指定できる楽器の一覧はこちらをご覧ください。こちらの表にある番号もやはり1 originになっているので1引いて指定してください。

#const SNARE_DRUM_1 37#const TEMP MIDI_MSG(NOTE_ON, 9, SNARE_DRUM_1, 127)midiOutShortMsg midi, TEMP#undef TEMP

参考