4.1 KiB
MIDI Tutorial
Original: MIDI Tutorial
MIDI Interfaces
A MIDI interface handles the transport layer — BLE, USB, serial, or WiFi. Instantiate one before sending or receiving:
BluetoothMIDI_Interface midi; // BLE MIDI via BTstack
Initialize with midi.begin() (standalone) or let Control_Surface.begin() handle it.
MIDI Addresses
MIDIAddress combines three components: address (0-127), channel (1-16), and cable (1-16, default 1).
MIDIAddress note = {MIDI_Notes::C[4], Channel_1};
MIDIAddress cc = {MIDI_CC::Channel_Volume, Channel_2};
MIDIAddress pc = {MIDI_PC::Harpsichord, Channel_10};
Predefined constants:
MIDI_Notes::C[4]— note number 60 (middle C)MIDI_CC::Channel_Volume— CC 7MIDI_PC::Harpsichord— program 7
Channel defaults to Channel_1, cable defaults to Cable_1.
Sending Messages
All interface objects inherit from MIDI_Sender and provide:
midi.sendNoteOn(address, velocity);
midi.sendNoteOff(address, velocity);
midi.sendControlChange(address, value);
midi.sendProgramChange(address);
midi.sendPitchBend(channel, value); // 14-bit (0-16383)
midi.sendChannelPressure(channel, pressure);
midi.sendKeyPressure(address, pressure);
System messages:
midi.sendTimingClock();
midi.sendStart();
midi.sendContinue();
midi.sendStop();
midi.sendSysEx(data); // uint8_t array with F0...F7
Call midi.sendNow() to flush any buffered output.
Receiving Messages
MIDI_Callbacks
Subclass MIDI_Callbacks and override the handlers you need:
struct MyCallbacks : MIDI_Callbacks {
void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
uint8_t type = static_cast<uint8_t>(msg.getMessageType());
uint8_t d1 = msg.getData1();
uint8_t d2 = msg.getData2();
Channel ch = msg.getChannel();
(void)type; (void)d1; (void)d2; (void)ch;
}
void onSysExMessage(MIDI_Interface &, SysExMessage msg) override {
const uint8_t *data = msg.data;
uint16_t len = msg.length;
(void)data; (void)len;
}
void onRealTimeMessage(MIDI_Interface &, RealTimeMessage msg) override {
(void)msg;
}
} callbacks;
// Register: midi.setCallbacks(callbacks);
FineGrainedMIDI_Callbacks
For per-message-type dispatch, use the CRTP variant:
struct MyFineCallbacks : FineGrainedMIDI_Callbacks<MyFineCallbacks> {
void onNoteOn(Channel ch, uint8_t note, uint8_t vel, Cable cable) {
(void)ch; (void)note; (void)vel; (void)cable;
}
void onNoteOff(Channel ch, uint8_t note, uint8_t vel, Cable cable) {
(void)ch; (void)note; (void)vel; (void)cable;
}
void onControlChange(Channel ch, uint8_t cc, uint8_t val, Cable cable) {
(void)ch; (void)cc; (void)val; (void)cable;
}
} fineCallbacks;
Only override the methods you need — unoverridden methods are no-ops.
Routing Messages
Default routing
When using Control_Surface.begin(), the singleton automatically connects output elements to the default MIDI interface and dispatches incoming messages to input elements.
Manual routing with pipes
MIDI_Pipe connects a source to a sink. Use operators for concise wiring:
MIDI_PipeFactory<2> pipes;
midi1 >> pipes >> midi2; // midi1 output → midi2
midi2 >> pipes >> midi1; // midi2 output → midi1
Bidirectional:
BidirectionalMIDI_PipeFactory<1> bpipes;
midi1 | bpipes | midi2; // full duplex
Filtering
Subclass MIDI_Pipe and override mapForwardMIDI to filter or transform:
struct NoteOnly : MIDI_Pipe {
void mapForwardMIDI(ChannelMessage msg) override {
if (msg.getMessageType() == MIDIMessageType::NoteOn ||
msg.getMessageType() == MIDIMessageType::NoteOff)
sourceMIDItoSink(msg);
}
void mapForwardMIDI(SysExMessage) override {}
void mapForwardMIDI(SysCommonMessage) override {}
void mapForwardMIDI(RealTimeMessage msg) override {
sourceMIDItoSink(msg);
}
};