cs-midi-docs/docs/manual/02-midi/01-midi-tutorial.md

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);
}
};
```