Added more examples, etc
This commit is contained in:
parent
3927d073e9
commit
be1ee30967
|
|
@ -1,12 +1,33 @@
|
|||
# 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
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.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:
|
||||
|
||||
```cmake
|
||||
```cmake pico
|
||||
add_subdirectory(lib/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_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.
|
||||
|
||||
|
|
@ -58,6 +80,8 @@ cd lib/cs-midi && make tests
|
|||
- Standard `main()` replaces Arduino `setup()`/`loop()`
|
||||
- All types live in the `cs::` namespace
|
||||
- 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
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Sends Note On/Off on button press/release.
|
|||
|
||||
> Original: `NoteButton.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Sends CC 127 on press, CC 0 on release.
|
|||
|
||||
> Original: `CCButton.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Sends Program Change on button press.
|
|||
|
||||
> Original: `PCButton.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Plays a chord on button press.
|
|||
|
||||
> Original: `NoteChordButton.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
First press sends Note On, second press sends Note Off.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Note On while held, Note Off on release; toggles state.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Multiple note buttons with sequential addresses.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Grid of note buttons using row/column scanning.
|
|||
|
||||
> Original: `NoteButtonMatrix.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
First press sends CC 127, second press sends CC 0.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
CC 127 while held, CC 0 on release; toggles state.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Multiple CC buttons with sequential addresses.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Grid of CC buttons using row/column scanning.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Analog input mapped to CC value 0-127.
|
|||
|
||||
> Original: `Control-Change-Potentiometer.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Analog input mapped to 14-bit Pitch Bend.
|
|||
|
||||
> Original: `Pitch-Bend-Potentiometer.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Relative CC encoder for DAW parameter control.
|
|||
|
||||
> Original: `RotaryEncoder.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Encoder position mapped to absolute CC value.
|
|||
|
||||
> Original: `AbsoluteRotaryEncoder.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Encoder position mapped to 14-bit Pitch Bend.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Two buttons for CC increment/decrement with optional reset.
|
|||
|
||||
> Original: `CCIncrementDecrementButtons.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Sends different program changes based on selector state.
|
|||
|
||||
> Original: `Program-Changer.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Several analog inputs mapped to CC on different channels.
|
|||
|
||||
> Original: `Multiple-Control-Change-Potentiometers.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Shared encoder reference for CC output, allowing multiple elements to read the same physical encoder.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.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`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Multiple LEDs respond to a range of incoming notes.
|
|||
|
||||
> Original: `2.Note-Range-LEDs.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Reads incoming CC messages and stores the value.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Reads incoming Pitch Bend messages (14-bit).
|
|||
|
||||
> Original: `Pitch-Bend-Value.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Reads incoming Note On values (8-bit).
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Reads incoming Key Pressure (aftertouch) values.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
LED responds to incoming CC messages.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Reads a contiguous range of incoming Note values.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Reads a contiguous range of incoming CC values.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,21 @@
|
|||
|
||||
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/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Routing between MIDI interfaces using pipe operators.
|
|||
|
||||
> Original: `MIDI_Pipes-Routing.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.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.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "tusb.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)`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include <cs_midi.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Multiple MIDI interfaces with pipe routing. Elements send to both transports simultaneously.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.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`.
|
||||
|
||||
```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/cyw43_arch.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`.
|
||||
|
||||
```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/cyw43_arch.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)
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Note transposition via increment/decrement buttons.
|
|||
|
||||
> Original: `Transposer.ino`
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Bank selection via rotary encoder.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
One button per bank for direct bank selection.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Two-state bank selection from a toggle switch.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Bank selection triggered by incoming MIDI Program Change messages.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Bank-switched LED responds to different notes per bank.
|
||||
|
||||
```cpp
|
||||
```cpp pico
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include <cs_midi.h>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
# 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] MIDI constants (notes, CC numbers, PC programs, chords/intervals)
|
||||
- [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
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package render
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/yuin/goldmark"
|
||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
|
|
@ -10,14 +13,20 @@ import (
|
|||
"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() {
|
||||
md = goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
extension.GFM,
|
||||
highlighting.NewHighlighting(
|
||||
highlighting.WithStyle("monokai"),
|
||||
highlighting.WithFormatOptions(
|
||||
chromahtml.WithClasses(true),
|
||||
chromahtml.ClassPrefix("hl-"),
|
||||
),
|
||||
),
|
||||
),
|
||||
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) {
|
||||
src = preprocessPlatformBlocks(src)
|
||||
var buf bytes.Buffer
|
||||
if err := md.Convert(src, &buf); err != nil {
|
||||
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;
|
||||
--accent: #2563eb;
|
||||
--border: #ddd;
|
||||
--code-bg: #f5f5f5;
|
||||
--code-bg: #f6f8fa;
|
||||
--code-fg: #24292e;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
[data-theme="dark"] {
|
||||
--bg: #1a1a2e;
|
||||
--fg: #e0e0e0;
|
||||
--sidebar-bg: #16213e;
|
||||
--accent: #60a5fa;
|
||||
--border: #333;
|
||||
--code-bg: #0f3460;
|
||||
--code-bg: #0d1117;
|
||||
--code-fg: #e0e0e0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #1a1a2e;
|
||||
--fg: #e0e0e0;
|
||||
--sidebar-bg: #16213e;
|
||||
--accent: #60a5fa;
|
||||
--border: #333;
|
||||
--code-bg: #0d1117;
|
||||
--code-fg: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +125,46 @@ body {
|
|||
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 {
|
||||
padding: 1em;
|
||||
border-top: 1px solid var(--border);
|
||||
|
|
@ -139,11 +191,14 @@ body {
|
|||
.content p { margin: 0.8em 0; }
|
||||
.content ul, .content ol { margin: 0.8em 0; padding-left: 2em; }
|
||||
.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 {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-fg);
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
overflow-x: auto;
|
||||
margin: 1em 0;
|
||||
font-size: 0.9em;
|
||||
|
|
@ -154,7 +209,7 @@ body {
|
|||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.content p code, .content li code {
|
||||
.content p code, .content li code, .content td code {
|
||||
background: var(--code-bg);
|
||||
padding: 0.15em 0.4em;
|
||||
border-radius: 3px;
|
||||
|
|
@ -205,3 +260,187 @@ body {
|
|||
.content { margin-left: 0; padding: 1.5em; }
|
||||
.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...">
|
||||
</form>
|
||||
</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}}
|
||||
<div class="sidebar-footer">
|
||||
<a href="/dl/book.md">Download .md</a>
|
||||
|
|
@ -25,6 +43,6 @@
|
|||
{{template "content" .}}
|
||||
</main>
|
||||
</div>
|
||||
<script src="/static/search.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
{{define "content"}}
|
||||
<article>
|
||||
{{.Body}}
|
||||
<p id="platform-notice" class="platform-notice" style="display:none">
|
||||
No examples available for this platform yet.
|
||||
</p>
|
||||
</article>
|
||||
<nav class="page-nav">
|
||||
{{if .PrevPath}}<a href="{{.PrevPath}}" class="prev">← {{.PrevTitle}}</a>{{end}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue