-
Notifications
You must be signed in to change notification settings - Fork 0
Instrument Developer Guide
This page is for builders of DIY MIDI instruments (Arduino, Teensy, ESP32, custom firmware, …) who want their device to be automatically recognised and configured by Général Midi Boop without any manual setup in the UI.
Full technical specification: docs/SYSEX_IDENTITY.md.
When a new device is connected, Général Midi Boop sends a SysEx Identity Request. If the device responds correctly, GMBoop reads the response and configures the instrument automatically. The more blocks your firmware implements, the more GMBoop can configure without user intervention.
Protocol: Custom SysEx using the MIDI Educational/Development manufacturer ID 0x7D.
| Block | ID | What it declares | Required? |
|---|---|---|---|
| 1 — Identity | 0x01 |
Device name, firmware version, feature flags | Yes — minimum requirement |
| 5 — Instrument Descriptor | 0x05 |
How many instruments, on which channels, which GM type | Multi-instrument devices |
| 6 — Instrument Capabilities | 0x06 |
Note range, polyphony, supported CCs, subtype | For auto-config without user input |
| 7 — String Instrument Config | 0x07 |
String count, fret count, tuning, CC wiring | String instruments only |
All GMBoop SysEx messages share this header:
F0 7D 00 <block_id> <direction> [data...] F7
| Byte | Value | Meaning |
|---|---|---|
F0 |
— | SysEx start |
7D |
— | MIDI Educational / Custom SysEx |
00 |
— | GMBoop Manufacturer ID |
<block_id> |
01–07 | Which block |
<direction> |
00 = request / 01 = response |
Direction flag |
Minimum effort. GMBoop registers the device and assumes one instrument on channel 0. The user configures capabilities manually via the Interface-Instrument-Creation.
GMBoop ──► F0 7D 00 01 00 F7 (Block 1 request)
◄── 52-byte identity response
→ Device registered, 1 instrument on ch 0, manual config needed
GMBoop fully auto-configures the instrument on channel 0 — no user interaction required.
GMBoop ──► Block 1 request
◄── Block 1 response (feature flag bit 4 = INSTRUMENT_CAPABILITIES)
──► Block 6 request (channel 0)
◄── Block 6 response
→ Instrument auto-configured, capabilities_source = 'sysex'
Every instrument on the device is discovered and fully configured automatically, including string geometry.
GMBoop ──► Block 1 request
◄── Block 1 response (feature flags bits 3, 4, 5)
──► Block 5 request
◄── Block 5 response (N instruments with channels and types)
──► Block 6 request × N (one per channel)
◄── Block 6 responses
──► Block 7 request (for each string instrument)
◄── Block 7 responses
→ All instruments auto-configured
F0 7D 00 01 00 F7
Fixed size: 6 bytes.
F0 7D 00 01 01 <version> <deviceId[5]> <name[32]> <firmware[3]> <features[5]> F7
Fixed size: 52 bytes.
| Offset | Size | Field | Description |
|---|---|---|---|
| 5 | 1 | Block version | 0x01 |
| 6–10 | 5 | Device ID | Unique 32-bit ID, 7-bit encoded |
| 11–42 | 32 | Device name | ASCII, null-terminated, padded to 32 bytes |
| 43–45 | 3 | Firmware | [major, minor, patch] |
| 46–50 | 5 | Feature flags | 32-bit bitmask, 7-bit encoded |
MIDI SysEx requires all data bytes to have MSB = 0 (values 0–127). A 32-bit integer is therefore spread across 5 bytes:
void encode32BitTo7Bit(uint32_t value, uint8_t* out) {
out[0] = (value ) & 0x7F;
out[1] = (value >> 7) & 0x7F;
out[2] = (value >> 14) & 0x7F;
out[3] = (value >> 21) & 0x7F;
out[4] = (value >> 28) & 0x07;
}| Bit | Name | Hex | Meaning |
|---|---|---|---|
| 0 | NOTE_MAP |
0x01 |
Supports Block 2 (reserved) |
| 1 | VELOCITY_CURVES |
0x02 |
Supports Block 3 (reserved) |
| 2 | CC_MAPPING |
0x04 |
Supports Block 4 (reserved) |
| 3 | INSTRUMENT_DESCRIPTOR |
0x08 |
Supports Block 5 |
| 4 | INSTRUMENT_CAPABILITIES |
0x10 |
Supports Block 6 |
| 5 | STRING_CONFIG |
0x20 |
Supports Block 7 |
Common combinations:
0x00000000 // Block 1 only — identity, manual config
0x00000010 // Block 1 + 6 — single instrument auto-config
0x00000018 // Block 1 + 5 + 6 — multi-instrument auto-config
0x00000038 // Block 1 + 5 + 6 + 7 — full auto-config with stringsvoid handleIdentityRequest() {
uint8_t response[52];
int pos = 0;
response[pos++] = 0xF0;
response[pos++] = 0x7D;
response[pos++] = 0x00;
response[pos++] = 0x01; // Block 1
response[pos++] = 0x01; // Reply
response[pos++] = 0x01; // Block version
encode32BitTo7Bit(0x12345678, &response[pos]); // Device ID
pos += 5;
const char* name = "MyRobotPiano";
memset(&response[pos], 0, 32);
memcpy(&response[pos], name, strlen(name));
pos += 32;
response[pos++] = 1; // Firmware major
response[pos++] = 0; // Firmware minor
response[pos++] = 0; // Firmware patch
encode32BitTo7Bit(0x00000010, &response[pos]); // Feature: INSTRUMENT_CAPABILITIES
pos += 5;
response[pos++] = 0xF7;
usbMIDI.sendSysEx(52, response);
}- Detect
F0 7D 00 01 00 F7(6 bytes) - Reply header
F0 7D 00 01 01 - Block version =
0x01 - Device ID: 32-bit, 7-bit encoded over 5 bytes
- Device name: ASCII printable, null-terminated, padded to exactly 32 bytes
- Firmware: 3 bytes
[major, minor, patch] - Feature flags: 32-bit, 7-bit encoded over 5 bytes
- End with
F7 - Total = exactly 52 bytes
Use this block if your device hosts more than one instrument on different MIDI channels (e.g. piano on ch 0, drums on ch 9).
F0 7D 00 05 00 F7
F0 7D 00 05 01 <version> <num_instruments> [<channel> <gm_program> <type_id>]... F7
Each instrument entry is 3 bytes. Total size = 8 + (3 × N) bytes.
| ID | Type | GM programs |
|---|---|---|
0x00 |
unknown | — |
0x01 |
piano | 0–7 |
0x02 |
chromatic_percussion | 8–15 |
0x03 |
organ | 16–23 |
0x04 |
guitar | 24–31 |
0x05 |
bass | 32–39 |
0x06 |
strings | 40–47 |
0x07 |
ensemble | 48–55 |
0x08 |
brass | 56–63 |
0x09 |
reed | 64–71 |
0x0A |
pipe | 72–79 |
0x0B |
synth_lead | 80–87 |
0x0C |
synth_pad | 88–95 |
0x0D |
synth_effects | 96–103 |
0x0E |
ethnic | 104–111 |
0x0F |
drums | 112–119 |
0x10 |
sound_effects | 120–127 |
Use 0x7F for gm_program when the program is undefined (typical for drums).
const uint8_t instruments[][3] = {
{ 0, 0, 0x01 }, // ch 0, GM 0 (Acoustic Grand), piano
{ 1, 24, 0x04 }, // ch 1, GM 24 (Nylon Guitar), guitar
{ 9, 0x7F, 0x0F }, // ch 9, no GM, drums
};
void handleDescriptorRequest() {
int N = 3;
uint8_t response[8 + N * 3];
int pos = 0;
response[pos++] = 0xF0;
response[pos++] = 0x7D;
response[pos++] = 0x00;
response[pos++] = 0x05;
response[pos++] = 0x01;
response[pos++] = 0x01; // version
response[pos++] = N;
for (int i = 0; i < N; i++) {
response[pos++] = instruments[i][0];
response[pos++] = instruments[i][1];
response[pos++] = instruments[i][2];
}
response[pos++] = 0xF7;
usbMIDI.sendSysEx(pos, response);
}Provides detailed capability data for a single instrument (identified by channel). GMBoop queries this for each channel it discovers.
F0 7D 00 06 00 <channel> F7
F0 7D 00 06 01 <version> <channel>
<gm_program> <type_id> <subtype_id>
<note_selection_mode> <note_min> <note_max> <polyphony>
<num_selected_notes> [<note>...]
<num_ccs> [<cc>...]
<name_length> [<chars>...]
F7
| Field | Description |
|---|---|
note_selection_mode |
0 = contiguous range, 1 = discrete list of notes |
note_min / note_max
|
MIDI note numbers 0–127 |
polyphony |
Max simultaneous notes (1–127) |
selected_notes |
Only if discrete mode (note_selection_mode = 1) |
supported_ccs |
CC numbers the instrument responds to (affects adaptation and MIDI-learn) |
name |
Instrument display name (ASCII, max 32 chars) |
Subtype IDs are relative to the parent type. The complete subtype table (piano subtypes, guitar subtypes, drum kit types, …) is in docs/SYSEX_IDENTITY.md §18.
void handleCapabilitiesRequest(uint8_t ch) {
if (ch != 0) return;
const char* name = "Grand Piano";
uint8_t ccs[] = { 1, 7, 10, 11, 64 }; // Mod, Vol, Pan, Expr, Sustain
int pos = 0;
uint8_t buf[64];
buf[pos++] = 0xF0; buf[pos++] = 0x7D; buf[pos++] = 0x00;
buf[pos++] = 0x06; buf[pos++] = 0x01;
buf[pos++] = 0x01; // version
buf[pos++] = 0; // channel
buf[pos++] = 0; // GM program 0 (Acoustic Grand)
buf[pos++] = 0x01; // type: piano
buf[pos++] = 0x01; // subtype: acoustic_grand
buf[pos++] = 0; // range mode
buf[pos++] = 21; // A0
buf[pos++] = 108; // C8
buf[pos++] = 88; // polyphony
buf[pos++] = 0; // no discrete notes
buf[pos++] = sizeof(ccs);
for (int i = 0; i < sizeof(ccs); i++) buf[pos++] = ccs[i];
buf[pos++] = strlen(name);
for (int i = 0; i < strlen(name); i++) buf[pos++] = name[i];
buf[pos++] = 0xF7;
usbMIDI.sendSysEx(pos, buf);
}- Detect
F0 7D 00 06 00 <channel> F7 - Reply with the capabilities for the requested channel only
-
note_selection_mode= 0 (range) or 1 (discrete) - If discrete: include the full note list
- CCs array: only CCs the hardware actually responds to
- Name: ASCII, length byte followed by characters (no null terminator)
- All byte values 0–127 (7-bit safe)
- Feature flag bit 4 set in Block 1
Only required for string instruments (guitar, bass, violin, etc.). Declares the physical geometry so GMBoop can run the hand-position planner.
F0 7D 00 07 00 <channel> F7
F0 7D 00 07 01 <version> <channel>
<num_strings> <num_frets> <is_fretless> <capo_fret>
<cc_enabled> <cc_string> <cc_fret>
<tuning[num_strings]>
F7
Total size = 15 + num_strings bytes.
| Field | Description |
|---|---|
num_strings |
Physical string count (1–6) |
num_frets |
Fret count (0 = fretless) |
is_fretless |
0 fretted, 1 fretless |
capo_fret |
0 = no capo; 1–36 = capo position |
cc_enabled |
1 if hardware accepts CC commands for string/fret selection |
cc_string |
CC number for string selection (default 20) |
cc_fret |
CC number for fret position (default 21) |
tuning |
MIDI note per open string, lowest string first |
| Instrument | Tuning bytes (hex) |
|---|---|
| Guitar standard |
28 2D 32 37 3B 40 (E2 A2 D3 G3 B3 E4) |
| Guitar Drop D |
26 2D 32 37 3B 40 (D2 A2 D3 G3 B3 E4) |
| Bass 4-string |
1C 21 26 2B (E1 A1 D2 G2) |
| Violin |
37 3E 45 4C (G3 D4 A4 E5) |
| Cello |
24 2B 32 39 (C2 G2 D3 A3) |
| Ukulele |
43 3C 40 45 (G4 C4 E4 A4) |
void handleStringConfigRequest(uint8_t ch) {
if (ch != 1) return;
uint8_t tuning[] = { 40, 45, 50, 55, 59, 64 }; // E2 A2 D3 G3 B3 E4
int N = 6;
uint8_t buf[15 + N];
int pos = 0;
buf[pos++] = 0xF0; buf[pos++] = 0x7D; buf[pos++] = 0x00;
buf[pos++] = 0x07; buf[pos++] = 0x01;
buf[pos++] = 0x01; // version
buf[pos++] = 1; // channel
buf[pos++] = N; // 6 strings
buf[pos++] = 22; // 22 frets
buf[pos++] = 0; // not fretless
buf[pos++] = 0; // no capo
buf[pos++] = 1; // CC enabled
buf[pos++] = 20; // CC20 = string
buf[pos++] = 21; // CC21 = fret
for (int i = 0; i < N; i++) buf[pos++] = tuning[i];
buf[pos++] = 0xF7;
usbMIDI.sendSysEx(pos, buf);
}void checkSysExRequest() {
if (!usbMIDI.read() || usbMIDI.getType() != usbMIDI.SystemExclusive) return;
uint8_t* data = usbMIDI.getSysExArray();
int len = usbMIDI.getSysExArrayLength();
// Validate GMBoop header: F0 7D 00 <block> 00
if (len < 6 || data[0] != 0xF0 || data[1] != 0x7D || data[2] != 0x00) return;
if (data[4] != 0x00) return; // Not a request
switch (data[3]) {
case 0x01: handleIdentityRequest(); break;
case 0x05: handleDescriptorRequest(); break;
case 0x06: if (len >= 7) handleCapabilitiesRequest(data[5]); break;
case 0x07: if (len >= 7) handleStringConfigRequest(data[5]); break;
}
}These are Général Midi Boop–side settings and are never queried from the instrument:
| Field | Source |
|---|---|
sync_delay |
Measured by the microphone calibrator — see Interface-Microphone |
octave_mode |
User preference (chromatic / diatonic / pentatonic) |
tab_algorithm |
GMBoop tablature processing setting |
capabilities_source |
Set automatically to 'sysex' after auto-config |
| MAC address | Discovered by the Bluetooth stack |
| USB serial number | Discovered by OS USB enumeration |
GMBoop never requests a block the device does not advertise. Feature flags in Block 1 are the gate:
| Device implements | GMBoop behaviour |
|---|---|
| Block 1 only | Identity registered, manual UI config required |
| Block 1 + 6 | Single instrument auto-configured on ch 0 |
| Block 1 + 5 | Multi-instrument discovery, manual capability config |
| Block 1 + 5 + 6 | Full multi-instrument auto-config |
| Block 1 + 5 + 6 + 7 | Full auto-config including string geometry |
A device that only implements Block 1 remains 100 % compatible with all future versions of GMBoop.
- Interface-Instrument-Creation — what the UI shows after auto-config completes
- Interface-Hand-Management — hand-position planning driven by Block 6 and Block 7 data
- Hardware-Integration — physical connection over USB, Bluetooth, and Serial UART
-
docs/SYSEX_IDENTITY.md— full technical specification with complete subtype tables
Général Midi Boop · MIT License · Sources tracked in wiki/ — edits synced on push to main.
Getting Started
Interface — Pages & Modals
- Interface-Main-Page
- Interface-Instrument-Creation
- Interface-Virtual-Piano
- Interface-Loop-Manager
- Interface-Lighting-Control
- Interface-Playlist
- Interface-Microphone
- Interface-Settings
- Interface-Hand-Management
Core Concepts
Reference
Operations
Community