この記事は、Web MIDI APIを題材にした連載の第一回目です。
Web MIDI APIはWebブラウザのAPIですが、Webブラウザの中で完結はしません。Webブラウザと外部のMIDIデバイスとの間でMIDIによる通信を行うためのAPIなのです。Webブラウザと外部デバイスとのやりとりでというとGamepad APIを想像される方もいらっしゃると思いますが、Gamepad APIはGame PadからWebブラウザへの一方通行の通信を行うのに対し、Web MIDI APIはWebブラウザと外部MIDIデバイスとの間で、双方向の通信が可能です。
昔ホームページで鳴っていたやつ
ところで、MIDIってご存じですか? MIDIと聞くと、「昔ホームページで鳴っていたやつ」 を思い出される方が多いと思います。そもそもMIDIとは、「電子楽器の演奏データを機器間でデジタル転送する」ための物理的な回路・インターフェイス、通信プロトコル、ファイルフォーマット等の複数の規格から成り立っている規格の総称です。そして、Web MIDIを語る上で重要になるのが、「MIDIプロトコル」と「物理的な回路・インターフェイス」というキーワードです。
冒頭の「昔ホームページで鳴っていたやつ」 ですが、あれはMIDIの中のファイルフォーマット(Standard MIDI File)を、OSが持つ音源を使って再生したモノとなります。「つまりどういうこと?」という方もいらっしゃると思いますが、この後、解説する、MIDIプロトコルの説明をご覧いただければ理解できるはずです。
まず最初は「MIDIプロトコル」から解説していきます。
MIDIプロトコル
MIDIとは先述した通り、「電子楽器の演奏データを機器間でデジタル転送する」ための規格でした。まず、演奏データ、つまり「音楽」とはどういうモノで成り立っているか、誰もが小学生で触ったであろうリコーダー(縦笛)を例に考えてみまます。
リコーダーは口にくわえて息を吹き込むことで音が出ます。強く吹けば力強い音が出るし、弱く吹けば優しい音が出る、つまり息の強さで 音の強弱が表現 できます。それから、押さえる穴の数を変えれば高い音階の音が鳴ったり、低い音階の音が鳴ったりする、つまり穴の数を変えることで 音程の変化 をさせているのです。これら 音の強弱が表現 、 音程の変化 の2つの情報があれば、音を再現できそうですよね。実はMIDIプロトコルで音を鳴らす部分は、この2つの情報を1つのメッセージとして扱うことで「機器間での演奏データの転送」を実現しています。さらに、MIDIには チャンネル というものがあり、これを使い分けることで16個の独立したメッセージを扱うことが可能になります。
それでは、実際にMIDIメッセージを見てみましょう。「音を出す」、「音を停止」する場合は、このように16進数でに3バイトを1つのMIDIメッセージとして指定します。
音を出す(noteOn)
音を止める(noteOff)
- チャンネル: 0〜15を16進数で指定
- 音階: 21(最も低いラ:27.5Hz)〜108(最も高いド:4186Hz)を16進数で指定
- 音の強さ: 0〜127を16進数で指定(音を停止の場合、機器によって解釈が違う場合もある)
MIDIメッセージの特徴の1つは、このように 「何を」 、 「どのくらい」、 「どうする」 の3つの内容が1組になっているところです。この2つのメッセージが分かれば音が出せるので、早速実装してみましょう。
Web MIDI APIを使って実装する
Web MIDI APIの仕様に中で、実装に必要な処理は以下の4つです。
- コンピュータに接続されているMIDI Input/Outputの機器を列挙
- 利用したい、MIDI機器を選択
- MIDI Input機器からの入力はEventで上がってくるので、EventListenerで処理を書く
- MIDI Output機器にMIDIメッセージを送信する
今回は単純に、MIDI Input機器からの出力をブラウザで中継して、MIDI Output機器に送信するプログラムを書いてみたいと思います。まずはライブデモを御覧ください。
「PC-Keyboard」と「GMPlayer(WebMIDILink)」は、MIDI機器がなくても動かせるように、PCのキーボードで鍵盤、また、MIDIメッセージで発音するWeb Audioで作った音源モジュールをPolymerで仮想のデバイスにしています。(「GMPlayer(WebMIDILink)」は@g200kgさんが開発した音源です)環境の整備
今回は、PolymerとPolymerのElement(x-webmidiというElementのExtraのElement)を使いますので、ダウンロードしてきます。
例えば、 hx_webmidi_01 ディレクトリを作成して、その下に bower_components ディレクトリを作成し、リンクからダウンロードしたファイルを保存してください。(x-webmidiは解凍するとファイル名にバージョン番号が付いてしまいますので削除してください)
そして、これからコードを追加していく index.html を作成します。
以上で環境整備は完了です。
Polymerのコードを実装
では、次に実装するコードを見てみましょう。今回作成する index.html のコード全体はこちらになります。早速、HTML部分を上から説明していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<script src="bower_components/webcomponentsjs/webcomponents.js"></script> <link rel="import" href="bower_components/polymer/polymer.html"> <link rel="import" href="bower_components/x-webmidi/extras/wm-webmidilink/wm-webmidilink.html"> <link rel="import" href="bower_components/x-webmidi/extras/wm-pckeyboard/wm-pckeyboard.html"> <wm-webmidilink id="wmlink"></wm-webmidilink> <wm-pckeyboard id="pckeyboard"></wm-pckeyboard> input: <select id="midi-input"><option>SelectOne</option></select><span id="virtual-input"></span><br><br> output: <select id="midi-output"><option>SelectOne</option></select><span id="virtual-output"></span><br> <br> <div id="msg" style="margin:30px;"></div> |
Web MIDI APIで列挙したMIDIデバイスを選択するためのSelectタグを書いておきます(9行目、10行目)。また、MIDI Inputデバイスから受けたMIDIメッセージを表示するエリアもつけています(12行目)。尚、今回は、MIDIデバイスがなくても楽しめるようにPolymerを使っています(1行目から7行目)が、ここでは深く触れません。Selectタグの直後の<span></span>も同様にPolymerに関連する記載のためここでは触れません。
MIDIデバイスの列挙
続いてWeb MIDI APIを使った実際の処理の説明です。
1 2 3 4 5 6 7 |
navigator.requestMIDIAccess({sysex:false}).then(successCallback, errorCallback); function successCallback(access) { (後ほど詳しく) } function errorCallback(msg) { console.log("[ERROR] ", msg); } |
requestMIDIAccessはPromiseの形になっていますので、まずは「システムエクスクルーシブ(sysex)」を使用するか・使用しないかを設定し、実行します。成功すればsuccessCallbackへ、失敗すればerrorCallbackへと処理が遷移します。ここで「システムエクスクルーシブ」とはMIDIメッセージの1つで、電子楽器機器固有の機能を制御するために使われます。電子楽器内のデータのバックアップで使うことも多いため、Web MIDI APIではセキュリティの観点から、ユーザの許可(ダイアログを表示)を得ることが利用する条件となっています。
Promiseが成功し、successCallbackへ遷移した場合の説明です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// MIDI Inputデバイスの配列を作成 var inputIterator = access.inputs.values(); for (var o = inputIterator.next(); !o.done; o = inputIterator.next()) { midi.inputs.push(o.value); } // 仮想のMIDI Inputデバイスを追加 midi.inputs.push((document.querySelector("#pckeyboard")).getInput()); // MIDI Outputデバイスの配列を作成 var outputIterator = access.outputs.values(); for (var o = outputIterator.next(); !o.done; o = outputIterator.next()) { midi.outputs.push(o.value); } // 仮想のMIDI Outputデバイスを追加 midi.outputs.push((document.querySelector("#wmlink")).getOutput()); |
Prmiseが成功すると、コンピュータに接続されているMIDIデバイスの情報を引数で渡されます。MIDI Inputデバイスの場合access.outputs.values() 、MIDI Output デバイスの場合はaccess.outputs.values()を実行することで、それぞれのデバイスのリストをIteratorの形で取得できます。ここでは機器情報の配列 midi.inputs、midi.outputsを作成します。最後に仮想のデバイスを取得して追加しています。
MIDI Outputデバイスのリスト表示と選択時の処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// MIDI Outputデバイスのリスト表示と選択時の処理 var osel=document.querySelector("#midi-output"); for(var i=0; i<midi.outputs.length; i++) { osel.appendChild((new Option(midi.outputs[i]["name"], i))); } osel.addEventListener("change", function(event){ outputSelIdx=event.target.value; // 仮想のMIDI Outputデバイスが選択された時の処理 if(parseInt(outputSelIdx)>=0 && midi.outputs[outputSelIdx].virtual==true) { document.querySelector("#virtual-output").appendChild(document.querySelector("#wmlink").getElement()); } else { document.querySelector("#virtual-output").innerHTML="" } }); |
先ほど作成したMIDI Inputデバイスの配列の要素を、リストとして表示するためにselectタグにoptionとして追加します。(2行目〜5行目) リストの選択の変化に対してEventListenerを設定します。(6行目~14行目)ここで行っているのは、選択された機器に割り振られたIDを記憶(7行目)、続いて、もし仮想デバイスの場合はリストの横にアイコンを追加(9行目~13行目)を行っています。
MIDI Inputデバイスのリスト表示と選択時の処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// MIDI Inputデバイスのリスト表示と選択時の処理 var isel=document.querySelector("#midi-input"); for(var i=0; i<midi.inputs.length; i++) { isel.appendChild(new Option(midi.inputs[i]["name"], i)); } isel.addEventListener("change", function(event){ if(inputSelIdx!=null) { midi.inputs[inputSelIdx].onmidimessage=null; } inputSelIdx=event.target.value; midi.inputs[event.target.value].onmidimessage=eventOut; // 仮想のMIDI Inputデバイスが選択された時の処理 if(parseInt(inputSelIdx)>=0 && midi.inputs[inputSelIdx].virtual==true) { document.querySelector("#virtual-input").appendChild(document.querySelector("#pckeyboard").getElement()); } else { document.querySelector("#virtual-input").innerHTML="" } }); |
先ほど作成したMIDI Inputデバイスの配列の要素をリストとして表示するために、selectタグにoptionとして追加します。(2行目~5行目)リストの選択の変化に対してEventListenerを設定します。(6行目〜18行目)ここで行っているのは、選択された機器に割り振られたIDを記憶(10行目)、MIDI Inputデバイスからの入力を受ける為の関数をonmidimessageに指定(7行目〜11行目)、もし既MIDI Inputデバイスからの入力を受ける機器が指定されていたらonmidimessageも解除(7行目〜9行目)、続いて、もし仮想デバイスの場合はリストの横にアイコンを追加(8行目~12行目)を行っています。
MIDI Inputデバイスからの入力を受けた時の処理
ここでは、MIDI Inputデバイスから取得したMIDIメッセージを、もしMIDI Outputデバイスが指定されていたら加工はせずにそのまま送信しますので、以下のようになります(7行目~9行目)。MIDI InputからのMIDIメッセージはevent.dataで取得することが可能で、MIDIメッセージは画面表示します(4行目、5行目)。
1 2 3 4 5 6 7 8 9 10 11 12 |
function eventOut(event) { // MIDIメッセージを表示 for(var i=0, out=[]; i<event.data.length; i++) out.push(("00"+event.data[i].toString(16)).substr(-2)); var div_msg=document.getElementById("msg"); var ex_msg=div_msg.innerHTML.split("<br>"); while(ex_msg.length>19) ex_msg.pop(); div_msg.innerHTML=out.join(" ")+"<br>" + ex_msg.join("<br>"); if(parseInt(outputSelIdx)>=0) { midi.outputs[outputSelIdx].send(event.data); } } |
以上で、接続されたMIDI Inputデバイスから、指定したMIDI Outputデバイスへメッセージを送信するWebアプリケーションが作成されました。PCのキーボードで演奏ができるようになったはずです。
もし電子ピアノ等の電子楽器をお手持ちの場合はUSB端子がついてないかご確認ください。そして、もしUSBで接続可能であれば接続し、作成したWebアプリをリロードしてみてください。inputもしくはoutputのどちらかに接続したデバイスがリストされるはずです。(ポケットミク、eVY1シールドも操作できます)
また、MIDIメッセージは単なるメッセージですので、音以外に割り当てたり、鍵盤に複数のMIDIメッセージを割り当ててフレーズを再生する等、アイデア次第で様々なモノのコントローラとして活用することができます。今回は、花火をモチーフに(季節外れですが…)こんなアプリケーションを作ってみました。鍵盤を弾くと、音とともに花火の打ち上がります。この音はもちろんMIDIメッセージで音源モジュールを鳴らしています。
おわりに
いかがでしたでしょうか?Web MIDIはそんなに難しくない、いやむしろ簡単であることをお分かりいただけたと思います。普段Webの開発をしていると「外部デバイスと接続して何かをする」という発想はなかったかもしれませんが、Web MIDIを使うとそういった実装もWebだけでできるようになります。「何かを作る」場合に選択の1つに是非Web MIDIを加えてみてください。
さて、今回は「MIDIとは何か?」というところから「Web MIDI APIの実装」、そして「MIDIを別の用途に応用してみる」というところを紹介してきました。しかし「難しくはないけど、少し準備が長い」と感じたりしていませんか?
実はWeb MIDIにはHotPlugの機能もありますが、今回の実装ではそこまで紹介しきれていないのです。そこで次回は、今回の記事にも少し登場した、Web MIDIと仮想デバイスの実装が一瞬でできてしまうx-webmidiというPolymerのElementについて、さらに具体的に紹介する予定です。
お楽しみに!