Musical Instrument Digital Interface (MIDI) protocol是乐器(instruments),控制器(controllers)和电脑(computers)之间的的通信协议
MIDI不直接发送音频信号,而是发送event messages,包含了musical notes,controller signals(volume,vibrato,panning,cues,clock signals)来设置tempo等。
Web MIDI API 将不会实现高级的概念,比如sequencing,不直接支持 MIDI文件(MIDI播放器可以使用Web MIDI API创建)
requestMIDIAccess()
The feature name for requestMIDIAccess() is midi
partial interface Navigator {
[SecureContext]
Promise <MIDIAccess> requestMIDIAccess(optional MIDIOptions options = {});
};
MIDIOptions Dictionary
dictionary MIDIOptions {
boolean sysex;
boolean software;
};
MIDIInputMap Interface
[SecureContext, Exposed=Window] interface MIDIInputMap {
readonly maplike <DOMString, MIDIInput>;
};
This type is used to represent all the currently available MIDI input ports.
// to tell how many entries there are:
var numberOfMIDIInputs = inputs.size;
// add each of the ports to a <select> box
inputs.forEach( function( port, key ) {
var opt = document.createElement("option");
opt.text = port.name;
document.getElementById("inputportselector").add(opt);
});
MIDIOutputMap Interface
[SecureContext, Exposed=Window] interface MIDIOutputMap {
readonly maplike <DOMString, MIDIOutput>;
};
This type is used to represent all the currently available MIDI output ports. This enables:
// to tell how many entries there are:
var numberOfMIDIOutputs = outputs.size;
// add each of the ports to a <select> box
outputs.forEach( function( port, key ) {
var opt = document.createElement("option");
opt.text = port.name;
document.getElementById("outputportselector").add(opt);
});
MIDIAccess Interface
provides the methods to list MIDI input and output devices, and obtain access to an individual device
[SecureContext, Exposed=Window] interface MIDIAccess: EventTarget {
readonly attribute MIDIInputMap inputs;
readonly attribute MIDIOutputMap outputs;
attribute EventHandler onstatechange;
readonly attribute boolean sysexEnabled;
};
属性 | 描述 |
---|---|
inputs | MIDI输入 |
outputs | MIDI输出 |
onstatechange | 新的端口连接,或者已有的端口变化 |
sysexEnabled |
MIDIPort Interface
This interface represents a MIDI input or output port.
[SecureContext, Exposed=Window] interface MIDIPort: EventTarget {
readonly attribute DOMString id;
readonly attribute DOMString? manufacturer;
readonly attribute DOMString? name;
readonly attribute MIDIPortType type;
readonly attribute DOMString? version;
readonly attribute MIDIPortDeviceState state;
readonly attribute MIDIPortConnectionState connection;
attribute EventHandler onstatechange;
Promise <MIDIPort> open();
Promise <MIDIPort> close();
};
属性 | 描述 |
---|---|
id | A unique ID of the port |
manufacturer | 端口的生产商 |
name | 端口的名称 |
type | 输入(input)或输出(output)的标志 |
version | |
state | disconnected 或 connected |
connection | open 或 close 或 pending |
onstatechange | |
open | 打开端口 |
close | 关闭端口 |
MIDIInput Interface
[SecureContext, Exposed=Window] interface MIDIInput: MIDIPort {
attribute EventHandler onmidimessage;
};
MIDIOutput Interface
[SecureContext, Exposed=Window] interface MIDIOutput : MIDIPort {
void send(sequence<octet> data, optional DOMHighResTimeStamp timestamp = 0);
void clear();
};
MIDIMessageEvent Interface
[SecureContext, Exposed=Window]
interface MIDIMessageEvent : Event {
constructor(DOMString type, optional MIDIMessageEventInit eventInitDict = {});
readonly attribute Uint8Array data;
};
例子
获得midi接口
var midi = null; // global MIDIAccess object
function onMIDISuccess( midiAccess ) {//如果连接midi设备成功
console.log( "MIDI ready!" );
midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)
}
function onMIDIFailure(msg) {//如果连接midi设备失败
console.log( "Failed to get MIDI access - " + msg );
}
navigator.requestMIDIAccess().then( onMIDISuccess, onMIDIFailure );
获得midi的输入和输出端口
function listInputsAndOutputs( midiAccess ) {
for (var entry of midiAccess.inputs) {
var input = entry[1];
console.log( "Input port [type:'" + input.type + "'] id:'" + input.id +
"' manufacturer:'" + input.manufacturer + "' name:'" + input.name +
"' version:'" + input.version + "'" );
}
for (var entry of midiAccess.outputs) {
var output = entry[1];
console.log( "Output port [type:'" + output.type + "'] id:'" + output.id +
"' manufacturer:'" + output.manufacturer + "' name:'" + output.name +
"' version:'" + output.version + "'" );
}
}
操作MIDI的输入
function onMIDIMessage( event ) {
var str = "MIDI message received at timestamp " + event.timestamp + "[" + event.data.length + " bytes]: ";
for (var i=0; i<event.data.length; i++) {
str += "0x" + event.data[i].toString(16) + " ";
}
console.log( str );
}
function startLoggingMIDIInput( midiAccess, indexOfPort ) {
midiAccess.inputs.forEach( function(entry) {entry.onmidimessage = onMIDIMessage;});
}
操作MIDI的输出
function sendMiddleC( midiAccess, portID ) {
var noteOnMessage = [0x90, 60, 0x7f]; // note on, middle C, full velocity
var output = midiAccess.outputs.get(portID);
output.send( noteOnMessage ); //omitting the timestamp means send immediately.
output.send( [0x80, 60, 0x40], window.performance.now() + 1000.0 ); // Inlined array creation- note off, middle C,
// release velocity = 64, timestamp = now + 1000ms.
}
var context=null; // the Web Audio "context" object
var midiAccess=null; // the MIDIAccess object.
var oscillator=null; // the single oscillator
var envelope=null; // the envelope for the single oscillator
var attack=0.05; // attack speed
var release=0.05; // release speed
var portamento=0.05; // portamento/glide speed
var activeNotes = []; // the stack of actively-pressed keys
window.addEventListener('load', function() {
// patch up prefixes
window.AudioContext=window.AudioContext||window.webkitAudioContext;
context = new AudioContext();
if (navigator.requestMIDIAccess)
navigator.requestMIDIAccess().then( onMIDIInit, onMIDIReject );
else
alert("No MIDI support present in your browser. You're gonna have a bad time.")
// set up the basic oscillator chain, muted to begin with.
oscillator = context.createOscillator();
oscillator.frequency.setValueAtTime(110, 0);
envelope = context.createGain();
oscillator.connect(envelope);
envelope.connect(context.destination);
envelope.gain.value = 0.0; // Mute the sound
oscillator.start(0); // Go ahead and start up the oscillator
} );
function onMIDIInit(midi) {
midiAccess = midi;
var haveAtLeastOneDevice=false;
var inputs=midiAccess.inputs.values();
for ( var input = inputs.next(); input && !input.done; input = inputs.next()) {
input.value.onmidimessage = MIDIMessageEventHandler;
haveAtLeastOneDevice = true;
}
if (!haveAtLeastOneDevice)
alert("No MIDI input devices present. You're gonna have a bad time.");
}
function onMIDIReject(err) {
alert("The MIDI system failed to start. You're gonna have a bad time.");
}
function MIDIMessageEventHandler(event) {
// Mask off the lower nibble (MIDI channel, which we don't care about)
switch (event.data[0] & 0xf0) {
case 0x90:
if (event.data[2]!=0) { // if velocity != 0, this is a note-on message
noteOn(event.data[1]);
return;
}
// if velocity == 0, fall thru: it's a note-off. MIDI's weird, y'all.
case 0x80:
noteOff(event.data[1]);
return;
}
}
function frequencyFromNoteNumber( note ) {
return 440 * Math.pow(2,(note-69)/12);
}
function noteOn(noteNumber) {
activeNotes.push( noteNumber );
oscillator.frequency.cancelScheduledValues(0);
oscillator.frequency.setTargetAtTime( frequencyFromNoteNumber(noteNumber), 0, portamento );
envelope.gain.cancelScheduledValues(0);
envelope.gain.setTargetAtTime(1.0, 0, attack);
}
function noteOff(noteNumber) {
var position = activeNotes.indexOf(noteNumber);
if (position!=-1) {
activeNotes.splice(position,1);
}
if (activeNotes.length==0) { // shut off the envelope
envelope.gain.cancelScheduledValues(0);
envelope.gain.setTargetAtTime(0.0, 0, release );
} else {
oscillator.frequency.cancelScheduledValues(0);
oscillator.frequency.setTargetAtTime( frequencyFromNoteNumber(activeNotes[activeNotes.length-1]), 0, portamento );
}
}
参考:
https://webaudio.github.io/web-midi-api/#midiaccess-interface