# MIDI Tutorial > Original: [MIDI Tutorial](https://tttapa.github.io/Control-Surface/Doxygen/d3/df7/midi-tutorial.html) ## MIDI Interfaces A MIDI interface handles the transport layer — BLE, USB, serial, or WiFi. Instantiate one before sending or receiving: ```cpp pico 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). ```cpp pico 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 7 - `MIDI_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: ```cpp pico 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: ```cpp pico 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: ```cpp pico struct MyCallbacks : MIDI_Callbacks { void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override { uint8_t type = static_cast(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: ```cpp pico struct MyFineCallbacks : FineGrainedMIDI_Callbacks { 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: ```cpp pico MIDI_PipeFactory<2> pipes; midi1 >> pipes >> midi2; // midi1 output → midi2 midi2 >> pipes >> midi1; // midi2 output → midi1 ``` Bidirectional: ```cpp pico BidirectionalMIDI_PipeFactory<1> bpipes; midi1 | bpipes | midi2; // full duplex ``` ### Filtering Subclass `MIDI_Pipe` and override `mapForwardMIDI` to filter or transform: ```cpp pico 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); } }; ```