WebMidiLink
2.Specification
|
WebMidiLink is a MIDI message transport protocol for Web-based application. By this protocol, Web-based sequencer or synthesizers can co-operate crossing domains. This means the Host-app can use Web-synths as if they are the plugins by only specifying the URL.
WebMidiLink use the javascript window.postMessage() function.
Link Level
There are two level of specifications, Level 0 and Level 1 depends on the function. Level 0 is a very simple protocol just the Host App sends MIDI messages to synthesizer one-sidedly. In Level 1, Host App can read the sound configuration data from synthesizer or setup the synthesizer.
Link Level 0
MIDI messages are sent with the Javascript window.postMessage() function in string format. This function can transport data without cross domain limitation. Then the string format is simply
"midi,nn[,nn...]"
The "midi" is a ID string and following "nn"s are comma-separated MIDI message bytes represented in hexadecimal.
For example, Note-On event will be represented like this : "midi,90,c3,64"
The Web-Synth should be implemented at leaset support Note-On "9n,nn,nn", Note-Off "8n,nn,nn" and All-Sound-Off "bn,78,0". Or a instrument with a sequencer like a rhythm-machine may support MIDI sync messages.
The Host should post one MIDI message in one postMessage(). And should not use the MIDI running status abbreviation.
WebMidiLink は Webアプリ間でMIDIメッセージを送る方法を定めたものです。このプロトコルを使うとWebアプリのシーケンサーやシンセサイザーがドメインをまたいで連携動作できます。つまり、WebベースのホストアプリはWebベースのシンセサイザーをURLを指定するだけでプラグインのように使う事ができるようになります。
WebMidiLinkは、単一の文字列をドメイン制限なく送る事が可能なJavascriptの window.postMessage()関数を使用します。
リンクレベル
どこまでの機能をサポートするかによってレベル0とレベル1の2つのレベルを定義します。レベル0はホストアプリがシンセサイザーを演奏するためのMIDIデータを一方的に送りつけるだけの非常に単純な仕組みです。レベル1はこれに加えて、シンセサイザーの現在の設定(パッチ)をホストアプリ側で読み取ったり、ホストアプリからシンセサイザーを設定したりする機構を持ちます。
リンクレベル0
MIDIデータは文字列化され、Javascriptのwindow.postMessage()関数によってシンセサイザーに送られます。文字列のフォーマットは次の形式を取ります。
"midi,nn[,nn...]"
ここで "midi" は識別用の文字列で、その後ろに MIDIメッセージのバイトデータを16進文字列で表したものをコンマで繋いでいきます。
例えばノートオンのイベントならば "midi,90,c3,64" のようになります。
Webシンセサイザーは少なくとも Note-On "9n,nn,nn", Note-Off "8n,nn,nn", All-Sound-Off "bn,78,0" をサポートする必要があります。また、リズムマシンのようにインスツルメント内にシーケンスを保持している機器の場合は MIDI シンク関係のメッセージが必要になるかも知れません。
ホストアプリは、1つのpostMessage()の呼び出しに1つのMIDIメッセージを入れて送信します。また MIDIランニングステータスによる省略は使用できません。
Javascript fragment for WebMidiLink receiver
app.NoteOn(), app.NoteOff(), app.AllSoundOff() should be implemented.
* 2012/07/12 Fixed : The velocity will be 0 when 'a'-'f' chars are used as msg[3].
window.addEventListener("message", webMidiLinkRecv, false);
function webMidiLinkRecv(event) {
var msg = event.data.split(",");
switch (msg[0]) {
case "midi":
switch (parseInt(msg[1], 16) & 0xf0) {
case 0x80:
app.NoteOff(parseInt(msg[2], 16));
break;
case 0x90:
var velo = parseInt(msg[3], 16);
if (velo > 0)
app.NoteOn(parseInt(msg[2], 16), velo);
else
app.NoteOff(parseInt(msg[2], 16));
break;
case 0xb0:
if (parseInt(msg[2], 16) == 0x78) {
app.AllSoundOff();
}
break;
}
break;
}
}
Javascript fragment for WebMidiLink sender
function Synth() {
this.sy = null;
this.Load = function(url) {
this.sy = window.open(url, "sy1", "width=900,height=670,scrollbars=yes,resizable=yes");
}
this.NoteOn = function(note, velo) {
this.SendMessage(this.sy, "midi,90," + note.toString(16) + "," + velo.toString(16));
}
this.NoteOff = function(note) {
this.SendMessage(this.sy, "midi,80," + note.toString(16) + ",0");
}
this.AllSoundOff = function() {
this.SendMessage(this.sy, "midi,b0,78,0");
}
this.SendMessage = function(sy, s) {
if(this.sy)
this.sy.postMessage(s, "*");
}
}
var synth = new Synth();
Simple Sample Page
If you want to use WebMidiLink synth in your page, here is a simple sample
Sample page
<html>
<head>
<script type="text/javascript">
function Synth() {
this.sy = null;
this.Load = function(url) {
this.sy = window.open(url, "sy1", "width=900,height=670,scrollbars=yes,resizable=yes");
}
this.NoteOn = function(note, velo) {
this.SendMessage(this.sy, "midi,90," + note.toString(16) + "," + velo.toString(16));
}
this.NoteOff = function(note) {
this.SendMessage(this.sy, "midi,80," + note.toString(16) + ",0");
}
this.AllSoundOff = function() {
this.SendMessage(this.sy, "midi,b0,78,0");
}
this.SendMessage = function(sy, s) {
if(this.sy)
this.sy.postMessage(s, "*");
}
}
var synth = new Synth();
</script>
</head>
<body>
<button onclick="synth.Load('http://www.g200kg.com/en/docs/webmodular/webmodular.html')">Load Synth</button>
<hr/>
<button style="width:40px;height:100px" onmouseover="synth.NoteOn(60,64)" onmouseout="synth.NoteOff(60)">C </button>
<button style="width:40px;height:100px" onmouseover="synth.NoteOn(61,64)" onmouseout="synth.NoteOff(61)">Db</button>
<button style="width:40px;height:100px" onmouseover="synth.NoteOn(63,64)" onmouseout="synth.NoteOff(63)">Eb</button>
<button style="width:40px;height:100px" onmouseover="synth.NoteOn(67,64)" onmouseout="synth.NoteOff(67)">G </button>
<button style="width:40px;height:100px" onmouseover="synth.NoteOn(68,64)" onmouseout="synth.NoteOff(68)">Ab</button>
</body>
</html>
Link Level 1
LinkLevel 1 support sending a configuration data (sound patch) between Host App and synthesizers. The id-string is not 'midi' but use 'link'.
LinkLevel 1 communication use string format window.postMessage() function same as LinkLevel 0 but bi-directional.
Message | Direction | Description |
---|---|---|
"link,ready" | Host<=Synth | Synthesizer should send this message to host when ready after start up. The host may be window.opener or window.parent depends on popup or iframe. Host noticed the synthesizer is LinkLevel 1 with this message and enable following LinkLevel 1 messages. |
"link,reqpatch" | Host=>Synth | Host request the current configuration data to synthesizer. The synthesizer should send following "link,patch" message. |
"link,patch,<data>" | Host<=Synth | The synthesizer send current configuration data to host with this message when received the "link,reqpatch". <data> is a proprietary data of this synthesizer but should be represented as a string and should not use comma (,). For example, it may be a url-encoded query string (a=00&b=11&c=33), or a Base64 / Base64url encoded binary-data. |
"link,setpatch,<data>" | Host=>Synth | Host send configuration data to synthesizer with this message. This <data> is a data acquired with "link,patch,<data>". The synthesizer should set-up the sound patch with this message. |
リンクレベル1
リンクレベル1ではリンクレベル0のMIDIメッセージに加えて、シンセサイザーの設定データの通信を行います。先頭の識別文字列は"midi"ではなく"link"を使用します。
リンクレベル1のメッセージの送信はリンクレベル0と同じくJavascriptのwindow.postMessage()による文字列通信を使用しますが、ホストからシンセサイザーにメッセージを送るだけでなく、シンセサイザーからホストに対してのメッセージ送信が発生します。
メッセージ | 方向 | 説明 |
---|---|---|
"link,ready" | ホスト<=シンセ | シンセサイザーは起動後、動作の準備が整った時にこのメッセージをホストに対して送信します。この時のホスト側はシンセサイザー側から見ると、ポップアップ / IFrame のどちらで呼び出されたかによって、window.opener または window.parent となりますので、window.openerがあればwindow.opener、ない場合はwindow.parentに対して送出します。ホスト側はこのメッセージを受け取る事でシンセサイザーがリンクレベル1である事を知り、以下のメッセージを使用するようになります。 |
"link,reqpatch" | ホスト=>シンセ | ホストがシンセサイザーに対して現在の音色設定データを要求します。シンセサイザーはこのメッセージを受け取った場合、次の"link,patch"メッセージを送出します。 |
"link,patch,<data>" | ホスト<=シンセ | シンセサイザーは"link,reqpatch"メッセージを受け取った場合、このメッセージで現在の設定データをホストに送出します。この時のホストは"link,reqptch"メッセージを受け取った時のevent.sourceとなります。 <data>はそのシンセサイザーに固有のデータであり、データの形式は問われませんが、文字列化され、かつコンマ(,)を含む事はできません。一般的な方法としてはurlエンコードされたクエリ文字列のような形式(a=00&b=11&c=33)やバイナリデータをBase64あるいはBase64urlでエンコードしたものなどが考えられます。 |
"link,setpatch,<data>" | ホスト=>シンセ | ホストからシンセサイザーに対して設定データを送り込みます。この<data>は"link,patch,<data>"メッセージによって送出された各シンセサイザーに固有のフォーマットのデータです。シンセサイザーはこのメッセージを受け取るとそれに合わせて音色の設定を行います。 |
Javascript fragment for WebMidiLink LinkLevel 1 Receiver
window.addEventListener("message", webMidiLinkRecv, false);
function webMidiLinkRecv(event) {
var msg = event.data.split(",");
switch (msg[0]) {
case "link": //Level1 messages
switch (msg[1]) {
case "reqpatch":
event.source.postMessage("link,patch," + GetPatchString(),"*");
break;
case "setpatch":
SetPatchString(msg[2]);
break;
}
break;
case "midi": //Level0 messages
switch (parseInt(msg[1], 16) & 0xf0) {
case 0x80:
NoteOff(parseInt(msg[2], 16));
break;
case 0x90:
var velo = parseInt(msg[3], 16);
if (velo > 0)
NoteOn(parseInt(msg[2], 16), velo);
else
NoteOff(parseInt(msg[2], 16));
break;
case 0xb0:
if (parseInt(msg[2], 16) == 0x78) {
AllNoteOff();
}
break;
}
break;
}
}
// Functions should be implemented.
function NoteOn(note,velo) { }
function NoteOff(note) { }
function NoteAllOff() { }
function GetPatchString() { return strPatch; } // Get current patch data as a string.
function SetPatchString(strPatch) { } // Setup Synthesizer with strPatch
// Should be called when the Synthesizer is ready.
function LinkReady() {
if (window.opener)
window.opener.postMessage("link,ready", "*");
else
window.parent.postMessage("link,ready", "*");
}