MIDIメッセージを受信するためには, MIDIInputインスタンスのonmidimessageイベントハンドラを設定して,
そのイベントオブジェクト (MIDIMessageEventインスタンス) にアクセスすることが必要です.
サンプルコード 03
/**
* @param {MIDIAccess} midiAccess
*/
var successCallback = function(midiAccess) {
/** @type {Array.<MIDIInput>} */
var inputs = [];
/** @type {Array.<MIDIOutput>} */
var outputs = [];
if (Object.prototype.toString.call(midiAccess) === '[object Function]') {
// Legacy Chrome
inputs = midiAccess.inputs();
outputs = midiAccess.outputs();
} else {
// Chrome 39 and later
var inputIterator = midiAccess.inputs.values();
var outputIterator = midiAccess.outputs.values();
for (var i = inputIterator.next(); !i.done; i = inputIterator.next()) {
inputs.push(i.value);
}
for (var o = outputIterator.next(); !o.done; o = outputIterator.next()) {
outputs.push(o.value);
}
}
if (inputs.length > 0) {
/**
* @param {MIDIMessageEvent} event
*/
inputs[0].onmidimessage = function(event) {
// do something ....
};
}
};
/**
* @param {DOMException} error
*/
var errorCallback = function(error) {
// do something ....
};
navigator.requestMIDIAccess({sysex : true}).then(successCallback, errorCallback);
MIDIMessageEventインスタンスには, dataプロパティとreceivedTimeプロパティが定義されています.
receivedTimeプロパティはイベントが発生したときのタイムスタンプを格納しています.
イベントオブジェクトに定義されているtimeStampプロパティよりも精度が高いので,
タイムスタンプを利用する場合には, receivedTimeプロパティを利用するようにしましょう.
そして, 受信したMIDIメッセージはこのdataプロパティに格納されています.
dataプロパティはUint8Array型の配列です.
MIDIメッセージの種類は多く, そのすべてを解説するのは難しいので,
このセクションでは, 最低限の解説として, 音を発生させる, 音を停止するためのMIDIメッセージの処理について解説します.
音を発生させる (ノートオン), 音を停止する (ノートオフ) 場合,
dataプロパティは要素数が3のUint8Array型の配列となっています.
表4 - 2 - a. ノートオンの場合のdataプロパティ
Index | Description |
0 | 9n hex (0xf0とのマスク値) |
1 | ノートナンバー (0 〜 127) |
2 | ベロシティ (0 〜 127) |
表4 - 2 - b. ノートオフの場合のdataプロパティ
Index | Description |
0 | 8n hex (0xf0とのマスク値) |
1 | ノートナンバー (0 〜 127) |
2 | ベロシティ (0 〜 127) |
第1バイト (インデックス0) を除けば, ノートオンもノートオフも同じ意味のメッセージが格納されています.
ノートナンバーとは, 簡単に表現すればどの鍵盤が押されたのか, あるいは, 離されたのかを表す値で, 0から127までの値をとります.
値が大きいほど, ピッチが高くなります. ちなみに, ピアノ88鍵に対応するノートナンバーの範囲は, 21 〜 108です.
ベロシティとは, 簡単に表現すれば鍵盤を弾くときの強弱を表す値で, 0から127までの値をとります.
値が大きいほど, 音が強くなります.
ちなみに, ほとんどのMIDIメッセージは最大で3バイトです (例外は, システムエクスクルーシブメッセージです).
表4 - 2 - c. MIDIメッセージの例
1st byte | 2nd byte | 3rd byte |
8n hex (ノートオフ) | ノートナンバー | ベロシティ |
9n hex (ノートオン) | ノートナンバー | ベロシティ |
An hex (ポリフォニック キープレッシャー) | ノートナンバー | プレッシャー値 |
Bn hex (コントロールチェンジ) | コントロールナンバー | コントロール値 |
Cn hex (プログラムチェンジ) | プログラムナンバー | 使用しない |
Dn hex (チャンネルプレッシャー) | プレッシャー値 | 使用しない |
En hex (ピッチベンド) | ピッチベンド値MSB | ピッチベンド値LSB |
F0 hex (システムエクスクルーシブ開始) | F7 hexを受信すると終了する |
F7 hex (システムエクスクルーシブの終了) | 第2, 第3バイトをもたない |
F8 hex (MIDIクロック) | 使用しない | 使用しない |
音を発生させる, 音を停止する場合の実際のコードは以下のようになるでしょう.
サンプルコード 04
/**
* @param {MIDIAccess} midiAccess
*/
var successCallback = function(midiAccess) {
/** @type {Array.<MIDIInput>} */
var inputs = [];
/** @type {Array.<MIDIOutput>} */
var outputs = [];
if (Object.prototype.toString.call(midiAccess) === '[object Function]') {
// Legacy Chrome
inputs = midiAccess.inputs();
outputs = midiAccess.outputs();
} else {
// Chrome 39 and later
var inputIterator = midiAccess.inputs.values();
var outputIterator = midiAccess.outputs.values();
for (var i = inputIterator.next(); !i.done; i = inputIterator.next()) {
inputs.push(i.value);
}
for (var o = outputIterator.next(); !o.done; o = outputIterator.next()) {
outputs.push(o.value);
}
}
if (inputs.length > 0) {
/**
* @param {MIDIMessageEvent} event
*/
inputs[0].onmidimessage = function(event) {
switch (event.data[0] & 0xf0) {
case 0x90 :
noteOn(event.data[1], event.data[2]);
break;
case 0x80 :
noteOff(event.data[1], event.data[3]);
break;
default :
break;
}
};
}
};
/**
* @param {DOMException} error
*/
var errorCallback = function(error) {
// do something ....
};
/**
* @param {number} noteNumber
* @param {number} velocity
*/
var noteOn = function(noteNumber, velocity) {
// Start sound by Web Audio API
};
/**
* @param {number} noteNumber
* @param {number} velocity
*/
var noteOff = function(noteNumber, velocity) {
// Stop sound by Web Audio API
};
navigator.requestMIDIAccess({sysex : true}).then(successCallback, errorCallback);
noteOn / noteOff関数の処理は,
Web MIDI APIではなく, Web Audio APIの領域です.
引数に, ノートナンバーとベロシティを受け取っているので,
OscillatorNodeのfrequencyプロパティやGainNodeのgainプロパティを設定することができるでしょう.
また, それ以外にも, ベロシティに応じたオートワウなどのエフェクターの実装も考えられます.
サンプルコード 05
window.AudioContext = window.AudioContext || window.webkitAudioContext;
// Create the instance of AudioContext
var context = new AudioContext();
// for the instance of OscillatorNode
var oscillator = null
// for legacy browsers
context.createGain = context.createGain || context.createGainNode;
// Create the instance of GainNode
var gain = context.createGain();
var isStop = true;
/**
* @param {MIDIAccess} midiAccess
*/
var successCallback = function(midiAccess) {
/** @type {Array.<MIDIInput>} */
var inputs = [];
/** @type {Array.<MIDIOutput>} */
var outputs = [];
if (Object.prototype.toString.call(midiAccess) === '[object Function]') {
// Legacy Chrome
inputs = midiAccess.inputs();
outputs = midiAccess.outputs();
} else {
// Chrome 39 and later
var inputIterator = midiAccess.inputs.values();
var outputIterator = midiAccess.outputs.values();
for (var i = inputIterator.next(); !i.done; i = inputIterator.next()) {
inputs.push(i.value);
}
for (var o = outputIterator.next(); !o.done; o = outputIterator.next()) {
outputs.push(o.value);
}
}
if (inputs.length > 0) {
/**
* @param {MIDIMessageEvent} event
*/
inputs[0].onmidimessage = function(event) {
switch (event.data[0] & 0xf0) {
case 0x90 :
noteOn(event.data[1], event.data[2]);
break;
case 0x80 :
noteOff(event.data[1], event.data[3]);
break;
default :
break;
}
};
}
};
/**
* @param {DOMException} error
*/
var errorCallback = function(error) {
// do something ....
};
/**
* @param {number} noteNumber
* @param {number} velocity
*/
var noteOn = function(noteNumber, velocity) {
if (!isStop) {
oscillator.stop(0);
}
var FREQUENCY_RATIO = Math.pow(2, (1 / 12)); // about 1.059463
var MIN_A = 27.5;
var NOTE_NUMBER_OFFSET = 21;
var MAX_VELOCITY = 127;
// Create the instance of OscillatorNode
oscillator = context.createOscillator();
// for legacy browsers
oscillator.start = oscillator.start || oscillator.noteOn;
oscillator.stop = oscillator.stop || oscillator.noteOff;
// Set parameters
oscillator.frequency.value = MIN_A * Math.pow(FREQUENCY_RATIO, (noteNumber - NOTE_NUMBER_OFFSET));
gain.gain.value = velocity / MAX_VELOCITY;
// OscillatorNode (Input) -> GainNode (Volume) -> AudioDestinationNode (Output)
oscillator.connect(gain);
gain.connect(context.destination);
// Start sound
oscillator.start(0);
isStop = false;
};
/**
* @param {number} noteNumber
* @param {number} velocity
*/
var noteOff = function(noteNumber, velocity) {
if (isStop) {
return;
}
oscillator.stop(0);
isStop = true;
};
navigator.requestMIDIAccess({sysex : true}).then(successCallback, errorCallback);
デモ 04