MIDI SysEx Protocol in Practice
System Exclusive (SysEx) is MIDI's most powerful extension mechanism. Unlike fixed-format channel messages, SysEx allows manufacturers to define custom messages of arbitrary length and format — from simple device queries to complex firmware updates, anything is possible. This article takes a practical approach, using the LdA MS-3 protocol to explain SysEx core concepts and real-world applications.
1. SysEx Message Basics
Every SysEx message starts with 0xF0 and ends with 0xF7. In between is a manufacturer ID (1 or 3 bytes) and custom data. Manufacturer IDs are assigned by MMA or AMEI — well-known brands like Roland (0x41), Yamaha (0x43), Korg (0x42) each have their ID. LdA currently uses temporary ID 0x7D (pending formal MMA application).
┌──────┬──────────┬──────────┬────────────┬────────┬──────┐ │ 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+)
Only System Real-Time messages (e.g., Timing Clock 0xF8) can be interleaved within a SysEx message. Any other message type will terminate the SysEx transfer. Additionally, all data bytes must have MSB = 0 (i.e., value < 0x80).
2. Universal SysEx Messages
The MMA defines Universal SysEx messages using special IDs 0x7E (Non-Real Time) and 0x7F (Real Time) that all devices should respond to. The most commonly used Universal SysEx is the Identity Request/Reply — the standardized way to discover MIDI device identity.
Identity Request
F0 7E [Channel] 06 01 F7
Channel: 0x7F = broadcast (all devices respond), 0x00-0x0F = specific channel
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 Protocol In Detail
LdA devices use a unified SysEx protocol format that extends standard SysEx with device ID, command byte, and XOR checksum for communication reliability and data integrity.
LdA Message Format
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]
Response Format
All command responses use ResponseCode = Command | 0x80 (MSB set to 1). Error responses uniformly use ResponseCode = 0x8F.
Command Set
| Range | Category | Key Commands |
|---|---|---|
| 0x00-0x0F | System | 0x00=Query Firmware, 0x01=Device Info, 0x03=Status, 0x04=Ping |
| 0x10-0x1F | Presets | 0x10=Read Preset, 0x11=Write Preset, 0x13=Switch Preset, 0x14=Backup All, 0x16=Factory Reset |
| 0x20-0x2F | MIDI Ctrl | 0x20=Send MIDI Msg, 0x21=Config Preset MIDI |
| 0x30-0x3F | Loop Ctrl | 0x30=Read Loops, 0x31=Toggle One, 0x32=Set All |
| 0x40-0x4F | Config | 0x40=Read Config, 0x42=Read MIDI Channel Config |
| 0xF0-0xFF | Firmware | 0xF0=Enter Bootloader, 0xF3=Send Block (128B) |
4. Checksum and Error Handling
The LdA protocol uses a simple XOR checksum to detect transmission errors. Calculation: XOR Maker ID (0x7D), Dev ID, Command, and all Data bytes together. The receiver validates the checksum and returns ErrorCode 0x03 on mismatch.
// 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| Error Code | Meaning | Action |
|---|---|---|
| 0x01 | Command not supported | Check command byte |
| 0x02 | Invalid data format | Verify data byte range (0-0x7F) |
| 0x03 | Checksum mismatch | Recalculate checksum, retry |
| 0x04 | Device busy | Wait 100ms and retry |
| 0x05 | Storage error | Check memory / factory reset |
5. Practical Examples
Example 1: Query MS-3 Firmware
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)Example 2: Switch to Preset 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)Example 3: Enable Loop 1, Disable 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 Integration
Below is a complete example of using the Web MIDI API to communicate with LdA devices from the browser. Note: Web MIDI API requires HTTPS or localhost, and the user must explicitly grant permission (especially for SysEx access).
// 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. Best Practices & Pitfalls
MIDI transmission can have errors (especially through USB-MIDI adapters). Always verify checksum on receive, and request retransmission on mismatch.
Devices may need time to process SysEx commands (especially writes). On 0x04 error, wait 100-500ms then retry. Add delays between bulk operations.
Large data transfers like preset backups or firmware updates should be chunked (e.g., 128 bytes per block). Wait for device response after each block before sending the next.
All data bytes in SysEx messages must be < 0x80. To transmit 8-bit data, split into 7-bit format (every 7 bytes encoded as 8 SysEx bytes).
In Web MIDI API, SysEx access requires explicit permission (`{ sysex: true }`). Some browsers may block SysEx or require user interaction. Always check `midi.access` permission state.
8. Summary
The SysEx protocol gives MIDI devices deep control capabilities beyond standard messages. The LdA protocol, with its standardized command format, XOR checksum, and comprehensive error code system, provides a reliable and extensible device communication solution. Mastering SysEx means you can write your own control software, implement automated workflows, and even push the boundaries of device functionality.
📚 Further Reading: MIDI Technical Reference — Complete protocol specification and technical details