148 lines
4.1 KiB
Markdown
148 lines
4.1 KiB
Markdown
# 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<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:
|
|
|
|
```cpp pico
|
|
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:
|
|
|
|
```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);
|
|
}
|
|
};
|
|
```
|