HTML的Web MIDI API:实现浏览器与MIDI设备的双向通信与实时交互
大家好,今天我们来深入探讨Web MIDI API,它允许我们的网页浏览器与MIDI设备进行双向通信,实现各种各样的实时交互应用。我们将从基础概念入手,逐步深入到代码实现,并探讨其在不同场景下的应用。
1. MIDI基础:理解音乐的数字化表达
在深入了解Web MIDI API之前,我们需要对MIDI(Musical Instrument Digital Interface)有一个基本的认识。MIDI是一种协议,它定义了电子乐器、电脑和其他相关设备之间如何通信。它并非传输音频,而是传输“事件”或“消息”,例如音符的按下和释放、音量变化、音色选择等。
MIDI消息通常由以下几个部分组成:
- Status Byte (状态字节): 指示消息的类型,例如音符开 (Note On)、音符关 (Note Off)、控制变更 (Control Change) 等。
- Data Byte 1 (数据字节1): 通常包含音符的键值 (Key Number) 或控制器的编号 (Controller Number)。
- Data Byte 2 (数据字节2): 通常包含音符的力度 (Velocity) 或控制器的值 (Controller Value)。
例如,一个 Note On 消息可能如下所示(以十六进制表示):
0x90 0x3C 0x7F
0x90:Note On消息,通道 0 (MIDI 通道从 0 开始计数)0x3C: 音符键值 60 (对应于中央 C)0x7F: 力度 127 (最大力度)
理解MIDI消息的结构对于理解Web MIDI API的工作方式至关重要。
2. Web MIDI API:浏览器与MIDI设备的桥梁
Web MIDI API提供了一组JavaScript接口,允许网页应用程序访问和控制连接到用户的计算机的MIDI设备。它允许我们:
- 枚举 MIDI 设备: 获取连接到计算机的输入和输出 MIDI 设备列表。
- 接收 MIDI 输入: 监听来自 MIDI 设备的消息,并根据这些消息执行相应的操作。
- 发送 MIDI 输出: 向 MIDI 设备发送消息,例如播放音符、更改音色等。
3. Web MIDI API 的核心接口
Web MIDI API 提供了几个关键的接口:
-
navigator.requestMIDIAccess(): 这是访问 Web MIDI API 的入口点。它返回一个 Promise,解析为MIDIAccess对象,该对象提供对 MIDI 设备的访问。navigator.requestMIDIAccess() .then(onMIDISuccess, onMIDIFailure); function onMIDISuccess(midiAccess) { console.log("MIDI Access Granted!"); // 处理 midiAccess 对象,例如列出设备 } function onMIDIFailure(msg) { console.log(`Failed to get MIDI access - ${msg}`); } -
MIDIAccess: 表示对 MIDI 系统的访问。它包含inputs和outputs属性,它们分别是MIDIInputMap和MIDIOutputMap对象,用于访问输入和输出设备。它也包含sysexEnabled属性,表示是否启用System Exclusive消息。 -
MIDIInput: 表示一个 MIDI 输入设备。它包含name、manufacturer和version等属性,用于描述设备。最重要的是,它有一个onmidimessage事件处理程序,用于监听来自设备的消息。function onMIDISuccess(midiAccess) { for (let input of midiAccess.inputs.values()) { input.onmidimessage = getMIDIMessage; console.log(`Input device found: ${input.name}`); } } function getMIDIMessage(midiMessage) { const command = midiMessage.data[0]; const note = midiMessage.data[1]; const velocity = (midiMessage.data.length > 2) ? midiMessage.data[2] : 0; console.log(`MIDI message received: Command: ${command}, Note: ${note}, Velocity: ${velocity}`); } -
MIDIOutput: 表示一个 MIDI 输出设备。它包含name、manufacturer和version等属性。它有一个send()方法,用于向设备发送 MIDI 消息。function onMIDISuccess(midiAccess) { for (let output of midiAccess.outputs.values()) { // Send a Note On message to play middle C with velocity 127 output.send([0x90, 0x3C, 0x7F]); // Channel 1, Middle C, Max Velocity // Send a Note Off message after 1 second setTimeout(() => { output.send([0x80, 0x3C, 0x00]); // Channel 1, Middle C, Velocity 0 }, 1000); } } -
MIDIMessageEvent: 包含来自 MIDI 输入设备的消息数据。data属性是一个Uint8Array,包含 MIDI 消息的字节。
4. 代码示例:简单的 MIDI 键盘监听器
下面是一个简单的示例,演示如何监听来自 MIDI 键盘的输入并将其打印到控制台:
<!DOCTYPE html>
<html>
<head>
<title>Web MIDI API Example</title>
</head>
<body>
<h1>Web MIDI API Example</h1>
<script>
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess()
.then(onMIDISuccess, onMIDIFailure);
} else {
console.log("Web MIDI API is not supported in this browser.");
}
function onMIDISuccess(midiAccess) {
console.log("MIDI Access Granted!");
for (let input of midiAccess.inputs.values()) {
input.onmidimessage = getMIDIMessage;
console.log(`Input device found: ${input.name}`);
}
midiAccess.onstatechange = function(e) {
console.log(e.port.name, e.port.manufacturer, e.port.state);
};
}
function onMIDIFailure(msg) {
console.log(`Failed to get MIDI access - ${msg}`);
}
function getMIDIMessage(midiMessage) {
const command = midiMessage.data[0];
const note = midiMessage.data[1];
const velocity = (midiMessage.data.length > 2) ? midiMessage.data[2] : 0;
console.log(`MIDI message received: Command: ${command}, Note: ${note}, Velocity: ${velocity}`);
}
</script>
</body>
</html>
这个示例首先检查浏览器是否支持 Web MIDI API。如果支持,它调用 navigator.requestMIDIAccess() 来请求访问 MIDI 设备。如果请求成功,onMIDISuccess 函数会被调用。这个函数遍历所有可用的 MIDI 输入设备,并为每个设备设置 onmidimessage 事件处理程序。getMIDIMessage 函数会被调用,当收到来自 MIDI 设备的消息时,它会将消息数据打印到控制台。
5. 代码示例:虚拟 MIDI 键盘发送音符
以下示例演示如何创建一个虚拟的 MIDI 键盘,并通过 Web MIDI API 发送音符到连接的 MIDI 输出设备。
<!DOCTYPE html>
<html>
<head>
<title>Virtual MIDI Keyboard</title>
<style>
.key {
display: inline-block;
width: 40px;
height: 100px;
border: 1px solid black;
text-align: center;
vertical-align: bottom;
cursor: pointer;
}
.key:active {
background-color: lightgray;
}
</style>
</head>
<body>
<h1>Virtual MIDI Keyboard</h1>
<div id="keyboard">
<div class="key" data-note="60">C4</div>
<div class="key" data-note="62">D4</div>
<div class="key" data-note="64">E4</div>
<div class="key" data-note="65">F4</div>
<div class="key" data-note="67">G4</div>
<div class="key" data-note="69">A4</div>
<div class="key" data-note="71">B4</div>
</div>
<script>
let midiOutput;
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess()
.then(onMIDISuccess, onMIDIFailure);
} else {
console.log("Web MIDI API is not supported in this browser.");
}
function onMIDISuccess(midiAccess) {
console.log("MIDI Access Granted!");
for (let output of midiAccess.outputs.values()) {
midiOutput = output; // Use the first output device found
console.log(`Output device found: ${output.name}`);
break; // Stop after finding the first output
}
if (!midiOutput) {
console.log("No MIDI output device found.");
return;
}
// Add event listeners to the keys
const keys = document.querySelectorAll('.key');
keys.forEach(key => {
key.addEventListener('mousedown', playNote);
key.addEventListener('mouseup', stopNote);
key.addEventListener('mouseout', stopNote); // Handle mouse leaving the key
});
}
function onMIDIFailure(msg) {
console.log(`Failed to get MIDI access - ${msg}`);
}
function playNote(event) {
const note = parseInt(event.target.dataset.note);
if (midiOutput) {
midiOutput.send([0x90, note, 0x7F]); // Note On, velocity 127
}
}
function stopNote(event) {
const note = parseInt(event.target.dataset.note);
if (midiOutput) {
midiOutput.send([0x80, note, 0x00]); // Note Off, velocity 0
}
}
</script>
</body>
</html>
这个示例创建了一个简单的HTML键盘。当用户点击一个键时,playNote 函数会被调用,它会向 MIDI 输出设备发送一个 Note On 消息。当用户释放键时,stopNote 函数会被调用,它会发送一个 Note Off 消息。
6. 处理 System Exclusive (SysEx) 消息
System Exclusive (SysEx) 消息是一种特殊的 MIDI 消息,允许设备制造商定义自己的特定于设备的命令。 它们通常用于固件更新、音色库传输和其他高级功能。
要使用 SysEx 消息,您需要在请求 MIDI 访问时启用 sysex 选项:
navigator.requestMIDIAccess({ sysex: true })
.then(onMIDISuccess, onMIDIFailure);
收到 SysEx 消息时,MIDIMessageEvent.data 将包含完整的 SysEx 消息,包括起始字节 (0xF0) 和结束字节 (0xF7)。
发送 SysEx 消息时,您需要将消息作为 Uint8Array 传递给 MIDIOutput.send() 方法:
const sysexMessage = new Uint8Array([0xF0, 0x43, 0x79, 0x09, 0x01, 0xF7]); // Example SysEx message
midiOutput.send(sysexMessage);
7. MIDI 通道
MIDI 消息可以发送到 16 个不同的通道。通道允许您控制多个乐器或音色,每个音色都在不同的通道上接收消息。 状态字节的高四位指示消息类型(例如,Note On、Note Off),而低四位指示通道号(0-15)。
例如,0x90 表示通道 1 上的 Note On 消息,而 0x9A 表示通道 11 上的 Note On 消息。
在上面的示例中,我们始终使用 0x90 和 0x80,这意味着所有消息都发送到通道 1。 要向其他通道发送消息,您需要更改状态字节。
8. 错误处理和兼容性
- 错误处理: 始终使用
try...catch块来处理可能发生的错误,例如 MIDI 设备断开连接或发送无效的 MIDI 消息。 - 兼容性: Web MIDI API 的支持程度因浏览器而异。 在使用它之前,最好检查一下浏览器是否支持它。 你可以使用
navigator.requestMIDIAccess来检查支持情况。 - 用户权限: 用户必须明确授予网页访问 MIDI 设备的权限。 浏览器通常会显示一个提示,要求用户允许或拒绝访问。
9. Web MIDI API 的应用场景
Web MIDI API 为各种创意和实用应用打开了大门:
- 在线音乐制作: 创建基于浏览器的 DAW(数字音频工作站),允许用户使用 MIDI 键盘或其他 MIDI 控制器来录制、编辑和混音音乐。
- 交互式音乐教育: 开发交互式音乐学习工具,例如虚拟钢琴课程或音乐游戏,使用 MIDI 输入来提供实时反馈。
- VJing 和实时音频可视化: 使用 MIDI 控制器来控制视频和音频效果,创建动态的实时表演。
- 硬件控制: 构建网页应用程序来控制 MIDI 兼容的硬件设备,例如灯光、投影仪或机器人。
- 可访问性: 为残疾人士创建辅助技术,例如使用 MIDI 设备来控制网页浏览器或操作系统。
10. 表格:常用 MIDI 消息类型
| 消息类型 | 状态字节范围 (十六进制) | 描述 |
|---|---|---|
| Note On | 0x90 – 0x9F | 启动一个音符的播放。Data Byte 1: 音符键值 (0-127),Data Byte 2: 力度 (0-127)。 力度为 0 通常被视为 Note Off 消息。 |
| Note Off | 0x80 – 0x8F | 停止一个音符的播放。Data Byte 1: 音符键值 (0-127),Data Byte 2: 力度 (0-127)。 |
| Control Change | 0xB0 – 0xBF | 控制器变更消息,用于调整音量、调制、声像等。Data Byte 1: 控制器编号 (0-127),Data Byte 2: 控制器值 (0-127)。 |
| Program Change | 0xC0 – 0xCF | 程序变更消息,用于选择乐器或音色。Data Byte 1: 程序编号 (0-127)。 |
| Pitch Bend | 0xE0 – 0xEF | 音高弯曲消息,用于平滑地调整音高。Data Byte 1: LSB (低字节) of pitch bend value,Data Byte 2: MSB (高字节) of pitch bend value。 弯曲值范围是 -8192 到 8191。 |
| System Exclusive (SysEx) | 0xF0 | 系统独占消息,用于设备制造商定义自己的特定于设备的命令。 消息以 0xF0 开始,以 0xF7 结束。 |
11. 总结:Web MIDI API的强大之处
Web MIDI API为Web应用提供了与MIDI硬件设备进行直接通信的能力,通过简洁的JavaScript接口,实现了浏览器与外部音乐设备的双向数据传输,为音乐创作、教育、表演以及硬件控制等领域带来了前所未有的可能性,极大地扩展了Web应用的交互性和实用性。