Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save okunokentaro/2413f7992ac2cd9bfd668b8e9ad3e038 to your computer and use it in GitHub Desktop.
Save okunokentaro/2413f7992ac2cd9bfd668b8e9ad3e038 to your computer and use it in GitHub Desktop.
Web MIDI APIを扱うためのMIDI基礎知識
2016/12/18 にQiitaに投稿した記事のアーカイブです
---
@armorik83です。[WebAudio Web MIDI API Advent Calendar 2016](http://qiita.com/advent-calendar/2016/webaudio)の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`](https://www.w3.org/TR/webmidi/#idl-def-MIDIAccess)を得る必要があります。`MIDIAccess`は接続されているMIDI機器を返します。
```js
window.navigator.requestMIDIAccess()
```
そのために`requestMIDIAccess()`を呼びます。`requestMIDIAccess()`はPromiseを返すので、その`then()`で`MIDIAccess`が得られます。
```js
window.navigator.requestMIDIAccess().then((midi) => {
//
})
```
## `MIDIAccess`
[`MIDIAccess`](https://www.w3.org/TR/webmidi/#idl-def-MIDIAccess)は`inputs`や`outputs`というプロパティを持ちます。ここから接続されているMIDI機器のMapを取得します。
```js
window.navigator.requestMIDIAccess().then((midi) => {
midi.inputs .forEach((input) => console.log(input))
midi.outputs.forEach((output) => console.log(output))
})
```
これで入出力ともに接続可能な機器がログに書き出されます。
## `MIDIOutput#send()`
MIDIメッセージを送信するためには、[`MIDIOutput`](https://www.w3.org/TR/webmidi/#idl-def-MIDIOutput)に対して`send()`を呼びます。まず、`MIDIOutput`を得ましょう。
```js
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`の使用も可能です。
```js
output.send(new Uint8Array([0x90, 0x45, 0x7f]))
```
すなわち上記は次のように書くことが許容されます。
```js
output.send([0x90, 0x45, 0x7f])
```
また、下記のように書くこともできます。
```js
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`を除く、本稿では割愛)
前節の例をもう一度取り上げましょう。
```js
output.send([0x90, 0x45, 0x7f])
```
このうち`0x90`はステータスバイト、`0x45`, `0x7f`はデータバイトです。データバイトは0から127の128段階の値とおおむね見なしてよいので、これらを次のように10進数に書き直すと便利なのです。
```js
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`であると定義されています。
# まとめ
これらの知識をもとに再度次の処理を読んでみましょう。
```js
output.send([0x90, 69, 127])
```
- `0x90`
- `0x9n`はノートオン
- `0x90`はチャンネル1に対してのメッセージ
- `69`
- ノートナンバー
- `69`は中央ドから少し上のラの音
- `127`
- ベロシティ
- `127`は最も強く鳴らすということ
つまりこれは、`output`に対して「チャンネル1はラの音を最も強く鳴らせ」という命令を`.send()`していると解釈できるのです。
ノートナンバーやベロシティは若干の音楽知識を求められるようにも見えますが、ノートナンバーは結局キーコードと同じようなもので、ベロシティは単なる強弱です。なので、ここまでで解説した話はほとんどがバイト列に関するものであるため、エンジニアにも親しみやすいですね。
# デバッグに便利なアプリケーション
[MIDIMonitor](https://www.snoize.com/MIDIMonitor/)というアプリケーションをお勧めします(macOS用)。macOSには最初からIAC Driverという仮想MIDIドライバが備わっているため、MIDI機器をひとつも所持していなくてもWeb MIDI APIを楽しむことができます。
---
それでは。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment