Added more examples, etc
This commit is contained in:
parent
3927d073e9
commit
be1ee30967
|
|
@ -1,12 +1,33 @@
|
||||||
# cs-midi
|
# cs-midi
|
||||||
|
|
||||||
A pico-sdk port of [tttapa/Control-Surface](https://github.com/tttapa/Control-Surface) (GPL-3.0).
|
A standalone extraction of [tttapa/Control-Surface](https://github.com/tttapa/Control-Surface) (GPL-3.0) for embedded platforms.
|
||||||
|
|
||||||
cs-midi provides the full Control Surface MIDI element system — output elements, input elements, banks, selectors, MIDI routing, and the declarative `Control_Surface` singleton — for use with the Raspberry Pi Pico SDK.
|
cs-midi provides the full Control Surface MIDI element system — output elements, input elements, banks, selectors, MIDI routing, and the declarative `Control_Surface` singleton.
|
||||||
|
|
||||||
|
Supported platforms:
|
||||||
|
|
||||||
|
| Platform | Transport | Status |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| **RP2xxx** (pico-sdk) | BLE, USB, Serial, AppleMIDI | Stable |
|
||||||
|
| **ESP32-S3** | BLE, USB, Serial | Planned |
|
||||||
|
| **ESP32-C3** | BLE, Serial | Planned |
|
||||||
|
|
||||||
|
Use the **Platform** dropdown in the sidebar to view examples for your target device.
|
||||||
|
|
||||||
|
### RP2xxx Radio Module Note
|
||||||
|
|
||||||
|
The RP2xxx BLE transport uses BTstack with the CYW43 radio (Infineon RM2 module). While other CYW43-based radio modules may work, **only the RM2 has been tested** and has its driver fully implemented.
|
||||||
|
|
||||||
|
A pico-sdk board file for the Waveshare RP2350B Plus W (RM2-equipped) is provided at `boards/waveshare_rp2350b_plus_w.h`. To use it, set `PICO_BOARD_HEADER_DIRS` in your CMake config:
|
||||||
|
|
||||||
|
```cmake pico
|
||||||
|
set(PICO_BOARD waveshare_rp2350b_plus_w)
|
||||||
|
set(PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}/lib/cs-midi/boards)
|
||||||
|
```
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
|
@ -30,7 +51,7 @@ int main() {
|
||||||
|
|
||||||
cs-midi is a static library meant to be included in a pico-sdk project:
|
cs-midi is a static library meant to be included in a pico-sdk project:
|
||||||
|
|
||||||
```cmake
|
```cmake pico
|
||||||
add_subdirectory(lib/cs-midi)
|
add_subdirectory(lib/cs-midi)
|
||||||
target_link_libraries(your_target cs_midi)
|
target_link_libraries(your_target cs_midi)
|
||||||
```
|
```
|
||||||
|
|
@ -43,6 +64,7 @@ CMake options (set before `add_subdirectory()`):
|
||||||
| `CS_MIDI_USB` | OFF | USB MIDI via TinyUSB |
|
| `CS_MIDI_USB` | OFF | USB MIDI via TinyUSB |
|
||||||
| `CS_MIDI_SERIAL` | OFF | Serial MIDI over UART |
|
| `CS_MIDI_SERIAL` | OFF | Serial MIDI over UART |
|
||||||
| `CS_MIDI_APPLEMIDI` | OFF | AppleMIDI (RTP-MIDI over WiFi) |
|
| `CS_MIDI_APPLEMIDI` | OFF | AppleMIDI (RTP-MIDI over WiFi) |
|
||||||
|
| `CS_MIDI_HID_KEYBOARD` | OFF | HID keyboard + Battery Service for BLE auto-reconnect |
|
||||||
|
|
||||||
USB MIDI requires `tusb_config.h` and `usb_descriptors.c` in your project — see `templates/` for reference files.
|
USB MIDI requires `tusb_config.h` and `usb_descriptors.c` in your project — see `templates/` for reference files.
|
||||||
|
|
||||||
|
|
@ -58,6 +80,8 @@ cd lib/cs-midi && make tests
|
||||||
- Standard `main()` replaces Arduino `setup()`/`loop()`
|
- Standard `main()` replaces Arduino `setup()`/`loop()`
|
||||||
- All types live in the `cs::` namespace
|
- All types live in the `cs::` namespace
|
||||||
- No `MCU::` namespace yet — use raw CC numbers or `MIDI_CC::` constants
|
- No `MCU::` namespace yet — use raw CC numbers or `MIDI_CC::` constants
|
||||||
|
- Direct MIDI sending and callbacks available on all interface objects
|
||||||
|
- `MIDI_Callbacks` and `FineGrainedMIDI_Callbacks<T>` for incoming message handling
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Sends Note On/Off on button press/release.
|
||||||
|
|
||||||
> Original: `NoteButton.ino`
|
> Original: `NoteButton.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Sends CC 127 on press, CC 0 on release.
|
||||||
|
|
||||||
> Original: `CCButton.ino`
|
> Original: `CCButton.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Sends Program Change on button press.
|
||||||
|
|
||||||
> Original: `PCButton.ino`
|
> Original: `PCButton.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Plays a chord on button press.
|
||||||
|
|
||||||
> Original: `NoteChordButton.ino`
|
> Original: `NoteChordButton.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
First press sends Note On, second press sends Note Off.
|
First press sends Note On, second press sends Note Off.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Note On while held, Note Off on release; toggles state.
|
Note On while held, Note Off on release; toggles state.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Multiple note buttons with sequential addresses.
|
Multiple note buttons with sequential addresses.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Grid of note buttons using row/column scanning.
|
||||||
|
|
||||||
> Original: `NoteButtonMatrix.ino`
|
> Original: `NoteButtonMatrix.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
First press sends CC 127, second press sends CC 0.
|
First press sends CC 127, second press sends CC 0.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
CC 127 while held, CC 0 on release; toggles state.
|
CC 127 while held, CC 0 on release; toggles state.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Multiple CC buttons with sequential addresses.
|
Multiple CC buttons with sequential addresses.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Grid of CC buttons using row/column scanning.
|
Grid of CC buttons using row/column scanning.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Analog input mapped to CC value 0-127.
|
||||||
|
|
||||||
> Original: `Control-Change-Potentiometer.ino`
|
> Original: `Control-Change-Potentiometer.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Analog input mapped to 14-bit Pitch Bend.
|
||||||
|
|
||||||
> Original: `Pitch-Bend-Potentiometer.ino`
|
> Original: `Pitch-Bend-Potentiometer.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Relative CC encoder for DAW parameter control.
|
||||||
|
|
||||||
> Original: `RotaryEncoder.ino`
|
> Original: `RotaryEncoder.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Encoder position mapped to absolute CC value.
|
||||||
|
|
||||||
> Original: `AbsoluteRotaryEncoder.ino`
|
> Original: `AbsoluteRotaryEncoder.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Encoder position mapped to 14-bit Pitch Bend.
|
Encoder position mapped to 14-bit Pitch Bend.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Two buttons for CC increment/decrement with optional reset.
|
||||||
|
|
||||||
> Original: `CCIncrementDecrementButtons.ino`
|
> Original: `CCIncrementDecrementButtons.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Sends different program changes based on selector state.
|
||||||
|
|
||||||
> Original: `Program-Changer.ino`
|
> Original: `Program-Changer.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Several analog inputs mapped to CC on different channels.
|
||||||
|
|
||||||
> Original: `Multiple-Control-Change-Potentiometers.ino`
|
> Original: `Multiple-Control-Change-Potentiometers.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Shared encoder reference for CC output, allowing multiple elements to read the same physical encoder.
|
Shared encoder reference for CC output, allowing multiple elements to read the same physical encoder.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# CCPotentiometer Map
|
||||||
|
|
||||||
|
Analog input with a custom mapping function to eliminate dead zones.
|
||||||
|
|
||||||
|
> Original: `CCPotentiometer-Map.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
CCPotentiometer potentiometer {26, {MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
|
||||||
|
constexpr analog_t maxRawValue = CCPotentiometer::getMaxRawValue();
|
||||||
|
constexpr analog_t minimumValue = maxRawValue / 64;
|
||||||
|
constexpr analog_t maximumValue = maxRawValue - maxRawValue / 64;
|
||||||
|
|
||||||
|
analog_t mappingFunction(analog_t raw) {
|
||||||
|
if (raw < minimumValue) raw = minimumValue;
|
||||||
|
if (raw > maximumValue) raw = maximumValue;
|
||||||
|
return (raw - minimumValue) * maxRawValue / (maximumValue - minimumValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
potentiometer.map(mappingFunction);
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Finished Controller
|
||||||
|
|
||||||
|
Comprehensive example combining buttons, encoder, potentiometer, and bank switching with bankable input elements.
|
||||||
|
|
||||||
|
> Original: `MIDI-Controller-Finished-Example.ino` (adapted)
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
// 4 banks, 2 tracks per bank
|
||||||
|
Bank<4> bank(2);
|
||||||
|
|
||||||
|
// Two buttons to cycle banks
|
||||||
|
IncrementDecrementSelector<4> bankSelector {bank, {2, 3}, Wrap::Wrap};
|
||||||
|
|
||||||
|
// Bankable CC value readers — bank switches the CC address
|
||||||
|
Bankable::CCValue<4> volume1 {{bank, BankType::ChangeAddress}, {16, Channel_1}};
|
||||||
|
Bankable::CCValue<4> volume2 {{bank, BankType::ChangeAddress}, {17, Channel_1}};
|
||||||
|
|
||||||
|
// Non-bankable output elements
|
||||||
|
NoteButton button {5, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
CCPotentiometer pot {26, {MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
CCRotaryEncoder enc {{0, 1}, {MIDI_CC::Pan, Channel_1}, 1, 4};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Enable/Disable Elements
|
||||||
|
|
||||||
|
Enable and disable MIDI elements at runtime using a toggle button.
|
||||||
|
|
||||||
|
> Original: `Enable-disable.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
NoteButton button1 {5, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
NoteButton button2 {6, {MIDI_Notes::D[4], Channel_1}};
|
||||||
|
AH::Button toggleBtn {7};
|
||||||
|
|
||||||
|
bool button2Enabled = true;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
toggleBtn.begin();
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
toggleBtn.update();
|
||||||
|
if (toggleBtn.getState() == AH::Button::Falling) {
|
||||||
|
button2Enabled = !button2Enabled;
|
||||||
|
if (button2Enabled)
|
||||||
|
button2.enable();
|
||||||
|
else
|
||||||
|
button2.disable();
|
||||||
|
}
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -4,7 +4,7 @@ LED responds to incoming Note On/Off messages.
|
||||||
|
|
||||||
> Original: `1.Note-LED.ino`
|
> Original: `1.Note-LED.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Multiple LEDs respond to a range of incoming notes.
|
||||||
|
|
||||||
> Original: `2.Note-Range-LEDs.ino`
|
> Original: `2.Note-Range-LEDs.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Reads incoming CC messages and stores the value.
|
Reads incoming CC messages and stores the value.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Reads incoming Pitch Bend messages (14-bit).
|
||||||
|
|
||||||
> Original: `Pitch-Bend-Value.ino`
|
> Original: `Pitch-Bend-Value.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Reads incoming Note On values (8-bit).
|
Reads incoming Note On values (8-bit).
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Reads incoming Key Pressure (aftertouch) values.
|
Reads incoming Key Pressure (aftertouch) values.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
LED responds to incoming CC messages.
|
LED responds to incoming CC messages.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Reads a contiguous range of incoming Note values.
|
Reads a contiguous range of incoming Note values.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Reads a contiguous range of incoming CC values.
|
Reads a contiguous range of incoming CC values.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,21 @@
|
||||||
|
|
||||||
Bluetooth Low Energy MIDI interface setup.
|
Bluetooth Low Energy MIDI interface setup.
|
||||||
|
|
||||||
```cpp
|
Build with `CS_MIDI_HID_KEYBOARD=ON` to also advertise as a BLE HID
|
||||||
|
keyboard with a Battery Service. This serves two purposes:
|
||||||
|
|
||||||
|
1. **Auto-reconnect** — the host OS auto-reconnects to bonded HID
|
||||||
|
peripherals on power-up, which brings up the MIDI service without
|
||||||
|
manual intervention.
|
||||||
|
2. **Battery level** — the standard BLE Battery Service (UUID 0x180F)
|
||||||
|
reports charge level to the OS. Call `setBLEBatteryLevel(percent)` from
|
||||||
|
your application to update it (e.g. from an ADC reading on a voltage
|
||||||
|
divider).
|
||||||
|
|
||||||
|
No keyboard input is ever sent — the HID descriptor exists solely to
|
||||||
|
trigger the OS auto-connect behavior and surface the battery indicator.
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Routing between MIDI interfaces using pipe operators.
|
||||||
|
|
||||||
> Original: `MIDI_Pipes-Routing.ino`
|
> Original: `MIDI_Pipes-Routing.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ USB MIDI device interface via TinyUSB.
|
||||||
|
|
||||||
Requires `tusb_config.h` and `usb_descriptors.c` in the project — see `lib/cs-midi/templates/` for reference files. Link `tinyusb_device` and `tinyusb_board` in CMake.
|
Requires `tusb_config.h` and `usb_descriptors.c` in the project — see `lib/cs-midi/templates/` for reference files. Link `tinyusb_device` and `tinyusb_board` in CMake.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "tusb.h"
|
#include "tusb.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Classic 5-pin DIN MIDI over hardware UART at 31250 baud.
|
||||||
|
|
||||||
Constructor: `HardwareSerialMIDI_Interface(uart_inst_t *uart, uint tx_pin, uint rx_pin)`
|
Constructor: `HardwareSerialMIDI_Interface(uart_inst_t *uart, uint tx_pin, uint rx_pin)`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Multiple MIDI interfaces with pipe routing. Elements send to both transports simultaneously.
|
Multiple MIDI interfaces with pipe routing. Elements send to both transports simultaneously.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include "tusb.h"
|
#include "tusb.h"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,16 @@ Windows rtpMIDI, and iOS. Requires WiFi connection before `Control_Surface.begin
|
||||||
|
|
||||||
Build with `CS_MIDI_APPLEMIDI=ON`.
|
Build with `CS_MIDI_APPLEMIDI=ON`.
|
||||||
|
|
||||||
```cpp
|
> **Reliability note:** AppleMIDI is implemented via the vendored
|
||||||
|
> [lathoub/Arduino-AppleMIDI-Library](https://github.com/lathoub/Arduino-AppleMIDI-Library)
|
||||||
|
> with a LwIPUDP adapter wrapping the lwIP raw UDP API and mDNS/Bonjour
|
||||||
|
> service advertisement. The protocol implementation is complete and the
|
||||||
|
> device does appear in Audio MIDI Setup, but in our testing the session
|
||||||
|
> would not reliably establish on at least one macOS host. We suspect this
|
||||||
|
> is device-specific — your mileage may vary. If you have success (or
|
||||||
|
> failure) on your setup, reports are welcome.
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,11 @@ Elements send to both transports simultaneously via pipe routing.
|
||||||
|
|
||||||
Build with `CS_MIDI_BLE=ON CS_MIDI_APPLEMIDI=ON`.
|
Build with `CS_MIDI_BLE=ON CS_MIDI_APPLEMIDI=ON`.
|
||||||
|
|
||||||
```cpp
|
> **Note:** See the [AppleMIDI example](06-applemidi.md) for a reliability
|
||||||
|
> caveat on the WiFi transport. BLE remains the more reliable option on
|
||||||
|
> macOS/iOS.
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Direct MIDI Output
|
||||||
|
|
||||||
|
Send MIDI messages directly through the interface without using the element system.
|
||||||
|
|
||||||
|
> Original: `MIDI-Output.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
const MIDIAddress address {MIDI_Notes::C[4], Channel_1};
|
||||||
|
const uint8_t velocity = 0x7F;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi.begin();
|
||||||
|
while (true) {
|
||||||
|
midi.sendNoteOn(address, velocity);
|
||||||
|
sleep_ms(500);
|
||||||
|
midi.sendNoteOff(address, velocity);
|
||||||
|
sleep_ms(500);
|
||||||
|
midi.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Send MIDI Notes
|
||||||
|
|
||||||
|
Button-driven Note On/Off using the MIDI interface directly, without the element system.
|
||||||
|
|
||||||
|
> Original: `Send-MIDI-Notes.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
AH::Button pushbutton {5};
|
||||||
|
|
||||||
|
const MIDIAddress noteAddress {MIDI_Notes::C[4], Channel_1};
|
||||||
|
const uint8_t velocity = 0x7F;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
pushbutton.begin();
|
||||||
|
midi.begin();
|
||||||
|
while (true) {
|
||||||
|
midi.update();
|
||||||
|
pushbutton.update();
|
||||||
|
if (pushbutton.getState() == AH::Button::Falling)
|
||||||
|
midi.sendNoteOn(noteAddress, velocity);
|
||||||
|
else if (pushbutton.getState() == AH::Button::Rising)
|
||||||
|
midi.sendNoteOff(noteAddress, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Send All MIDI Messages
|
||||||
|
|
||||||
|
Demonstrates every MIDI message type: channel voice, system common, system exclusive, and real-time.
|
||||||
|
|
||||||
|
> Original: `Send-All-MIDI-Messages.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi.begin();
|
||||||
|
|
||||||
|
// Channel voice messages
|
||||||
|
MIDIAddress note = {MIDI_Notes::C[4], Channel_6};
|
||||||
|
midi.sendNoteOn(note, 127);
|
||||||
|
midi.sendNoteOff(note, 127);
|
||||||
|
midi.sendKeyPressure(note, 64);
|
||||||
|
|
||||||
|
MIDIAddress controller = {MIDI_CC::Channel_Volume, Channel_2};
|
||||||
|
midi.sendControlChange(controller, 120);
|
||||||
|
|
||||||
|
MIDIAddress program = {MIDI_PC::Harpsichord, Channel_9};
|
||||||
|
midi.sendProgramChange(program);
|
||||||
|
|
||||||
|
midi.sendChannelPressure(Channel_3, 64);
|
||||||
|
midi.sendPitchBend(Channel_3, 16383);
|
||||||
|
|
||||||
|
// System common messages
|
||||||
|
midi.sendMTCQuarterFrame(2, 15);
|
||||||
|
midi.sendSongPositionPointer(10000);
|
||||||
|
midi.sendSongSelect(70);
|
||||||
|
midi.sendTuneRequest();
|
||||||
|
|
||||||
|
// System exclusive
|
||||||
|
uint8_t sysex[] = {0xF0, 0x00, 0x01, 0x02, 0x03, 0xF7};
|
||||||
|
midi.sendSysEx(sysex);
|
||||||
|
|
||||||
|
// Real-time messages
|
||||||
|
midi.sendTimingClock();
|
||||||
|
midi.sendStart();
|
||||||
|
midi.sendContinue();
|
||||||
|
midi.sendStop();
|
||||||
|
midi.sendActiveSensing();
|
||||||
|
midi.sendSystemReset();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
midi.update();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# MIDI Input Callback
|
||||||
|
|
||||||
|
Receive and handle incoming MIDI messages using the `MIDI_Callbacks` class.
|
||||||
|
|
||||||
|
> Original: `MIDI-Input.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
struct MyMIDI_Callbacks : MIDI_Callbacks {
|
||||||
|
void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
|
||||||
|
(void)msg;
|
||||||
|
}
|
||||||
|
void onSysExMessage(MIDI_Interface &, SysExMessage msg) override {
|
||||||
|
(void)msg;
|
||||||
|
}
|
||||||
|
void onRealTimeMessage(MIDI_Interface &, RealTimeMessage msg) override {
|
||||||
|
(void)msg;
|
||||||
|
}
|
||||||
|
} callback;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi.begin();
|
||||||
|
midi.setCallbacks(callback);
|
||||||
|
while (true) {
|
||||||
|
midi.update();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# MIDI Pipe Filter
|
||||||
|
|
||||||
|
Custom MIDI pipe that filters and modifies messages. Only Note messages pass through, transposed down one octave and redirected to Channel 5.
|
||||||
|
|
||||||
|
> Original: `MIDI_Pipes-Filter.ino`
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
struct NoteFilter : MIDI_Pipe {
|
||||||
|
void mapForwardMIDI(ChannelMessage msg) override {
|
||||||
|
switch (msg.getMessageType()) {
|
||||||
|
case MIDIMessageType::NoteOff:
|
||||||
|
case MIDIMessageType::NoteOn:
|
||||||
|
msg.setChannel(Channel_5);
|
||||||
|
if (msg.data1 >= 12)
|
||||||
|
msg.data1 -= 12;
|
||||||
|
sourceMIDItoSink(msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void mapForwardMIDI(SysExMessage) override {}
|
||||||
|
void mapForwardMIDI(SysCommonMessage) override {}
|
||||||
|
void mapForwardMIDI(RealTimeMessage msg) override {
|
||||||
|
sourceMIDItoSink(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NoteFilter filter;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi >> filter >> midi;
|
||||||
|
midi.begin();
|
||||||
|
while (true) {
|
||||||
|
midi.update();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -4,7 +4,7 @@ Bank switching with bankable input elements. Bank switching changes the CC addre
|
||||||
|
|
||||||
> Original: `Bank.ino` (adapted)
|
> Original: `Bank.ino` (adapted)
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Note transposition via increment/decrement buttons.
|
||||||
|
|
||||||
> Original: `Transposer.ino`
|
> Original: `Transposer.ino`
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Bank selection via rotary encoder.
|
Bank selection via rotary encoder.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
One button per bank for direct bank selection.
|
One button per bank for direct bank selection.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Two-state bank selection from a toggle switch.
|
Two-state bank selection from a toggle switch.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Bank selection triggered by incoming MIDI Program Change messages.
|
Bank selection triggered by incoming MIDI Program Change messages.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Bank-switched LED responds to different notes per bank.
|
Bank-switched LED responds to different notes per bank.
|
||||||
|
|
||||||
```cpp
|
```cpp pico
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/cyw43_arch.h"
|
#include "pico/cyw43_arch.h"
|
||||||
#include <cs_midi.h>
|
#include <cs_midi.h>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,18 @@
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
Each example is a 1:1 adaptation of the corresponding [Control Surface example](https://tttapa.github.io/Control-Surface/Doxygen/d4/de9/examples.html), rewritten for pico-sdk conventions. All examples are compile-verified.
|
Each example is a 1:1 adaptation of the corresponding [Control Surface example](https://tttapa.github.io/Control-Surface/Doxygen/d4/de9/examples.html), rewritten for pico-sdk conventions. All examples are compile-verified as tests.
|
||||||
|
|
||||||
|
Use the **Platform** dropdown in the sidebar to view examples for your target device. Examples not yet available for a platform will show a notice.
|
||||||
|
|
||||||
|
## Original examples not yet portable
|
||||||
|
|
||||||
|
The following Control Surface examples require features not yet implemented in cs-midi:
|
||||||
|
|
||||||
|
- **Bankable output elements** (Phase 11) — `Bank.ino` output side, `Bankable-Smart-Control-Change-Potentiometer.ino`, `Bank-Button-Matrix.ino`, `Note-ManyAddresses-Transposer.ino`, `ManyAddressesPCButton.ino`
|
||||||
|
- **MCP23017 / shift registers** — `MCP23017-RotaryEncoder-*.ino`, `1.First-Output.ino` (mux), `2.First-Input.ino` (shift register)
|
||||||
|
- **Display / MCU** — all OLED and Mackie Control examples
|
||||||
|
- **FastLED / PWM LEDs** — `Note-FastLED*.ino`, `Note-LED-PWM.ino`, `NoteLEDBar.ino`, `VULEDs.ino`
|
||||||
|
- **Debug interface** — `Debug-MIDI-Interface.ino`, `MIDI-Monitor*.ino`
|
||||||
|
- **SysEx helpers** — `SysEx-Send-Receive.ino`
|
||||||
|
- **USB/BLE adapters** — `BLEMIDI-Adapter.ino`, `USBMIDI-Adapter.ino`, `USBHostMIDI_Interface-Passthrough.ino`
|
||||||
|
- **Board-specific** — Teensy audio, ESP32 VU bridge
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
> Original: [Installation](https://tttapa.github.io/Control-Surface/Doxygen/d8/da8/md_pages_Installation.html)
|
||||||
|
|
||||||
|
cs-midi is a CMake static library included as a subdirectory of your pico-sdk project.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [pico-sdk](https://github.com/raspberrypi/pico-sdk) (1.5.1+)
|
||||||
|
- CMake 3.13+
|
||||||
|
- ARM GCC toolchain (`arm-none-eabi-gcc`)
|
||||||
|
|
||||||
|
## Adding cs-midi to your project
|
||||||
|
|
||||||
|
Place cs-midi under `lib/` (or any subdirectory) and add it from your project's `CMakeLists.txt`:
|
||||||
|
|
||||||
|
```cmake pico
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(my_controller C CXX ASM)
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
# Enable desired transports before add_subdirectory
|
||||||
|
set(CS_MIDI_BLE ON)
|
||||||
|
set(CS_MIDI_USB OFF)
|
||||||
|
set(CS_MIDI_SERIAL OFF)
|
||||||
|
set(CS_MIDI_APPLEMIDI OFF)
|
||||||
|
|
||||||
|
add_subdirectory(lib/cs-midi)
|
||||||
|
|
||||||
|
add_executable(my_controller main.cpp)
|
||||||
|
target_link_libraries(my_controller cs_midi)
|
||||||
|
|
||||||
|
pico_add_extra_outputs(my_controller)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Board configuration
|
||||||
|
|
||||||
|
For RP2350 boards with the RM2 radio module, cs-midi provides a board header:
|
||||||
|
|
||||||
|
```cmake pico
|
||||||
|
set(PICO_BOARD waveshare_rp2350b_plus_w)
|
||||||
|
set(PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}/lib/cs-midi/boards)
|
||||||
|
```
|
||||||
|
|
||||||
|
For Pico W (RP2040), use the built-in `pico_w` board:
|
||||||
|
|
||||||
|
```cmake pico
|
||||||
|
set(PICO_BOARD pico_w)
|
||||||
|
```
|
||||||
|
|
||||||
|
## CMake options
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `CS_MIDI_BLE` | ON | BLE MIDI via BTstack |
|
||||||
|
| `CS_MIDI_USB` | OFF | USB MIDI via TinyUSB |
|
||||||
|
| `CS_MIDI_SERIAL` | OFF | Serial MIDI over UART |
|
||||||
|
| `CS_MIDI_APPLEMIDI` | OFF | AppleMIDI (RTP-MIDI over WiFi) |
|
||||||
|
| `CS_MIDI_HID_KEYBOARD` | OFF | HID keyboard + Battery Service for BLE auto-reconnect |
|
||||||
|
|
||||||
|
USB MIDI requires `tusb_config.h` and `usb_descriptors.c` in your project — see `templates/` for reference files.
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
# First Output
|
||||||
|
|
||||||
|
> Original: [Getting Started — First Output](https://tttapa.github.io/Control-Surface/Doxygen/d5/d7d/md_pages_Getting-Started.html)
|
||||||
|
|
||||||
|
A minimal sketch that reads a physical control and sends MIDI.
|
||||||
|
|
||||||
|
## 1. Include the library
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
```
|
||||||
|
|
||||||
|
`cs_midi.h` provides all Control Surface classes. The `cs::` namespace contains everything; `using namespace cs` keeps examples concise.
|
||||||
|
|
||||||
|
## 2. Instantiate a MIDI interface
|
||||||
|
|
||||||
|
At least one MIDI interface must exist before `Control_Surface.begin()`.
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
```
|
||||||
|
|
||||||
|
Available interfaces:
|
||||||
|
|
||||||
|
| Class | Transport | CMake option |
|
||||||
|
|-------|-----------|-------------|
|
||||||
|
| `BluetoothMIDI_Interface` | BLE MIDI (BTstack) | `CS_MIDI_BLE` |
|
||||||
|
| `USBMIDI_Interface` | USB MIDI (TinyUSB) | `CS_MIDI_USB` |
|
||||||
|
| `HardwareSerialMIDI_Interface` | 5-pin DIN MIDI | `CS_MIDI_SERIAL` |
|
||||||
|
| `AppleMIDI_Interface` | RTP-MIDI over WiFi | `CS_MIDI_APPLEMIDI` |
|
||||||
|
|
||||||
|
## 3. Add MIDI output elements
|
||||||
|
|
||||||
|
Output elements read physical inputs and send MIDI messages automatically.
|
||||||
|
|
||||||
|
A single potentiometer sending CC:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
CCPotentiometer pot {26, {MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
```
|
||||||
|
|
||||||
|
Constructor arguments: GPIO pin, then MIDI address (controller number, channel).
|
||||||
|
|
||||||
|
Multiple buttons:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
NoteButton buttons[] {
|
||||||
|
{5, {MIDI_Notes::C[4], Channel_1}},
|
||||||
|
{6, {MIDI_Notes::D[4], Channel_1}},
|
||||||
|
{7, {MIDI_Notes::E[4], Channel_1}},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Initialize and run
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`Control_Surface.begin()` initializes all interfaces and elements. `Control_Surface.loop()` polls inputs and sends MIDI when values change.
|
||||||
|
|
||||||
|
## Complete example
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
CCPotentiometer pot {26, {MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
NoteButton button {5, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
# First Input
|
||||||
|
|
||||||
|
> Original: [Getting Started — First Input](https://tttapa.github.io/Control-Surface/Doxygen/d5/d7d/md_pages_Getting-Started.html)
|
||||||
|
|
||||||
|
A minimal sketch that receives MIDI and drives outputs (LEDs).
|
||||||
|
|
||||||
|
## MIDI input elements
|
||||||
|
|
||||||
|
Input elements listen for specific MIDI messages and update their state. `NoteLED` drives a GPIO pin high when its note is active:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
NoteLED led {25, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
```
|
||||||
|
|
||||||
|
An array of LEDs responding to consecutive notes:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
NoteLED leds[] {
|
||||||
|
{16, MIDI_Notes::C[4]},
|
||||||
|
{17, MIDI_Notes::D[4]},
|
||||||
|
{18, MIDI_Notes::E[4]},
|
||||||
|
{19, MIDI_Notes::F[4]},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Value readers store the incoming value without driving hardware:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
CCValue ccVal {{MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
```
|
||||||
|
|
||||||
|
Access the current value with `ccVal.getValue()`.
|
||||||
|
|
||||||
|
## Complete example
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
NoteLED led {25, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
|
||||||
|
CCValue volume {{MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
uint8_t v = volume.getValue();
|
||||||
|
(void)v;
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
> Original: [Frequently Asked Questions](https://tttapa.github.io/Control-Surface/Doxygen/da/dc1/FAQ.html)
|
||||||
|
|
||||||
|
## Can cs-midi be used as a general MIDI library?
|
||||||
|
|
||||||
|
Yes. You can use the MIDI interface objects directly for sending and receiving, without using the element system or `Control_Surface` singleton.
|
||||||
|
|
||||||
|
Sending:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi.begin();
|
||||||
|
midi.sendNoteOn({MIDI_Notes::C[4], Channel_1}, 127);
|
||||||
|
midi.sendNoteOff({MIDI_Notes::C[4], Channel_1}, 127);
|
||||||
|
while (true) { midi.update(); sleep_ms(1); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Receiving with callbacks:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
struct MyCallbacks : MIDI_Callbacks {
|
||||||
|
void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
|
||||||
|
// handle msg
|
||||||
|
(void)msg;
|
||||||
|
}
|
||||||
|
} cb;
|
||||||
|
|
||||||
|
// in main(): midi.setCallbacks(cb);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Can addresses or channels be changed at runtime?
|
||||||
|
|
||||||
|
Yes, through banks. A `Bank<N>` groups bankable elements and a selector controls which bank is active. See the [Banks](../../examples/04-banks/) examples.
|
||||||
|
|
||||||
|
## What's the difference from Control Surface?
|
||||||
|
|
||||||
|
cs-midi is a source-level extraction of Control Surface for pico-sdk. Key differences:
|
||||||
|
|
||||||
|
- `#include <cs_midi.h>` instead of `#include <Control_Surface.h>`
|
||||||
|
- Standard `main()` replaces Arduino `setup()`/`loop()`
|
||||||
|
- `cs::` namespace instead of top-level
|
||||||
|
- BTstack BLE backend instead of ArduinoBLE/Bluedroid
|
||||||
|
- No MCU protocol, displays, FastLED, or hardware expanders (yet)
|
||||||
|
|
||||||
|
## What radio modules are supported?
|
||||||
|
|
||||||
|
The BLE transport uses BTstack with the CYW43 radio. Only the **Infineon RM2** module has been tested. Other CYW43 variants may work but are unverified.
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
Setup, installation, and first steps with cs-midi.
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
# 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Sending MIDI Messages
|
||||||
|
|
||||||
|
> Original: [Sending MIDI messages](https://tttapa.github.io/Control-Surface/Doxygen/d3/df7/midi-tutorial.html)
|
||||||
|
|
||||||
|
## Direct sending
|
||||||
|
|
||||||
|
Use the interface object directly when you don't need the element system:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi.begin();
|
||||||
|
|
||||||
|
MIDIAddress note = {MIDI_Notes::C[4], Channel_1};
|
||||||
|
midi.sendNoteOn(note, 127);
|
||||||
|
sleep_ms(500);
|
||||||
|
midi.sendNoteOff(note, 0);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
midi.update();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Channel voice messages
|
||||||
|
|
||||||
|
| Method | Arguments | Description |
|
||||||
|
|--------|-----------|-------------|
|
||||||
|
| `sendNoteOn` | address, velocity | Note On |
|
||||||
|
| `sendNoteOff` | address, velocity | Note Off |
|
||||||
|
| `sendControlChange` | address, value | Control Change (0-127) |
|
||||||
|
| `sendProgramChange` | address | Program Change |
|
||||||
|
| `sendPitchBend` | channel, value | Pitch Bend (0-16383, center 8192) |
|
||||||
|
| `sendChannelPressure` | channel, pressure | Channel Pressure |
|
||||||
|
| `sendKeyPressure` | address, pressure | Polyphonic Key Pressure |
|
||||||
|
|
||||||
|
## System common messages
|
||||||
|
|
||||||
|
| Method | Arguments | Description |
|
||||||
|
|--------|-----------|-------------|
|
||||||
|
| `sendMTCQuarterFrame` | type, value | MIDI Time Code quarter frame |
|
||||||
|
| `sendSongPositionPointer` | beats | Song position (14-bit) |
|
||||||
|
| `sendSongSelect` | song | Song number (0-127) |
|
||||||
|
| `sendTuneRequest` | — | Tune request |
|
||||||
|
|
||||||
|
## System exclusive
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
uint8_t sysex[] = {0xF0, 0x7E, 0x7F, 0x06, 0x01, 0xF7};
|
||||||
|
midi.sendSysEx(sysex);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-time messages
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `sendTimingClock` | MIDI clock tick |
|
||||||
|
| `sendStart` | Start sequence |
|
||||||
|
| `sendContinue` | Continue sequence |
|
||||||
|
| `sendStop` | Stop sequence |
|
||||||
|
| `sendActiveSensing` | Active sensing |
|
||||||
|
| `sendSystemReset` | System reset |
|
||||||
|
|
||||||
|
## Flushing
|
||||||
|
|
||||||
|
Call `midi.sendNow()` to flush buffered messages immediately.
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Receiving MIDI Messages
|
||||||
|
|
||||||
|
> Original: [Receiving MIDI messages](https://tttapa.github.io/Control-Surface/Doxygen/d3/df7/midi-tutorial.html)
|
||||||
|
|
||||||
|
## With MIDI_Callbacks
|
||||||
|
|
||||||
|
Register a callback object on the interface to handle incoming messages:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
struct MyCallbacks : MIDI_Callbacks {
|
||||||
|
void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
|
||||||
|
auto type = msg.getMessageType();
|
||||||
|
Channel ch = msg.getChannel();
|
||||||
|
uint8_t d1 = msg.getData1();
|
||||||
|
uint8_t d2 = msg.getData2();
|
||||||
|
(void)type; (void)ch; (void)d1; (void)d2;
|
||||||
|
}
|
||||||
|
} cb;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
midi.begin();
|
||||||
|
midi.setCallbacks(cb);
|
||||||
|
while (true) {
|
||||||
|
midi.update();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`midi.update()` reads incoming data and dispatches to callbacks.
|
||||||
|
|
||||||
|
## With FineGrainedMIDI_Callbacks
|
||||||
|
|
||||||
|
For per-message-type handlers, use the CRTP pattern:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
struct Handlers : FineGrainedMIDI_Callbacks<Handlers> {
|
||||||
|
void onNoteOn(Channel ch, uint8_t note, uint8_t velocity, Cable cable) {
|
||||||
|
(void)ch; (void)note; (void)velocity; (void)cable;
|
||||||
|
}
|
||||||
|
void onControlChange(Channel ch, uint8_t cc, uint8_t value, Cable cable) {
|
||||||
|
(void)ch; (void)cc; (void)value; (void)cable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Handlers handlers;
|
||||||
|
// midi.setCallbacks(handlers);
|
||||||
|
```
|
||||||
|
|
||||||
|
Available overrides: `onNoteOn`, `onNoteOff`, `onKeyPressure`, `onControlChange`, `onProgramChange`, `onChannelPressure`, `onPitchBend`, `onSystemExclusive`, `onTimeCodeQuarterFrame`, `onSongPosition`, `onSongSelect`, `onTuneRequest`, `onClock`, `onStart`, `onContinue`, `onStop`, `onActiveSensing`, `onSystemReset`.
|
||||||
|
|
||||||
|
## ChannelMessage accessors
|
||||||
|
|
||||||
|
| Method | Returns |
|
||||||
|
|--------|---------|
|
||||||
|
| `getMessageType()` | `MIDIMessageType` enum |
|
||||||
|
| `getChannel()` | `Channel` |
|
||||||
|
| `getData1()` | First data byte (note/CC number) |
|
||||||
|
| `getData2()` | Second data byte (velocity/value) |
|
||||||
|
| `getData14bit()` | 14-bit value (Pitch Bend) |
|
||||||
|
| `getCable()` | `Cable` |
|
||||||
|
|
||||||
|
## With the element system
|
||||||
|
|
||||||
|
MIDI input elements (`CCValue`, `NoteLED`, etc.) handle message matching and state management automatically — no callbacks needed. They update on each `Control_Surface.loop()` call.
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Routing MIDI Messages
|
||||||
|
|
||||||
|
> Original: [Routing MIDI messages](https://tttapa.github.io/Control-Surface/Doxygen/d3/df7/midi-tutorial.html)
|
||||||
|
|
||||||
|
## Default routing
|
||||||
|
|
||||||
|
`Control_Surface.begin()` automatically connects the default MIDI interface to all output and input elements. For single-interface projects, no manual routing is needed.
|
||||||
|
|
||||||
|
## MIDI Pipes
|
||||||
|
|
||||||
|
Pipes connect sources to sinks. Use shift operators:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
MIDI_PipeFactory<2> pipes;
|
||||||
|
midi1 >> pipes >> midi2; // midi1 sends to midi2
|
||||||
|
midi2 >> pipes >> midi1; // midi2 sends to midi1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bidirectional pipes
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
BidirectionalMIDI_PipeFactory<1> bpipes;
|
||||||
|
midi1 | bpipes | midi2;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom filtering
|
||||||
|
|
||||||
|
Subclass `MIDI_Pipe` and override `mapForwardMIDI`:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
struct ChannelFilter : MIDI_Pipe {
|
||||||
|
Channel allowed;
|
||||||
|
ChannelFilter(Channel ch) : allowed(ch) {}
|
||||||
|
|
||||||
|
void mapForwardMIDI(ChannelMessage msg) override {
|
||||||
|
if (msg.getChannel() == allowed)
|
||||||
|
sourceMIDItoSink(msg);
|
||||||
|
}
|
||||||
|
void mapForwardMIDI(SysExMessage msg) override {
|
||||||
|
sourceMIDItoSink(msg);
|
||||||
|
}
|
||||||
|
void mapForwardMIDI(SysCommonMessage msg) override {
|
||||||
|
sourceMIDItoSink(msg);
|
||||||
|
}
|
||||||
|
void mapForwardMIDI(RealTimeMessage msg) override {
|
||||||
|
sourceMIDItoSink(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ChannelFilter ch1Only {Channel_1};
|
||||||
|
// midi >> ch1Only >> otherMidi;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-interface example
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface ble;
|
||||||
|
USBMIDI_Interface usb;
|
||||||
|
|
||||||
|
BidirectionalMIDI_PipeFactory<1> pipes;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
ble | pipes | usb;
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# MIDI over USB
|
||||||
|
|
||||||
|
> Original: [MIDI over USB](https://tttapa.github.io/Control-Surface/Doxygen/d8/d4a/md_pages_MIDI-over-USB.html)
|
||||||
|
|
||||||
|
USB MIDI uses TinyUSB to present the device as a standard USB MIDI class-compliant device. No drivers needed on macOS, Windows, or Linux.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Enable in CMake:
|
||||||
|
|
||||||
|
```cmake pico
|
||||||
|
set(CS_MIDI_USB ON)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your project needs two additional files:
|
||||||
|
|
||||||
|
- `tusb_config.h` — TinyUSB configuration
|
||||||
|
- `usb_descriptors.c` — USB device/configuration/string descriptors
|
||||||
|
|
||||||
|
Reference copies are in `lib/cs-midi/templates/`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
USBMIDI_Interface midi;
|
||||||
|
|
||||||
|
NoteButton button {5, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
No `cyw43_arch_init()` needed for USB-only configurations.
|
||||||
|
|
||||||
|
## Combining with BLE
|
||||||
|
|
||||||
|
Enable both `CS_MIDI_BLE` and `CS_MIDI_USB`, then route between them:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
BluetoothMIDI_Interface ble;
|
||||||
|
USBMIDI_Interface usb;
|
||||||
|
BidirectionalMIDI_PipeFactory<1> pipes;
|
||||||
|
// In main: ble | pipes | usb;
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
# MIDI over BLE
|
||||||
|
|
||||||
|
> Original: [MIDI over BLE](https://tttapa.github.io/Control-Surface/Doxygen/db/d99/md_pages_MIDI-over-BLE.html)
|
||||||
|
|
||||||
|
BLE MIDI uses BTstack to advertise a standard MIDI BLE service. Compatible with macOS, iOS, Windows 10+, and Android.
|
||||||
|
|
||||||
|
## Backend
|
||||||
|
|
||||||
|
cs-midi uses the `BTstackBackgroundBackend`, which runs BLE asynchronously but requires periodic `update()` calls for outgoing message buffering.
|
||||||
|
|
||||||
|
The convenience typedef:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
using BluetoothMIDI_Interface = GenericBLEMIDI_Interface<BTstackBackgroundBackend>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
|
||||||
|
NoteButton button {5, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
if (cyw43_arch_init()) return 1;
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`cyw43_arch_init()` is required to bring up the radio before BLE operations.
|
||||||
|
|
||||||
|
## HID keyboard + Battery Service
|
||||||
|
|
||||||
|
Build with `CS_MIDI_HID_KEYBOARD=ON` to advertise as a BLE HID keyboard alongside MIDI:
|
||||||
|
|
||||||
|
1. **Auto-reconnect** — the host OS auto-reconnects to bonded HID peripherals on power-up, bringing up the MIDI service without manual intervention.
|
||||||
|
2. **Battery level** — the standard Battery Service (UUID 0x180F) reports charge level. Update with:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
setBLEBatteryLevel(percent);
|
||||||
|
```
|
||||||
|
|
||||||
|
No keyboard input is sent — the HID descriptor exists solely to trigger OS auto-connect behavior and surface the battery indicator.
|
||||||
|
|
||||||
|
## Radio module
|
||||||
|
|
||||||
|
The BLE transport requires a CYW43 radio. Only the **Infineon RM2 module** has been tested. A board file for the Waveshare RP2350B Plus W (RM2-equipped) is provided at `boards/waveshare_rp2350b_plus_w.h`.
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# MIDI Communication
|
||||||
|
|
||||||
|
Sending, receiving, and routing MIDI messages.
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Control Surface Singleton
|
||||||
|
|
||||||
|
> Original: [Purpose of the Control_Surface singleton](https://tttapa.github.io/Control-Surface/Doxygen/d8/df5/control-surface-purpose.html)
|
||||||
|
|
||||||
|
The `Control_Surface` object is the central manager for all MIDI elements, interfaces, and lifecycle events.
|
||||||
|
|
||||||
|
## Program structure
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include <cs_midi.h>
|
||||||
|
|
||||||
|
using namespace cs;
|
||||||
|
|
||||||
|
BluetoothMIDI_Interface midi;
|
||||||
|
NoteButton button {5, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
stdio_init_all();
|
||||||
|
Control_Surface.begin();
|
||||||
|
while (true) {
|
||||||
|
Control_Surface.loop();
|
||||||
|
sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
1. **Initialization** — `begin()` activates all registered MIDI interfaces, output elements, input elements, and selectors.
|
||||||
|
2. **Periodic updates** — `loop()` polls physical inputs (buttons, pots, encoders), sends MIDI when values change, and dispatches incoming MIDI to input elements.
|
||||||
|
3. **Message routing** — connects output elements to the default MIDI interface and delivers incoming messages to matching input elements.
|
||||||
|
|
||||||
|
## Element registration
|
||||||
|
|
||||||
|
Elements register themselves on construction and deregister on destruction. No explicit `add()` calls needed.
|
||||||
|
|
||||||
|
## Enable/disable
|
||||||
|
|
||||||
|
Elements can be removed from the update loop at runtime:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
button.disable(); // stops polling and sending
|
||||||
|
button.enable(); // re-adds to update loop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Without the singleton
|
||||||
|
|
||||||
|
For direct MIDI sending/receiving without the element system, use the interface object directly:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
midi.begin();
|
||||||
|
midi.sendNoteOn({MIDI_Notes::C[4], Channel_1}, 127);
|
||||||
|
midi.update();
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
# MIDI Output Elements
|
||||||
|
|
||||||
|
> Original: [MIDI output elements](https://tttapa.github.io/Control-Surface/Doxygen/d7/dcd/group__MIDIOutputElements.html)
|
||||||
|
|
||||||
|
Output elements read physical controls and send MIDI messages. They register with `Control_Surface` on construction and update automatically in the `loop()` cycle.
|
||||||
|
|
||||||
|
## Buttons
|
||||||
|
|
||||||
|
| Class | Description | Constructor |
|
||||||
|
|-------|-------------|-------------|
|
||||||
|
| `NoteButton` | Note On/Off on press/release | `{pin, address}` |
|
||||||
|
| `CCButton` | CC 127/0 on press/release | `{pin, address}` |
|
||||||
|
| `PCButton` | Program Change on press | `{pin, address}` |
|
||||||
|
| `NoteButtonLatched` | Toggle Note each press | `{pin, address}` |
|
||||||
|
| `NoteButtonLatching` | Note while held, toggles | `{pin, address}` |
|
||||||
|
| `CCButtonLatched` | Toggle CC each press | `{pin, address}` |
|
||||||
|
| `CCButtonLatching` | CC while held, toggles | `{pin, address}` |
|
||||||
|
| `NoteChordButton` | Multiple notes on press | `{pin, address, chord}` |
|
||||||
|
|
||||||
|
## Button arrays and matrices
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `NoteButtons<N>` | Array of N sequential note buttons |
|
||||||
|
| `CCButtons<N>` | Array of N sequential CC buttons |
|
||||||
|
| `NoteButtonMatrix<R,C>` | Row/column scanned note grid |
|
||||||
|
| `CCButtonMatrix<R,C>` | Row/column scanned CC grid |
|
||||||
|
|
||||||
|
## Potentiometers
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `CCPotentiometer` | Analog input → CC 0-127 |
|
||||||
|
| `PBPotentiometer` | Analog input → 14-bit Pitch Bend |
|
||||||
|
| `CCPotentiometer` with `map()` | Custom mapping to calibrate or apply curves |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
CCPotentiometer pot {26, {MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
pot.map(myMappingFunction);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Encoders
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `CCRotaryEncoder` | Relative CC from quadrature encoder |
|
||||||
|
| `CCAbsoluteEncoder` | Absolute CC position from encoder |
|
||||||
|
| `PBAbsoluteEncoder` | 14-bit Pitch Bend from encoder |
|
||||||
|
| `BorrowedCCRotaryEncoder` | Shared encoder reference |
|
||||||
|
| `CCIncrementDecrementButtons` | Two buttons for CC inc/dec |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
CCRotaryEncoder enc {{0, 1}, {MIDI_CC::Pan, Channel_1}, 1, 4};
|
||||||
|
```
|
||||||
|
|
||||||
|
Constructor: `{pin_pair, address, speed_multiplier, pulses_per_click}`.
|
||||||
|
|
||||||
|
## Program changers
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `ProgramChanger<N>` | Selectable list of N program changes |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
ProgramChanger<4> pc {{
|
||||||
|
{MIDI_PC::Harpsichord, Channel_1},
|
||||||
|
{MIDI_PC::Organ, Channel_1},
|
||||||
|
{MIDI_PC::Guitar, Channel_1},
|
||||||
|
{MIDI_PC::Piano, Channel_1},
|
||||||
|
}};
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# MIDI Input Elements
|
||||||
|
|
||||||
|
> Original: [MIDI input elements](https://tttapa.github.io/Control-Surface/Doxygen/df/d8b/group__MIDIInputElements.html)
|
||||||
|
|
||||||
|
Input elements receive MIDI messages and update stored values or drive hardware outputs.
|
||||||
|
|
||||||
|
## Value readers
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `NoteValue` | Incoming Note velocity (8-bit) |
|
||||||
|
| `CCValue` | Incoming CC value (8-bit) |
|
||||||
|
| `KPValue` | Incoming Key Pressure (8-bit) |
|
||||||
|
| `PBValue` | Incoming Pitch Bend (14-bit) |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
CCValue vol {{MIDI_CC::Channel_Volume, Channel_1}};
|
||||||
|
// In loop: uint8_t v = vol.getValue();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Range readers
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `NoteRange<N>` | Array of N consecutive note values |
|
||||||
|
| `CCRange<N>` | Array of N consecutive CC values |
|
||||||
|
| `KPRange<N>` | Array of N consecutive key pressure values |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
CCRange<8> faders {{16, Channel_1}};
|
||||||
|
// faders.getValue(0) through faders.getValue(7)
|
||||||
|
```
|
||||||
|
|
||||||
|
## LED outputs
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `NoteLED` | GPIO high on Note On, low on Note Off |
|
||||||
|
| `CCLED` | GPIO high when CC > 63 |
|
||||||
|
| `KPLED` | GPIO high when Key Pressure > 63 |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
NoteLED led {25, {MIDI_Notes::C[4], Channel_1}};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bankable variants
|
||||||
|
|
||||||
|
All input elements have bankable versions under `Bankable::`:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
Bank<4> bank(2);
|
||||||
|
Bankable::CCValue<4> bankCC {{bank, BankType::ChangeAddress}, {16, Channel_1}};
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Banks and Selectors](../06-banks/) for details.
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Banks and Selectors
|
||||||
|
|
||||||
|
> Original: [Bank support](https://tttapa.github.io/Control-Surface/Doxygen/db/dbd/classBank.html)
|
||||||
|
|
||||||
|
Banks allow a single physical control to address multiple MIDI parameters by switching the active bank.
|
||||||
|
|
||||||
|
## How banks work
|
||||||
|
|
||||||
|
`Bank<N>` manages N settings. Bankable elements compute their effective address as:
|
||||||
|
|
||||||
|
effective_address = base_address + (bank_setting × offset)
|
||||||
|
|
||||||
|
The offset is set when constructing the bank, and the base address is set per-element.
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
Bank<4> bank(2); // 4 settings, offset 2 between each
|
||||||
|
```
|
||||||
|
|
||||||
|
With base address 16, the four banks produce addresses 16, 18, 20, 22.
|
||||||
|
|
||||||
|
## Bank types
|
||||||
|
|
||||||
|
- `BankType::ChangeAddress` — shifts the CC/note number
|
||||||
|
- `BankType::ChangeChannel` — shifts the MIDI channel
|
||||||
|
|
||||||
|
## Selectors
|
||||||
|
|
||||||
|
Selectors control which bank setting is active:
|
||||||
|
|
||||||
|
| Class | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `EncoderSelector<N>` | Rotary encoder cycles banks |
|
||||||
|
| `IncrementDecrementSelector<N>` | Two buttons: up/down |
|
||||||
|
| `IncrementSelector<N>` | Single button, wraps around |
|
||||||
|
| `ManyButtonsSelector<N>` | One button per bank |
|
||||||
|
| `SwitchSelector` | Two-position toggle |
|
||||||
|
| `ProgramChangeSelector<N>` | Incoming MIDI PC selects bank |
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
IncrementDecrementSelector<4> selector {bank, {2, 3}, Wrap::Wrap};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transposer
|
||||||
|
|
||||||
|
`Transposer<Min, Max>` is a specialized bank that offsets note numbers:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
Transposer<-12, 12> transposer;
|
||||||
|
IncrementDecrementSelector<25> transposeSelect {transposer, {2, 3}, Wrap::Clamp};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bankable input elements
|
||||||
|
|
||||||
|
All input elements (`CCValue`, `NoteLED`, etc.) have bankable variants:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
Bankable::CCValue<4> cc {{bank, BankType::ChangeAddress}, {16, Channel_1}};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bankable output elements
|
||||||
|
|
||||||
|
Bankable output elements (`Bankable::NoteButton`, `Bankable::CCPotentiometer`, etc.) are not yet implemented (Phase 11). Bank switching currently works with input elements and selectors only.
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Hardware Support
|
||||||
|
|
||||||
|
> Original: [Hardware support](https://tttapa.github.io/Control-Surface/Doxygen/db/dd3/group__AH__ExtIO.html)
|
||||||
|
|
||||||
|
Hardware utilities in the `AH::` namespace for debouncing, filtering, and encoder reading.
|
||||||
|
|
||||||
|
## Button
|
||||||
|
|
||||||
|
Debounced button with state machine:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
AH::Button btn {5};
|
||||||
|
btn.begin();
|
||||||
|
|
||||||
|
// In loop:
|
||||||
|
btn.update();
|
||||||
|
if (btn.getState() == AH::Button::Falling)
|
||||||
|
// pressed
|
||||||
|
if (btn.getState() == AH::Button::Rising)
|
||||||
|
// released
|
||||||
|
```
|
||||||
|
|
||||||
|
States: `Pressed`, `Released`, `Falling` (just pressed), `Rising` (just released).
|
||||||
|
|
||||||
|
## FilteredAnalog
|
||||||
|
|
||||||
|
EMA-filtered analog input with hysteresis to reduce noise:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
AH::FilteredAnalog<10> pot {26}; // 10-bit, GPIO 26
|
||||||
|
|
||||||
|
// In loop:
|
||||||
|
if (pot.update())
|
||||||
|
uint16_t value = pot.getValue();
|
||||||
|
```
|
||||||
|
|
||||||
|
## AHEncoder
|
||||||
|
|
||||||
|
Interrupt-driven quadrature rotary encoder:
|
||||||
|
|
||||||
|
```cpp pico
|
||||||
|
AH::AHEncoder enc {0, 1}; // pin A, pin B
|
||||||
|
enc.begin();
|
||||||
|
|
||||||
|
// In loop:
|
||||||
|
int32_t delta = enc.read();
|
||||||
|
enc.write(0); // reset position
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPIO on RP2xxx
|
||||||
|
|
||||||
|
cs-midi uses pico-sdk GPIO directly. Pin numbers are GPIO numbers (0-29 on RP2040, 0-47 on RP2350).
|
||||||
|
|
||||||
|
- Buttons use internal pull-ups (active low)
|
||||||
|
- ADC pins: GPIO 26-29 (ADC0-ADC3)
|
||||||
|
- Encoder pins: any GPIO with interrupt capability (all pins on RP2xxx)
|
||||||
|
|
||||||
|
## Extended I/O
|
||||||
|
|
||||||
|
Hardware expanders (MCP23017, MAX7219, shift registers) are not yet ported from Control Surface. Use direct GPIO pins for all inputs and outputs.
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Manual
|
||||||
|
|
||||||
|
A translated reference of the [Control Surface manual](https://tttapa.github.io/Control-Surface/Doxygen/d3/da5/manual.html) for cs-midi on pico-sdk.
|
||||||
|
|
||||||
|
Each section mirrors the original structure. Code examples are adapted for the target platform selected in the sidebar.
|
||||||
|
|
@ -55,6 +55,7 @@ Extraction status of [tttapa/Control-Surface](https://github.com/tttapa/Control-
|
||||||
- [x] `ExtendedInputOutput` — GPIO abstraction (direct pins)
|
- [x] `ExtendedInputOutput` — GPIO abstraction (direct pins)
|
||||||
- [x] MIDI constants (notes, CC numbers, PC programs, chords/intervals)
|
- [x] MIDI constants (notes, CC numbers, PC programs, chords/intervals)
|
||||||
- [x] Platform shim (`pico_shim.h`) — replaces Arduino runtime
|
- [x] Platform shim (`pico_shim.h`) — replaces Arduino runtime
|
||||||
|
- [x] HID over GATT (HoG) keyboard + Battery Service — BLE auto-reconnect and battery level via `CS_MIDI_HID_KEYBOARD`
|
||||||
|
|
||||||
## Not Yet Implemented
|
## Not Yet Implemented
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
|
|
@ -10,14 +13,20 @@ import (
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
var md goldmark.Markdown
|
var (
|
||||||
|
md goldmark.Markdown
|
||||||
|
platformRe = regexp.MustCompile("^```(\\w+)\\s+(pico|esp32s3|esp32c3)\\s*$")
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
md = goldmark.New(
|
md = goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.GFM,
|
extension.GFM,
|
||||||
highlighting.NewHighlighting(
|
highlighting.NewHighlighting(
|
||||||
highlighting.WithStyle("monokai"),
|
highlighting.WithFormatOptions(
|
||||||
|
chromahtml.WithClasses(true),
|
||||||
|
chromahtml.ClassPrefix("hl-"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
goldmark.WithParserOptions(
|
goldmark.WithParserOptions(
|
||||||
|
|
@ -29,7 +38,49 @@ func init() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preprocessPlatformBlocks rewrites fenced code blocks tagged with a platform
|
||||||
|
// (e.g. ```cpp pico) into an HTML wrapper div + untagged fence so Goldmark
|
||||||
|
// renders them normally while preserving the data-platform attribute.
|
||||||
|
func preprocessPlatformBlocks(src []byte) []byte {
|
||||||
|
lines := bytes.Split(src, []byte("\n"))
|
||||||
|
var out [][]byte
|
||||||
|
inFence := false
|
||||||
|
platformFence := false
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
trimmed := bytes.TrimSpace(line)
|
||||||
|
|
||||||
|
if !inFence {
|
||||||
|
if m := platformRe.FindSubmatch(trimmed); m != nil {
|
||||||
|
out = append(out, []byte(fmt.Sprintf(`<div data-platform="%s">`, m[2])))
|
||||||
|
out = append(out, []byte{})
|
||||||
|
out = append(out, []byte("```"+string(m[1])))
|
||||||
|
inFence = true
|
||||||
|
platformFence = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bytes.HasPrefix(trimmed, []byte("```")) {
|
||||||
|
inFence = true
|
||||||
|
}
|
||||||
|
} else if bytes.Equal(trimmed, []byte("```")) {
|
||||||
|
inFence = false
|
||||||
|
if platformFence {
|
||||||
|
out = append(out, line)
|
||||||
|
out = append(out, []byte{})
|
||||||
|
out = append(out, []byte("</div>"))
|
||||||
|
platformFence = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.Join(out, []byte("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
func Markdown(src []byte) ([]byte, error) {
|
func Markdown(src []byte) ([]byte, error) {
|
||||||
|
src = preprocessPlatformBlocks(src)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := md.Convert(src, &buf); err != nil {
|
if err := md.Convert(src, &buf); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var input = document.querySelector('.search-form input');
|
||||||
|
if (input) {
|
||||||
|
input.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
input.value = '';
|
||||||
|
input.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Platform selector ---
|
||||||
|
var platformSel = document.getElementById('platform-select');
|
||||||
|
if (platformSel) {
|
||||||
|
var storedPlatform = localStorage.getItem('cs-midi-platform');
|
||||||
|
if (storedPlatform) platformSel.value = storedPlatform;
|
||||||
|
|
||||||
|
function applyPlatform() {
|
||||||
|
var platform = platformSel.value;
|
||||||
|
localStorage.setItem('cs-midi-platform', platform);
|
||||||
|
|
||||||
|
var blocks = document.querySelectorAll('[data-platform]');
|
||||||
|
var anyVisible = false;
|
||||||
|
|
||||||
|
blocks.forEach(function(el) {
|
||||||
|
if (el.getAttribute('data-platform') === platform) {
|
||||||
|
el.style.display = '';
|
||||||
|
anyVisible = true;
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var notice = document.getElementById('platform-notice');
|
||||||
|
if (notice) {
|
||||||
|
notice.style.display = (blocks.length > 0 && !anyVisible) ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
platformSel.addEventListener('change', applyPlatform);
|
||||||
|
applyPlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Theme selector ---
|
||||||
|
var themeSel = document.getElementById('theme-select');
|
||||||
|
if (themeSel) {
|
||||||
|
var storedTheme = localStorage.getItem('cs-midi-theme') || 'auto';
|
||||||
|
themeSel.value = storedTheme;
|
||||||
|
|
||||||
|
function applyTheme() {
|
||||||
|
var theme = themeSel.value;
|
||||||
|
localStorage.setItem('cs-midi-theme', theme);
|
||||||
|
|
||||||
|
if (theme === 'auto') {
|
||||||
|
document.documentElement.removeAttribute('data-theme');
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
themeSel.addEventListener('change', applyTheme);
|
||||||
|
applyTheme();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var input = document.querySelector('.search-form input');
|
|
||||||
if (!input) return;
|
|
||||||
input.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
input.value = '';
|
|
||||||
input.blur();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -7,17 +7,29 @@
|
||||||
--sidebar-w: 280px;
|
--sidebar-w: 280px;
|
||||||
--accent: #2563eb;
|
--accent: #2563eb;
|
||||||
--border: #ddd;
|
--border: #ddd;
|
||||||
--code-bg: #f5f5f5;
|
--code-bg: #f6f8fa;
|
||||||
|
--code-fg: #24292e;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg: #1a1a2e;
|
||||||
|
--fg: #e0e0e0;
|
||||||
|
--sidebar-bg: #16213e;
|
||||||
|
--accent: #60a5fa;
|
||||||
|
--border: #333;
|
||||||
|
--code-bg: #0d1117;
|
||||||
|
--code-fg: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root:not([data-theme="light"]) {
|
||||||
--bg: #1a1a2e;
|
--bg: #1a1a2e;
|
||||||
--fg: #e0e0e0;
|
--fg: #e0e0e0;
|
||||||
--sidebar-bg: #16213e;
|
--sidebar-bg: #16213e;
|
||||||
--accent: #60a5fa;
|
--accent: #60a5fa;
|
||||||
--border: #333;
|
--border: #333;
|
||||||
--code-bg: #0f3460;
|
--code-bg: #0d1117;
|
||||||
|
--code-fg: #e0e0e0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +125,46 @@ body {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-controls {
|
||||||
|
padding: 0.8em 1em;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
gap: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-controls > div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-controls label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-controls select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-notice {
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-style: italic;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
|
|
@ -139,11 +191,14 @@ body {
|
||||||
.content p { margin: 0.8em 0; }
|
.content p { margin: 0.8em 0; }
|
||||||
.content ul, .content ol { margin: 0.8em 0; padding-left: 2em; }
|
.content ul, .content ol { margin: 0.8em 0; padding-left: 2em; }
|
||||||
.content li { margin: 0.3em 0; }
|
.content li { margin: 0.3em 0; }
|
||||||
|
.content blockquote { border-left: 3px solid var(--border); padding-left: 1em; margin: 0.8em 0; opacity: 0.8; }
|
||||||
|
|
||||||
.content pre {
|
.content pre {
|
||||||
background: var(--code-bg);
|
background: var(--code-bg);
|
||||||
|
color: var(--code-fg);
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
@ -154,7 +209,7 @@ body {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content p code, .content li code {
|
.content p code, .content li code, .content td code {
|
||||||
background: var(--code-bg);
|
background: var(--code-bg);
|
||||||
padding: 0.15em 0.4em;
|
padding: 0.15em 0.4em;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
@ -205,3 +260,187 @@ body {
|
||||||
.content { margin-left: 0; padding: 1.5em; }
|
.content { margin-left: 0; padding: 1.5em; }
|
||||||
.layout { flex-direction: column; }
|
.layout { flex-direction: column; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Syntax highlighting — light theme (github) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
.hl-chroma { background-color: transparent; }
|
||||||
|
.hl-chroma .hl-err { color: #a61717; background-color: #e3d2d2 }
|
||||||
|
.hl-chroma .hl-k { color: #0550ae; font-weight: bold }
|
||||||
|
.hl-chroma .hl-kc { color: #0550ae; font-weight: bold }
|
||||||
|
.hl-chroma .hl-kd { color: #0550ae; font-weight: bold }
|
||||||
|
.hl-chroma .hl-kn { color: #0550ae; font-weight: bold }
|
||||||
|
.hl-chroma .hl-kp { color: #0550ae; font-weight: bold }
|
||||||
|
.hl-chroma .hl-kr { color: #0550ae; font-weight: bold }
|
||||||
|
.hl-chroma .hl-kt { color: #445588; font-weight: bold }
|
||||||
|
.hl-chroma .hl-na { color: #008080 }
|
||||||
|
.hl-chroma .hl-nb { color: #0086b3 }
|
||||||
|
.hl-chroma .hl-bp { color: #999999 }
|
||||||
|
.hl-chroma .hl-nc { color: #445588; font-weight: bold }
|
||||||
|
.hl-chroma .hl-no { color: #008080 }
|
||||||
|
.hl-chroma .hl-nd { color: #3c5d5d; font-weight: bold }
|
||||||
|
.hl-chroma .hl-ni { color: #800080 }
|
||||||
|
.hl-chroma .hl-ne { color: #990000; font-weight: bold }
|
||||||
|
.hl-chroma .hl-nf { color: #6f42c1 }
|
||||||
|
.hl-chroma .hl-nl { color: #990000; font-weight: bold }
|
||||||
|
.hl-chroma .hl-nn { color: #555555 }
|
||||||
|
.hl-chroma .hl-nt { color: #22863a }
|
||||||
|
.hl-chroma .hl-nv { color: #008080 }
|
||||||
|
.hl-chroma .hl-vc { color: #008080 }
|
||||||
|
.hl-chroma .hl-vg { color: #008080 }
|
||||||
|
.hl-chroma .hl-vi { color: #008080 }
|
||||||
|
.hl-chroma .hl-s { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sa { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sb { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sc { color: #032f62 }
|
||||||
|
.hl-chroma .hl-dl { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sd { color: #032f62 }
|
||||||
|
.hl-chroma .hl-s2 { color: #032f62 }
|
||||||
|
.hl-chroma .hl-se { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sh { color: #032f62 }
|
||||||
|
.hl-chroma .hl-si { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sx { color: #032f62 }
|
||||||
|
.hl-chroma .hl-sr { color: #009926 }
|
||||||
|
.hl-chroma .hl-s1 { color: #032f62 }
|
||||||
|
.hl-chroma .hl-ss { color: #990073 }
|
||||||
|
.hl-chroma .hl-m { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-mb { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-mf { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-mh { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-mi { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-il { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-mo { color: #005cc5 }
|
||||||
|
.hl-chroma .hl-o { color: #d73a49 }
|
||||||
|
.hl-chroma .hl-ow { color: #d73a49 }
|
||||||
|
.hl-chroma .hl-c { color: #6a737d; font-style: italic }
|
||||||
|
.hl-chroma .hl-ch { color: #6a737d; font-style: italic }
|
||||||
|
.hl-chroma .hl-cm { color: #6a737d; font-style: italic }
|
||||||
|
.hl-chroma .hl-c1 { color: #6a737d; font-style: italic }
|
||||||
|
.hl-chroma .hl-cs { color: #6a737d; font-weight: bold; font-style: italic }
|
||||||
|
.hl-chroma .hl-cp { color: #6a737d; font-weight: bold; font-style: italic }
|
||||||
|
.hl-chroma .hl-cpf { color: #6a737d; font-weight: bold; font-style: italic }
|
||||||
|
.hl-chroma .hl-gd { color: #b31d28; background-color: #ffeef0 }
|
||||||
|
.hl-chroma .hl-gi { color: #22863a; background-color: #f0fff4 }
|
||||||
|
.hl-chroma .hl-ge { font-style: italic }
|
||||||
|
.hl-chroma .hl-gs { font-weight: bold }
|
||||||
|
.hl-chroma .hl-gh { color: #999999 }
|
||||||
|
.hl-chroma .hl-gu { color: #aaaaaa }
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Syntax highlighting — dark theme (monokai) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-err { color: #960050; background-color: #1e0010 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-k { color: #66d9ef; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-kc { color: #66d9ef; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-kd { color: #66d9ef; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-kn { color: #f92672; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-kp { color: #66d9ef; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-kr { color: #66d9ef; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-kt { color: #66d9ef; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-na { color: #a6e22e }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nb { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-bp { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nc { color: #a6e22e; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-no { color: #66d9ef }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nd { color: #a6e22e; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-ni { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-ne { color: #a6e22e; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nf { color: #a6e22e }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nl { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nn { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nt { color: #f92672 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-nv { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-vc { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-vg { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-vi { color: #f8f8f2 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-s { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sa { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sb { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sc { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-dl { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sd { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-s2 { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-se { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sh { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-si { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sx { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-sr { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-s1 { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-ss { color: #e6db74 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-m { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-mb { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-mf { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-mh { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-mi { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-il { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-mo { color: #ae81ff }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-o { color: #f92672; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-ow { color: #f92672; font-weight: normal }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-c { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-ch { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-cm { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-c1 { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-cs { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-cp { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-cpf { color: #75715e; font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-gd { color: #f92672 }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-gi { color: #a6e22e }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-ge { font-style: italic }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-gs { font-weight: bold }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-gh { color: #75715e }
|
||||||
|
[data-theme="dark"] .hl-chroma .hl-gu { color: #75715e }
|
||||||
|
|
||||||
|
/* Dark syntax when auto-detected via media query */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-err { color: #960050; background-color: #1e0010 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-k { color: #66d9ef; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-kc { color: #66d9ef; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-kd { color: #66d9ef; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-kn { color: #f92672; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-kp { color: #66d9ef; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-kr { color: #66d9ef; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-kt { color: #66d9ef; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-na { color: #a6e22e }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nb { color: #f8f8f2 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-bp { color: #f8f8f2 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nc { color: #a6e22e; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-no { color: #66d9ef }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nd { color: #a6e22e; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-ne { color: #a6e22e; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nf { color: #a6e22e }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nn { color: #f8f8f2 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nt { color: #f92672 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-nv { color: #f8f8f2 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-s { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sa { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sb { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sc { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-dl { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sd { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-s2 { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-se { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sh { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-si { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sx { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-sr { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-s1 { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-ss { color: #e6db74 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-m { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-mb { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-mf { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-mh { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-mi { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-il { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-mo { color: #ae81ff }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-o { color: #f92672; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-ow { color: #f92672; font-weight: normal }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-c { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-ch { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-cm { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-c1 { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-cs { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-cp { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-cpf { color: #75715e; font-style: italic }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-gd { color: #f92672 }
|
||||||
|
:root:not([data-theme="light"]) .hl-chroma .hl-gi { color: #a6e22e }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,24 @@
|
||||||
<input type="search" name="q" placeholder="Search...">
|
<input type="search" name="q" placeholder="Search...">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sidebar-controls">
|
||||||
|
<div>
|
||||||
|
<label for="platform-select">Platform</label>
|
||||||
|
<select id="platform-select">
|
||||||
|
<option value="pico">RP2xxx (pico-sdk)</option>
|
||||||
|
<option value="esp32s3">ESP32-S3</option>
|
||||||
|
<option value="esp32c3">ESP32-C3</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="theme-select">Theme</label>
|
||||||
|
<select id="theme-select">
|
||||||
|
<option value="auto">Auto</option>
|
||||||
|
<option value="light">Light</option>
|
||||||
|
<option value="dark">Dark</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{.Nav}}
|
{{.Nav}}
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<a href="/dl/book.md">Download .md</a>
|
<a href="/dl/book.md">Download .md</a>
|
||||||
|
|
@ -25,6 +43,6 @@
|
||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/search.js"></script>
|
<script src="/static/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<article>
|
<article>
|
||||||
{{.Body}}
|
{{.Body}}
|
||||||
|
<p id="platform-notice" class="platform-notice" style="display:none">
|
||||||
|
No examples available for this platform yet.
|
||||||
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<nav class="page-nav">
|
<nav class="page-nav">
|
||||||
{{if .PrevPath}}<a href="{{.PrevPath}}" class="prev">← {{.PrevTitle}}</a>{{end}}
|
{{if .PrevPath}}<a href="{{.PrevPath}}" class="prev">← {{.PrevTitle}}</a>{{end}}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue