Web MIDI API による Web アプリと DAW の連携


HTML5 Advent Calendar 2015 10日目の記事になります。

Web MIDI API は、MIDI (Musical Instrument Digital Interface) とよばれるプロトコルを用いて Web ブラウザと MIDI 機器との間で通信を行うための API です。

この Web MIDI API は、Web ブラウザからハードウェア MIDI 音源を鳴らしたり、MIDI コントローラで Web ブラウザをコントロールしたりといった用途で使われています。Web MIDI API は Web Audio API とセットで使われることも多いです。また、音楽関連の用途以外にも、Arduino などのデバイスと PC を連携させたりする場合に Web MIDI API が活用されることもあります（参考: [HTML5 Advent Calendar 2015 4日目](http://qiita.com/ryoyakawai/items/374cf35808bb118d6ca5)）。

一方、本記事では、MIDI 対応の機器ではなく DAW (Digital Audio Workstation) と Web MIDI API を用いた Web アプリを連携させる方法とそのサンプルアプリを紹介します。今まで、MIDI 機器を持っていなくて Web MIDI API を試すことが出来なかった方も、本記事の内容は DAW さえあれば試すことができます。
（DAW は Mac であれば GarageBand が標準でインストールされています。また、フリーウェアのソフトもたくさんあります）


## 準備

冒頭に書いた通り、Web MIDI API は Web ブラウザと MIDI 機器との間で通信を行う API です。そのため、ブラウザから DAW へ直接 MIDI メッセージを送信することはできません。そこで、「仮想 MIDI ポート」にブラウザと DAW の仲介役をしてもらうことでこの問題を解決します。

Mac では、標準で「IAC ドライバ」という仮想 MIDI ポートを作成するための機能が搭載されており、これを有効にすることで DAW との連携が可能になります。

![1](https://qiita-image-store.s3.amazonaws.com/0/19511/bdc18537-e8f2-ea45-9034-d329520a6c71.png)

一方、Windows には Mac でいう IAC ドライバにあたるものは標準搭載されていませんが、loopMIDI（フリーウェア）などをインストールすることで同様のことができます。

![2](https://qiita-image-store.s3.amazonaws.com/0/19511/3129d9d6-a096-6f12-4733-e6557f677a7f.png)

仮想 MIDI ポートの設定方法についてはプログラミングの話題から外れそうなので、本記事では省略します。
詳細を知りたい方は[こちら](https://github.com/tanikawa04/webmidiapi_to_daw_sample/blob/master/virtual_midi_port.md)をご覧ください。


## サンプルアプリ

仮想 MIDI ポートの設定が完了したら、まずは DAW 上でド（ノートナンバー60）の音を1秒間鳴らす Web アプリを作成してみます。

```html:sample1.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Web MIDI API Sample</title>
</head>
<body>
    <h1>Web MIDI API Sample</h1>
    <p>
        <label for="output_selector">MIDI Output Port: </label>
        <select name="output_selector" id="output_selector"></select>
    </p>
    <p>
        <button id="play_button">Play</button>
    </p>

    <script src="sample1.js"></script>
</body>
</html>
```

```js:sample1.js
'use strict';

const outputSelector = document.getElementById('output_selector');
const playButton = document.getElementById('play_button');

let outputs;

// MIDIデバイスへのアクセス要求
navigator.requestMIDIAccess()
    // アクセス許可（成功）
    .then(midiAccess => {
        // 使用可能なポートを output_selector に追加
        outputs = midiAccess.outputs;
        for (let output of outputs.values()) {
            const optionEl = document.createElement('option');
            optionEl.text = output.name;    // ポート名
            optionEl.value = output.id;     // ポート ID
            outputSelector.add(optionEl);
        }
    })
    // アクセス拒否（失敗）
    .catch(err => {
        console.log(err);
    });

playButton.addEventListener('click', () => {
    // output_selector で選択されているポートの ID を取得
    const index = outputSelector.selectedIndex;
    const portId = outputSelector[index].value;

    // 出力先の MIDI ポートを取得
    const output = outputs.get(portId);

    // MIDI メッセージを送信
    output.send([0x90, 60, 100]);                                       // ノートオン
    output.send([0x80, 60, 100], window.performance.now() + 1000);      // 1秒後にノートオフ
});
```

※ ES6 のスタイルで記述しています


### コードの解説

基本的な部分のみ簡潔に説明します。より詳しい内容は W3C のページなどをご参照ください。

- [Web MIDI API W3C Working Draft](http://www.w3.org/TR/webmidi/)
- [Web MIDI API (日本語訳)](http://g200kg.github.io/web-midi-api-ja/)

#### `navigator.requestMIDIAccess().then( ... ).catch( ... )`

`navigator.requestMIDIAccess()` は MIDI デバイスへアクセス要求を行うメソッドです。このメソッドは Promise オブジェクトを返却するので、 `then()` と `catch()` でチェインしてアクセス要求後の処理を記述します。

 `then()` のコールバック関数における引数（上記サンプルでは `midiAccess` ）は、MIDI 入力および出力ポートへのアクセスを得るためのオブジェクトです。`midiAccess.outputs` で使用可能な MIDI 出力ポートの情報を取得できます。サンプルには出てきませんが `midiAccess.inputs` で MIDI 入力ポートの情報を取得できます。


#### `for (let output of outputs.values()) { ... }`

`outputs.values()` で 出力先のポート情報をコレクションとして持つイテレータオブジェクトを取得することができます。for - of ループ（※ES6 の構文）によって、コレクションに格納されているポート情報を順に取得しています。

#### `output.send([0x90, 60, 100])`, `output.send([0x80, 60, 100], window.performance.now() + 1000)`

`output.send()` で MIDI ポートにメッセージを送信します。第1引数には MIDI メッセージを配列形式で渡します。第2引数は省略可能ですが、 `window.performance.now() + x` のような値を渡すと、x ミリ秒後に MIDI メッセージの内容が実行されます。省略した場合は即時実行されます。

MIDI メッセージについてはこちらが参考になります。
http://www.g200kg.com/jp/docs/tech/midi.html


### サンプルアプリの実行

コーディングが完了したら、お手持ちの DAW と Web MIDI API に対応している Web ブラウザを起動します。そして、ブラウザで作成したサンプルアプリのページにアクセスします。

"MIDI Output Port" の項目に、IAC ドライバや loopMIDI で設定した仮想 MIDI ポートの名前が表示されていれば成功です。MIDI ポートを選択した後、"Play" ボタンをクリックすると DAW に MIDI メッセージが送信されます。

![3](https://qiita-image-store.s3.amazonaws.com/0/19511/12655c68-7309-a925-c530-eb714d939aff.png)

※ Mac の GarageBand などでは、特に何も設定しなくても仮想 MIDI ポートを認識しますが、DAW によっては MIDI 入力として仮想 MIDI ポートを登録する必要があります。


### アプリの改良

動作確認ができたところで、プログラムを少し改良してみます。

上記のサンプルでは、click イベントのハンドラ内でノートオン、ノートオフのメッセージの両方を同時に登録していましたが、今度は MouseDown でノートオン、MouseUp でノートオフを登録するようにしてみます。ただし、これではボタン押下中にマウスカーソルがボタンの領域外にいってしまうと、MouseUp イベントが発火せず音が止まらくなってしまうので、（応急処置的ではありますが）MouseOut 時にもノートオフさせるようにします。

```js:sample2.js
'use strict';

const outputSelector = document.getElementById('output_selector');
const playButton = document.getElementById('play_button');

let outputs,
    output;

let playing = false;    // ボタン押下中かを判定するフラグ

const getSelectedOutput = () => {
    const index = outputSelector.selectedIndex;
    const portId = outputSelector[index].value;
    return outputs.get(portId);
};

const playC = () => {
    playing = true;
    output.send([0x90, 60, 100]);
};

const stopC = () => {
    if (playing) {
        playing = false;
        // ノートオフ (0x80) の代わりにベロシティ0のノートオンでも音を止めることが可能
        output.send([0x90, 60, 0]);
    }
};

navigator.requestMIDIAccess()
    .then(midiAccess => {
        outputs = midiAccess.outputs;
        for (let output of outputs.values()) {
            const optionEl = document.createElement('option');
            optionEl.text = output.name;
            optionEl.value = output.id;
            outputSelector.add(optionEl);
        }
        output = getSelectedOutput();
    })
    .catch(err => {
        console.log(err);
    });

// MIDI Output Port の項目が変更されたら output を更新
outputSelector.addEventListener('change', () => {
    output = getSelectedOutput();
});

playButton.addEventListener('mousedown', () => {
    playC();
});

playButton.addEventListener('mouseup', () => {
    stopC();
});

playButton.addEventListener('mouseout', () => {
    stopC();
});
```

これで、自由に音を長くしたり短くしたりできるようになりました。

![4](https://qiita-image-store.s3.amazonaws.com/0/19511/b9aac9e7-983a-8e90-dd23-eb14b42ff783.png)


## おわりに

本記事では、仮想 MIDI ポートを利用して、Web MIDI API を用いて作成した Web アプリと DAW を連携させる方法とサンプルアプリを紹介しました。

今回の例を少し発展させれば、MIDI キーボードアプリなどは比較的簡単に作ることができます。また、ここでの説明は割愛しますが、ちょっとした応用的なアプリの一例として、ハ長調のダイアトニックコード（主要な7つの和音）を鳴らすアプリを作ってみました（[アプリのページ](http://tanikawa04.github.io/webmidiapi_to_daw_sample/sample3.html)）。

（ここまでくると、さすがにマウスでの操作のしづらさが目立つので、各和音にショートカットキーを割り当てるともう少し実用的なアプリになりそうです）

![5](https://qiita-image-store.s3.amazonaws.com/0/19511/630c1f34-503d-ad74-824a-c070dc8157bf.png)


このように、MIDI 機器を用意せずとも Web MIDI API による Web アプリを活用する方法はあります。個人的には、書き慣れている JavaScript で作曲に活用するためのツールを開発できるのは嬉しいことです。


本記事サンプルのまとめページ
http://tanikawa04.github.io/webmidiapi_to_daw_sample/