Web MIDI APIを扱うためのMIDI基礎知識
@armorik83です。WebAudio Web MIDI API Advent Calendar 2016の18日目は、Web MIDI APIを扱う上での基礎知識となるMIDIについて解説します。
MIDI
MIDIは1981年に策定され一度も改訂されることなく現代まで続いている長寿規格です。DTM(デスクトップミュージック、打ち込み)だけでなくカラオケ店でも日常的に使用されており、(音楽を扱う)舞台で使用されたことをきっかけに舞台照明の操作などに扱われる規格にもなっています。そして、現代ではWeb MIDI APIを通じてブラウザで扱うこともできるようになりました。
照明機器の操作にも使われているということは、音楽のためと思わなくてもよいのです。実際に筆者は、MIDIキーボードをペンタブの側に置いてショートカットの代わりに使っている、というイラストレーターの話を聞いたことがあります。Web MIDI APIを用いて、音楽アプリに限らずとも新しい入力装置を使ったアプリが生まれるかもしれませんね。本稿がその一助になれば幸いです。
MIDIとは
MIDIはMusical Instrument Digital Interfaceの頭文字から取って名付けられたもので、電子楽器の演奏データを機器間でデジタル転送するための世界共通規格を指します。
古いウェブサイトだと、開くと同時に音楽が鳴り出し「MIDIが鳴り出した」という表現をしているものも見受けられますがこれは誤解で、MIDI規格に則って作成されたMIDIデータ(正確にはStandard MIDI File)の再生が始まったというのが正しく、MIDIという語自体に音楽データ、楽譜データというニュアンスは含まれないことに注意してください。
MIDIメッセージとは
MIDI規格上で通信される情報をMIDIメッセージと呼びます。MIDIメッセージは全てバイト列で実現され、書き表す場合には16進数を用います。MIDIメッセージの送受信が可能な機器を、俗にMIDI機器と呼ぶことがあります。
Web MIDI APIに触れる
requestMIDIAccess
すでに探せば解説記事は複数出てきますが、本稿でも述べておきます。まずJavaScript上でMIDI機器を扱うためにMIDIAccess
を得る必要があります。MIDIAccess
は接続されているMIDI機器を返します。
window.navigator.requestMIDIAccess()
そのためにrequestMIDIAccess()
を呼びます。requestMIDIAccess()
はPromiseを返すので、そのthen()
でMIDIAccess
が得られます。
window.navigator.requestMIDIAccess().then((midi) => {
//
})
MIDIAccess
MIDIAccess
はinputs
やoutputs
というプロパティを持ちます。ここから接続されているMIDI機器のMapを取得します。
window.navigator.requestMIDIAccess().then((midi) => {
midi.inputs .forEach((input) => console.log(input))
midi.outputs.forEach((output) => console.log(output))
})
これで入出力ともに接続可能な機器がログに書き出されます。
MIDIOutput#send()
MIDIメッセージを送信するためには、MIDIOutput
に対してsend()
を呼びます。まず、MIDIOutput
を得ましょう。
window.navigator.requestMIDIAccess().then((midi) => {
const output = Array.from(midi.outputs).map((output) => output[1])[0]
})
最初のMIDIOutput
の取得を1行で書くとこのようになります。Array.from(midi.outputs).map()
の辺りは好みなので、for of
文でもかまいません。const output
にはMIDIOutput
インスタンスが格納されます。
次に、このMIDIOutput
に対してsend()
を呼びます。このとき、send()
の引数に注意しなければなりません。ここでMIDIメッセージに対する基礎知識が活かされます。
MIDIOutput#send()
の引数
send()
の第一引数data
はsequence<octet>
を受取ります。バイト列とほぼ同義とみてよいです。長大なMIDIメッセージの送信などのためにUint8Array
の使用が許可されていますが、利便性のためにlength
が3のArray
の使用も可能です。
output.send(new Uint8Array([0x90, 0x45, 0x7f]))
すなわち上記は次のように書くことが許容されます。
output.send([0x90, 0x45, 0x7f])
また、下記のように書くこともできます。
output.send([0x90, 69, 127])
いまここで、index 0
のみ16進数で表記しindex 1
および2
については10進数で表記しましたが、これには理由があります。その理由を次の節にて解説します。
MIDIメッセージ
2種類のバイト
先ほど、MIDIメッセージは全てバイト列で実現されると述べました。このバイト列の仕様について、もう少し詳しく解説します。
MIDIメッセージに使用されるバイトは、ステータスバイトかデータバイトの2種類に分けられます。ステータスバイトとは0x80
から0xff
までの128個のバイトを指し、データバイトとは0x00
から0x7f
までの128個のバイトを指します。
MIDIメッセージは複数のバイトによって連なるバイト列によって表されますが、すべてのMIDIメッセージはステータスバイトを先頭に、その後ろにデータバイトが連なるように策定されています。ステータスバイトによって、その後ろのデータバイト長は確定し、すべて固定長です。(ただしSystem Exclusive Messages
を除く、本稿では割愛)
前節の例をもう一度取り上げましょう。
output.send([0x90, 0x45, 0x7f])
このうち0x90
はステータスバイト、0x45
, 0x7f
はデータバイトです。データバイトは0から127の128段階の値とおおむね見なしてよいので、これらを次のように10進数に書き直すと便利なのです。
output.send([0x90, 69, 127])
では10進数に書き直さなかったステータスバイトとはなんでしょうか。
ステータスバイト
ステータスバイトは0x80
から0xff
までの128種類存在すると述べました。これらには1つずつ意味が定義されています(欠番も存在します)。
ステータスバイトはいくつかの種類に分けられます。システムメッセージ、チャンネルボイスメッセージ、チャンネルモードメッセージという大きく3種類に分けることができます(仕様書上の分類を解説用に簡略化しています)
0xfn
システムメッセージ
0xf0
から0xff
のバイトはシステムメッセージを扱います。高等な機能を扱えますが、本稿では紹介しません。
0x8n
から0xen
チャンネルボイスメッセージ
0x8n
から0xen
はチャンネルボイスメッセージを表します(例外あり、後述)。n
は任意の数で0
からf
を取りうると解釈してください。
上位4ビットがチャンネルボイスメッセージの種類を表し、下位4ビット(n
の部分)はチャンネル番号を表します。例を挙げましょう。
0x9n
は全てノートオンメッセージです。ここで0x90
であればチャンネル1に対するノートオン、0x91
であればチャンネル2に対するノートオンです。これが16チャンネル分存在するので、0x9f
はチャンネル16に対するノートオンとなります。
いくつか代表的なチャンネルボイスメッセージを紹介します。
0x8n
ノートオフ
n
に指定したチャンネル内の対象の音を止める。止める対象の音は後続のデータバイトにて指定する。データバイト長は2。
- 第1データバイト
- ノートナンバー
- 第2データバイト
- オフベロシティ
0x9n
ノートオン
n
に指定したチャンネル内の対象の音を鳴らす。鳴らす対象の音は後続のデータバイトにて指定する。データバイト長は2。
- 第1データバイト
- ノートナンバー
- 第2データバイト
- ベロシティ
0xen
ピッチベンド
n
に指定したチャンネルで鳴っている音のピッチを変える。MSB (Most Significant Byte) 128段階の1段階ずつをさらにLSB (Least Significant Byte) で128分割しているので、計16384段階の細かい指定ができる。データバイト長は2。
- 第1データバイト
- ピッチベンド値MSB
- 第2データバイト
- ピッチベンド値LSB
0xbn 0x78
から0xbn 0x7f
チャンネルモードメッセージ
チャンネルモードメッセージだけ特殊で、奇妙な仕様をしています。0xbn
のあと第1データバイトが0x78
から0x7f
だったときに限り、それらはチャンネルモードメッセージとなります。0xbn
のあとが0x00
から0x77
だった場合は前項でのチャンネルボイスメッセージとなり、コントロールチェンジとして扱われます。
チャンネルモードメッセージは歴史的経緯のような雰囲気を感じるため、あまり覚えなくてよいです。
ノートナンバーとベロシティ
最後にノートオン、ノートオフのデータバイトとして登場したノートナンバーとベロシティについて説明しましょう。
ノートナンバーは中央のドの音を60
と定め、(MIDIメッセージとして表現できうる)一番低い音を0
、一番高い音を127
と定義している音程の番号です。なお、グランドピアノは88鍵盤が備わっており、これらの範囲は21
から108
となっているため、128段階の分け方でも十分賄えているということです。
ベロシティは強弱を表します。127
が最も強く、1
が最も弱いです。0
の場合は無音、すなわちノートオフと同義とみなされます。強さについては主観的解釈が大きいですが、仕様書上ではmp(メゾピアノ)からmf(メゾフォルテ)の間の強さが64
であると定義されています。
まとめ
これらの知識をもとに再度次の処理を読んでみましょう。
output.send([0x90, 69, 127])
-
0x90
-
0x9n
はノートオン -
0x90
はチャンネル1に対してのメッセージ
-
-
69
- ノートナンバー
-
69
は中央ドから少し上のラの音
-
127
- ベロシティ
-
127
は最も強く鳴らすということ
つまりこれは、output
に対して「チャンネル1はラの音を最も強く鳴らせ」という命令を.send()
していると解釈できるのです。
ノートナンバーやベロシティは若干の音楽知識を求められるようにも見えますが、ノートナンバーは結局キーコードと同じようなもので、ベロシティは単なる強弱です。なので、ここまでで解説した話はほとんどがバイト列に関するものであるため、エンジニアにも親しみやすいですね。
デバッグに便利なアプリケーション
MIDIMonitorというアプリケーションをお勧めします(macOS用)。macOSには最初からIAC Driverという仮想MIDIドライバが備わっているため、MIDI機器をひとつも所持していなくてもWeb MIDI APIを楽しむことができます。
それでは。
Discussion