001-まとめ  前へ←  ホームへ  →次へ  002-02

No.002 - 01  MIDI再生の基本


筆者音楽系は苦手なのになぜかこんなコーナー。
MIDIシーケンサのソース置いてるサイトってあんまナイですね。


1.最低限の手順

さて最低限MIDI再生に必要な手順はこれだけです。

1. midiOutOpen() を使ってMIDIデバイスを開く(MIDI音源を使えるようにします)
2. midiOutShortMsg() を使って音を鳴らす
3. midiOutClose() を使ってMIDIデバイスを閉じる

2.実際の手順

音を鳴らすだけなら上記だけですが実際にやろうと思えば簡単じゃないです。
手順だけでもこんなカンジ。

1. timeGetDevCaps() を使ってタイマ・デバイスの能力を取得(最小何ミリ秒まで扱えるのか調べます)
2. timeBeginPeriod() を使ってタイマ解像度をセット
3. midiOutOpen() を使ってMIDIデバイスを開く(MIDI音源を使えるようにします)
4. timeSetEvent() を使ってタイマーセット       ←┐
5. midiOutShortMsg() を使って音を鳴らす(繰り返し)
6. timeEndPeriod() を使ってタイマ解像度を解除
7. midiOutClose() を使ってMIDIデバイスを閉じる



3.タイマ関数

実際MIDIを再生するにはタイマーを使って音を鳴らしていくワケですが
windowsタイマのWM_TIMERではメッセージの優先順位が低く精度が悪くて使い物にはなりません。

そこでタイマには『マルチメディア・タイム・ファンクションtimeSetEvent()』を使います。

timeSetEvent()高精度のタイマを持っていますが、代わりにこの関数内から呼べるWindowsAPIは8種類しかありません。
PostMessageとデバッグ用のOutputDebugStrと
4種のタイマ関数timeSetEvent(), timeKillEvent(), timeGetTime(), timeGetSystemTime() )と
2種のMIDI出力関数(midiOutShortMsg(), midiOutLongMsg() )だけです。

つまりこのタイマ関数はMIDI再生のための関数であって、それ以外にはほとんど使えないというこってす。

4.詳細な手順

1.

タイマ・デバイスの能力を取得(タイマの最小誤差が何ミリ秒までなのか調べます))
timeGetDevCaps(TIMECAPS
*ptc,UINTsize)

          まず  『 タイマ・デバイス・ドライバ』  が使える解像度最小値・最大値調べます(ミリ秒単位)。
TIMECAPS構造体のwPeriodMinに最小値、wPeriodMaxに最大値が返ってきます。
多くの場合最小値が1, 最大値が65535になります。
 
2. timeBeginPeriod(UINTperiod) を使ってタイマ解像度をセット
アプリケーションやタイマ・デバイス・ドライバで使う誤差の最小ミリ秒を設定します。
引数はtimeGetDevCaps()で得られた値の範囲内でなければいけません。
そして終了時にはtimeBeginPeriod()を呼び出した回数だけtimeEndPeriod()を必ず呼ばなければなりません。
 
3. midiOutOpen(HMIDIOUT*phmo, UINT id, DWORDdwCallback, DWORDdwCallbackInstance, DWORDdwFlags)
という関数でMIDIデバイスを開く
MIDI音源を使えるようにします。他のプログラム等が既にMIDIを使っている場合、この関数は失敗します。
第1引数はHMIDIOUT構造体へのポインタです。ここに返ってきたMIDI出力用のハンドルでこの後のMIDI操作を行います。
第2引数はデバイスIDを指定します。0からmidiOutGetNumDevs()の戻り値-1までの値を渡す必要がありますが、ほとんどの場合MIDIMAPPERという定義値で事足ります。
第3、4、5引数はとりあえず0かNULLを指定してください。
 
4. timer_id = timeSetEvent(UINTdelay, UINTres, DWORD CallbackFunc, DWORD dwData,DWORD flag)
を使ってタイマーセット(繰り返し)
タイマをセットします。
delay ミリ秒後に CallbackFunc を呼び出します。但し res ミリ秒の誤差を伴います。
restimeBeginPeriod()で渡した値以上でなければいけません。
dwDataCallbackFunc 呼出し時に渡されるユーザ・データです。

最後の引数flag にはTIME_ONE_SHOTTIME_PERIODIC を渡します。
TIME_ONE_SHOT ならdelay ミリ秒後に CallbackFunc1度だけ呼び出します。
TIME_PERIODIC  ならdelay ミリ秒経過するたびに CallbackFunc繰り返し呼び出します。
タイマ停止にはtimeKillEvent(timer_id)を呼び出して下さい(TIME_ONE_SHOTでCallbackFunc呼出し後なら不要)。

MIDI再生の場合毎回タイマ間隔が異なるのでTIME_ONE_SHOTCallbackFunc1度ずつ呼び出していきます。
 
5. midiOutShortMsg(HMIDIOUThmidi, DWORDdwMessage) を使って音を鳴らす(繰り返し)
メインとなる関数です。
第1引数はmidiOutOpen()で得られたハンドルです。
第2引数は最大3バイトから成るMIDIメッセージで、第4バイトは常に0です
MIDIメッセージの第1バイトはステータスバイト、第2、3バイトはデータバイトとなっており
第1バイトの上位8ビットで命令コード、下位8ビットでチャンネル番号を指定します。
例えば 0x90 は音をならす命令コードでチャンネル2の音を鳴らしたければ 0x91といった具合です。
第2、3バイトのデータはそれぞれの命令コードに対応した値を渡します。
音を鳴らすのであれば第2バイトは音階をあらわすキー番号、第3バイト音量を示す打鍵速度になります。
MIDIメッセージ データバイト 補足
音を消す Note off
(ノートオフ)
0x8n kk kk == キー番号(音階)…0〜127
音を鳴らす Note on
(ノート オフ)
0x9n kk vv kk== キー番号(音階) …0〜127
vv
== 打鍵速度(音量) …0〜127
打鍵速度0で音を消す
音色の変更  Program Change 0xCn pp pp == プログラム

詳細な表は下記にあります
6. timeEndPeriod(UINTperiod) を使ってタイマ解像度を解除
timeBeginPeriod()を呼び出した回数だけtimeEndPeriod()を呼びます。
7. midiOutClose() を使ってMIDIデバイスをを閉じる
MIDIデバイスを解放します。



5.MIDIメッセージ

種類 MIDIメッセージ データバイト
音を消す Note off
(ノートオフ)
0x8n kk kk == キー番号(音階)…0〜127
音を鳴らす Note on
(ノート オフ)
0x9n kk vv kk== キー番号(音階) …0〜127
vv
== 打鍵速度(音量) …0〜127
アフタータッチ(個々の音) Polyphonic After Touch
(ポリフォニック アフター タッチ)
0xAn kk tt kk== キー番号(音階) …0〜127
tt == アフタータッチ …0〜127
Control Change
(コントロール チェンジ)
0xBn cc xx cc == コントローラ …0〜121
xx == 値 …0〜127
Channel Mode Local Control
(チャンネル モード ローカル チェンジ)
0xBn 7A xx xx ==0(オフ)、==127(オン)
全ての音を消す All Notes Off
(オール ノート オフ)
0xBn 7B 00
Omni Mode Off 0xBn 7C 00
Omni Mode On 0xBn 7D 00
Mono Mode On 0xBn 7E cc cc == チャネル番号
Poly Mode On 0xBn 7F 00
音色の変更  Program Change 0xCn pp pp == プログラム
アフタータッチ(チャンネルごと) Channel After Touch 0xDn tt tt == アフタータッチ …0〜127
Pitch Wheel Change 0xEn ll hh ll == 下位7ビット …0〜127
hh == 上位7ビット …0〜127

※アフタータッチ…既に押しているキーを更に強く押して音色を変える機能。一部の機器で使える。

メタイベント
第1バイトが0xFF
メタイベントは第1バイトが0xffの特殊メッセージです。
MIDIデバイスに送るためのメッセージではありません。
何種類もありますがアプリケーション側で処理するためのものです。
種類 フォーマット
説明
第2バイト データ長さ
シーケンス番号 0x00 2バイト シーケンス番号。
テキスト 0x01 任意 任意のテキスト文字列。
著作権表示 0x02 任意 著作権表示用のテキスト文字列。トラックチャンクの先頭イベント(タイム0)とする。
シーケンス名
・トラック名
0x03 任意 シーケンス/トラック名を記述するテキスト文字列。
楽器名 0x04 任意 各チャンネルに対応する楽器名を記述する。
歌詞 0x05 任意 歌詞。カラオケで使われる。
マーカ 0x06 任意  
キューポイント 0x07 任意 ビデオとかに指示を出すときの文字データ。
MIDIチャンネル
プリフィックス
0x20 1バイト メタイベントやSysExイベントなどチャンネル属性を持たないイベントに、チャンネル属性を 持たせる。MIDIイベントや次にこのメッセージがでてくるまで有効。
トラック終端 0x2F 0バイト トラックの終了を示す。トラックの最終位置に必ず配置する。
テンポ 0x51 3バイト 4分音符の長さをマイクロ秒(μ秒)単位で表す。アプリケーション側で計算する。
このメッセージがない場合 テンポ120(==500000秒)
SMPTEオフセット 0x54 5バイト SMPTEはシンプティと読む。トラックチャンクがスタートすべきSMPTE時間を示す。トラックの始まりの全ての0でないデルタタイムの MIDIイベントの前に置く。
拍子 0x58 4バイト [a:b:c:d]拍子==a/2のb乗,  メトロノーム1カウントあたりのMIDIクロック数c、4分音符中の32分音符数d。
調号 0x59 2バイト [a:b] a はシャープ・フラット記号の数(フラットの数マイナス値)、b はメジャーのとき0, マイナのとき1。
固有のメタイベント 0x7F 任意 メーカー固有イベント。データはマニファクチャラーズIDを表記する。



●MIDI用語

・アフタータッチ
既に押しているキーを更に強く押して音色を変える機能。一部の機器で使える。
 

・キー番号

キー60 (10進数)を中央のCとする 0〜127 の音階番号。
半音ごとに1上がる。ド…60,   ド#…61,   レ…62,   レ#…63   etc...
 

・チャンネル

1〜16まであり、
各チャンネルに異なる楽器を対応させることが出来る。
・打鍵速度
英語でベロシティ(velocity)。元々キーボードをたたく速度であったほとんどがそのキー音量として扱われてる。


MIDI参考:
   書籍
     ファイルフォーマット事典
     プログラミングWindows第5版(下)
   サイト
      B#MIDIのページ
      ディレクト アート スタジオ詳細MIDI規格のページ  


 001-まとめ  前へ←  ホームへ  →次へ  002-02