MIDI SysEx 协议实战
System Exclusive(SysEx)是 MIDI 协议中最强大的扩展机制。与固定的通道消息不同,SysEx 允许制造商定义任意长度和格式的自定义消息——从简单的设备查询到复杂的固件升级,一切皆有可能。本文从实战角度出发,结合 LdA MS-3 协议,讲解 SysEx 的核心概念和实际应用。
1. SysEx 消息基础
每条 SysEx 消息以 0xF0 开始、0xF7 结束。中间包含制造商标识(1 或 3 字节)和自定义数据。制造商标识由 MMA 或 AMEI 分配——知名品牌如 Roland (0x41)、Yamaha (0x43)、Korg (0x42) 各有其 ID。LdA 目前使用临时 ID 0x7D(需向 MMA 申请正式 ID)。
┌──────┬──────────┬──────────┬────────────┬────────┬──────┐ │ 0xF0 │ Maker ID │ Data │ ... │ 0xF7 │ │ Start│ 1-3 bytes│ Variable │ │ End │ └──────┴──────────┴──────────┴────────────┴────────┘ 1-Byte IDs: 0x00-0x7F (e.g. Roland = 0x41) 3-Byte IDs: 0x00 0x00 0x00 format (0x00 0x20 0x00+)
SysEx 消息中只能包含系统实时消息(如 Timing Clock 0xF8)。其他任何消息类型都会中断 SysEx 传输。此外,所有数据字节的最高位必须为 0(即值 < 0x80)。
2. 通用 SysEx 消息
MMA 定义了通用 SysEx 消息,使用特殊 ID 0x7E(非实时)和 0x7F(实时),所有设备都应当响应。最常用的通用 SysEx 是 Identity Request/Reply——这是检测 MIDI 设备身份的标准化方法。
身份查询 (Identity Request)
F0 7E [Channel] 06 01 F7
Channel: 0x7F = 广播(所有设备响应),0x00-0x0F = 指定通道
身份回复 (Identity Reply)
F0 7E [Channel] 06 02 [MakerID] [Family] [Model] [Version] F7 Maker ID: 1-byte = 0x7D (LdA) Device Family: 2 bytes (MSB, LSB) Device Model: 2 bytes Version: 4 bytes (Major.Minor.Patch.Build)
3. LdA SysEx 协议详解
LdA 设备使用统一的 SysEx 协议格式,在标准 SysEx 基础上增加了设备 ID、命令字节和 XOR 校验和,以确保通信可靠性和数据完整性。
LdA 消息格式
F0 7D [DevID] [Command] [Data...] [Checksum] F7 Maker ID: 0x7D (LdA temporary) Dev ID: 0x01=MS-3, 0x02=LS-4p3, 0xFF=broadcast Command: See command table below Data: 0-N bytes, command-specific Checksum: XOR of Maker ID ⊕ DevID ⊕ Command ⊕ Data[0..N-1]
响应格式
所有命令的响应使用 ResponseCode = Command | 0x80(命令字节最高位置 1)。错误响应统一使用 ResponseCode = 0x8F。
命令集
| 范围 | 类别 | 重点命令 |
|---|---|---|
| 0x00-0x0F | 系统 | 0x00=查询固件, 0x01=设备信息, 0x03=状态, 0x04=Ping |
| 0x10-0x1F | 预设管理 | 0x10=读预设, 0x11=写预设, 0x13=切换预设, 0x14=全备份, 0x16=恢复出厂 |
| 0x20-0x2F | MIDI控制 | 0x20=发送MIDI, 0x21=配置预设MIDI |
| 0x30-0x3F | Loop控制 | 0x30=读Loop, 0x31=切换单个, 0x32=批量设置 |
| 0x40-0x4F | 系统配置 | 0x40=读配置, 0x42=读MIDI通道 |
| 0xF0-0xFF | 固件升级 | 0xF0=进Bootloader, 0xF3=传数据块 |
4. 校验和与错误处理
LdA 协议使用简单的 XOR 校验和来检测传输错误。计算方式:对 Maker ID (0x7D)、Dev ID、Command 和所有 Data 字节按位异或。接收方验证校验和,不匹配时返回 ErrorCode 0x03。
// Checksum calculation in C
uint8_t calc_checksum(uint8_t maker, uint8_t dev,
uint8_t cmd, uint8_t *data,
size_t len) {
uint8_t cs = maker ^ dev ^ cmd;
for (size_t i = 0; i < len; i++) cs ^= data[i];
return cs;
}
// Example: Query Firmware (cmd=0x00, no data)
// cs = 0x7D ^ 0x01 ^ 0x00 = 0x7C
// Message: F0 7D 01 00 7C F7| 错误码 | 含义 | 处理建议 |
|---|---|---|
| 0x01 | 命令不支持 | 检查命令字节 |
| 0x02 | 数据格式错误 | 检查数据字节范围(0-0x7F) |
| 0x03 | 校验和不匹配 | 重新计算校验和,重试 |
| 0x04 | 设备忙碌 | 等待100ms后重试 |
| 0x05 | 存储区错误 | 检查存储/恢复出厂设置 |
5. 实战示例
示例 1: 查询 MS-3 固件版本
Send: F0 7D 01 00 7C F7
(checksum: 0x7D^0x01^0x00 = 0x7C)
Receive: F0 7D 01 80 01 02 03 00 7F F7
(ResponseCode=0x80, v1.2.3.0, cs verified)示例 2: 切换预设至预设 5
Send: F0 7D 01 13 05 64 F7
(cmd=0x13, PresetNum=5, cs=0x7D^1^0x13^5=0x64)
Receive: F0 7D 01 93 05 6F F7
(ResponseCode=0x93, PresetNum=5)示例 3: 打开 Loop 1,关闭 Loop 2、3
Send: F0 7D 01 32 7F 00 00 4D F7
(cmd=0x32, L1=0x7F(ON), L2=0x00, L3=0x00,
cs=0x7D^1^0x32^0x7F^0^0=0x4D)6. Web MIDI API 集成
以下展示在浏览器中使用 Web MIDI API 与 LdA 设备通信的完整示例。注意:Web MIDI API 需要 HTTPS 或 localhost,且用户必须主动授权(特别是 SysEx 访问)。
// 1. Request MIDI access with SysEx
const midi = await navigator.requestMIDIAccess({ sysex: true });
// 2. Find LdA device by name
function findLdADevice(midi) {
const devices = { input: null, output: null };
for (const [id, input] of midi.inputs) {
if (input.name?.includes('MS-3') || input.name?.includes('LdA')) {
devices.input = input;
break;
}
}
for (const [id, output] of midi.outputs) {
if (output.name?.includes('MS-3') || output.name?.includes('LdA')) {
devices.output = output;
break;
}
}
return devices;
}
// 3. Send LdA SysEx command
function sendLdACmd(output, devId, cmd, data = []) {
const checksum = [0x7D, devId, cmd, ...data]
.reduce((a, b) => a ^ b, 0);
const msg = new Uint8Array([
0xF0, 0x7D, devId, cmd, ...data, checksum, 0xF7
]);
output.send(msg);
}
// 4. Listen for responses
function setupListener(input) {
let sysexBuffer = [];
input.onmidimessage = (event) => {
const data = Array.from(event.data);
if (data[0] === 0xF0) {
sysexBuffer = data;
} else if (sysexBuffer.length && data[data.length-1] !== 0xF7) {
sysexBuffer.push(...data);
}
if (data[data.length-1] === 0xF7) {
const full = sysexBuffer.length ? [...sysexBuffer, ...data] : data;
const makerId = full[1];
const devId = full[2];
const responseCode = full[3];
const cmd = responseCode & 0x7F;
const payload = full.slice(4, -2);
const checksum = full[full.length - 2];
// Verify checksum
const computed = full.slice(1, -2)
.reduce((a, b) => a ^ b, 0);
const valid = computed === checksum;
console.log('LdA Response:', {
cmd: '0x' + cmd.toString(16),
payload,
checksumValid: valid,
isError: cmd === 0x0F
});
sysexBuffer = [];
}
};
}
// 5. Usage: Query firmware
const { input, output } = findLdADevice(midi);
setupListener(input);
sendLdACmd(output, 0x01, 0x00); // DevID=MS-3, Cmd=Query Firmware7. 最佳实践与陷阱
MIDI 传输可能出错(特别是通过 USB-MIDI 转换器)。始终在接收端验证校验和,不匹配时请求重发。
设备处理 SysEx 命令可能需要时间(特别是写入操作)。收到 0x04 错误时,等待 100-500ms 后重试。批量操作间加入延迟。
预设备份或固件升级等大数据传输应分块进行(如每块 128 字节)。每块发送后等待设备响应,再发送下一块。
SysEx 消息中所有数据字节必须 < 0x80。如果需要传输 8-bit 数据,需要拆分为 7-bit 格式(每 7 个字节编码为 8 个 SysEx 字节)。
Web MIDI API 中,SysEx 访问需要显式授权(`{ sysex: true }`)。部分浏览器可能阻止 SysEx 或要求用户交互。始终检查 `midi.access` 权限状态。
8. 总结
SysEx 协议为 MIDI 设备提供了超越标准消息的深度控制能力。LdA 协议通过规范的命令格式、XOR 校验和以及完善的错误码体系,构建了一套可靠且可扩展的设备通信方案。掌握 SysEx 意味着你可以编写自己的控制软件、实现自动化工作流、甚至扩展设备的功能边界。
📚 延伸阅读: MIDI 技术参考文档 — 完整的协议规范和技术细节