Music theory engine for embedded systems. Build musical instruments and controllers that interact with people and speak MIDI 1.0 and 2.0.
- πͺ Gingo[duino]
Gingoduino is a music theory engine for embedded systems. It owns the musical domain (notes, intervals, chords, scales, harmonic fields, harmonic trees, progression analysis, fretboard engine), a real-time harmonic monitor, a live UMP stream interpreter (GingoFlow), and stateless output adapters that translate musical structures into serialized formats (raw MIDI 1.0 bytes and MIDI 2.0 UMP Flex Data).
It does not own the wire. Byte stream parsing, UMP receive dispatch, and MIDI-CI discovery and Property Exchange are protocol concerns delegated to dedicated libraries: midi2cpp for UMP, Arduino MIDI Library or your own parser for MIDI 1.0 byte streams, the midi2 C99 library for MIDI-CI responders.
Zero-heap architecture, PROGMEM lookup tables, C++11 compatible.
0.4.0 is a breaking release. See CHANGELOG.md for the migration guide if you are upgrading from 0.3.x.
Gingoduino is one of three companion libraries with distinct, complementary roles.
| Library | Role | Language | Speaks |
|---|---|---|---|
midi2 |
Infrastructure: UMP wire, parsing, dispatch, MIDI-CI | C99 | MIDI 2.0 |
midi2cpp |
Platform: USB MIDI 2.0 device, host, bridge | C++17 | MIDI 2.0 |
| gingoduino | Music theory engine. Notes, intervals, chords, scales, fields, harmonic monitor, output adapters | C++11 | MIDI 1.0 and 2.0 |
βββββββββββββββββββββββββββββββββββββββββββββββ
β Application β
βββββββββββββββββββββββββββββββββββββββββββββββ
β β
β uses for theory β uses for transport
βΌ βΌ
ββββββββββββββββββββββ ββββββββββββββββββββββββ
β gingoduino β β midi2cpp β
β Music theory β β USB MIDI 2.0 β
β Notes Β· Chords β β Device Β· Host β
β Scales Β· Fields β β Bridge Β· CI β
β MIDI 1.0 + 2.0 β ββββββββββββ¬ββββββββββββ
β output adapters β β uses
ββββββββββββββββββββββ βΌ
ββββββββββββββββββββββββ
β midi2 β
β UMP wire Β· MIDI-CI β
β Parsing Β· Dispatch β
ββββββββββββββββββββββββ
Gingoduino is the only one of the three with a foot in MIDI 1.0. GingoMIDI1 serializes musical structures to raw MIDI 1.0 bytes (DIN, USB MIDI 1.0, virtual ports); GingoMIDI2 serializes the same structures to MIDI 2.0 UMP Flex Data, ready to feed into midi2cpp or any UMP transport.
βββββββββββββββββββ musical event ββββββββββββββββββββ
β Transport β βββββββββββββββββββΆ β GingoMonitor β
β (midi2cpp, β noteOn / noteOff / β chord, field, β
β Arduino MIDI, β sustainOn / Off β per-note ctx β
β ESP32_Host_ β ββββββββββ¬ββββββββββ
β MIDI, ...) β β
βββββββββββββββββββ β
β² βΌ
β βββββββββββββββββββββββββββββββββββ
β β Theory + Analysis β
β β Note Β· Interval Β· Chord Β· β
β β Scale Β· Field Β· Tree Β· β
β β Progression Β· Comparison Β· β
β β Fretboard Β· Event Β· Sequence β
β βββββββββββββββββββ¬βββββββββββββββββ
β β
β βΌ
β βββββββββββββββββββββββββββββββββββ
β β Output adapters β
β bytes / β GingoMIDI1::fromEvent β
ββββββββββββββ€ GingoMIDI1::fromSequence β
UMP β GingoMIDI2::chordName β
β GingoMIDI2::keySignature β
β GingoMIDI2::perNoteController β
βββββββββββββββββββββββββββββββββββ
The transport sits outside the library. It parses bytes or UMP packets and feeds the Monitor through the musical-event API. Anything Gingoduino computes can be turned back into bytes or UMP through the namespaced output adapters.
Theory:
GingoNote: chromatic note, frequency, MIDI number, transpositionGingoInterval: labeled intervals, consonance, full nameGingoChord: 42 chord formulas, identification, transpositionGingoScale: scale types, modes, signature, brightness,formalNotes(diatonic spelling)GingoField: harmonic field, T/S/D functions, role,relative/parallel,branchOf,appliedChords(secondary dominants),compare(21-dimGingoFieldComparison)GingoTree: harmonic graph (classical and jazz traditions)GingoProgression: identify, deduce, predictGingoChordComparison: 17 dimensions, Neo-Riemannian, Forte vectorsGingoFretboard: guitar, violao, cavaquinho, mandolim/bandolim, ukulele; alternate tunings
Time and events:
GingoDuration(rational,operator+,operator*),GingoTempo,GingoTimeSigGingoEvent,GingoSequence
Real-time analysis:
GingoMonitor: chord detection, field deduction, per-note context,onPredictionwith rolling branch historyGingoFlow: live UMP stream interpreter; non-blocking capture, a logical event stream with note pairing, chord grouping and durations, an opt-in raw-UMP forward face, and a calibratable chord-grouping rule
Output adapters (stateless):
GingoMIDI1::fromEvent,GingoMIDI1::fromSequence: MIDI 1.0 bytesGingoMIDI2::chordName,keySignature,perNoteController: MIDI 2.0 UMP Flex Data
Portability:
Gingoduino is portable C++11 with zero heap and PROGMEM lookup tables. It does not depend on any board API and should build on any toolchain that provides standard C++11. The families that ship in library.properties (esp32, esp8266, samd, nrf52, rp2040, mbed_rp2040, renesas_uno, renesas_portenta, stm32, teensy) are the targets we exercise; other Cortex-M / RISC-V / desktop targets compile cleanly too. See Hardware validation for the boards we ran the on-device proof on.
A couple of practical notes:
<functional>is included only when the toolchain ships it. AVR (avr-libc) does not, so thestd::functionlambda-with-capture overloads onGingoMonitorare unavailable on AVR; the function-pointer overloads still work.- AVR (Uno, Nano, Leonardo, etc., 32 KB Flash / 2-2.5 KB SRAM) cannot fit the full library together with the Field/Monitor/Progression pipeline. The practical AVR scope is
GingoNote,GingoInterval,GingoChord,GingoScale(seeexamples/Foundation_AVR).
- 12-note chromatic system with enharmonic equivalents
- 42 chord formulas with reverse lookup (identify)
- 40+ scale types and modes with signature, brightness, relative and parallel
- Harmonic field analysis with T/S/D functions and roles, plus deduction from notes and chords
- Harmonic tree (directed graph, major and minor, classical and jazz traditions)
- Progression analysis: identify, deduce (ranked), predict (next branch)
- Fretboard engine: guitar, violao, cavaquinho, mandolim, ukulele; alternate tunings (Drop D, Open G, DADGAD); common chords and open-position fingerings
- Musical events (note, chord, rest) and sequences with tempo and time signature
- Real-time harmonic monitor with chord and field detection plus per-note context
- Live UMP stream interpreter (
GingoFlow): non-blocking capture, logical events with note pairing, chord grouping and durations, and an opt-in raw-UMP forward face - MIDI 1.0 output adapters:
GingoMIDI1::fromEvent,GingoMIDI1::fromSequence - MIDI 2.0 UMP Flex Data output adapters:
GingoMIDI2::chordName,keySignature,perNoteController - Chord comparison across 17 dimensions, including Neo-Riemannian transforms and Forte vectors
- Fixed-size arrays, no dynamic allocation, PROGMEM support
- Compatible with Arduino IDE, PlatformIO, ESP-IDF, and CMake (native)
- 585 native tests passing under
-Wall -Wextra -Werror
| Spec | Document | Surface in gingoduino |
|---|---|---|
| MIDI 1.0 | MIDI 1.0 Detailed Specification | Output: NoteOn/NoteOff bytes from GingoEvent and GingoSequence via GingoMIDI1::fromEvent, GingoMIDI1::fromSequence |
| MIDI 2.0 UMP | M2-104-UM v1.1.2 | Output: Flex Data (Set Chord Name, Set Key Signature, Set Per-Note Controller) via GingoMIDI2 |
| MIDI 2.0 Bit Scaling | M2-115-U v1.0.2 | Internal: round-trip safe value scaling between 7/14/16/32-bit fields |
Input parsing (MIDI 1.0 byte streams, UMP receive dispatch, MIDI-CI) is delegated to the transport library.
Arduino IDE Library Manager:
- Sketch > Include Library > Manage Libraries > search
Gingoduino> Install
PlatformIO:
; platformio.ini
lib_deps = sauloverissimo/GingoduinoESP-IDF Component:
idf.py add-dependency "sauloverissimo/gingoduino"CMake / native:
include(FetchContent)
FetchContent_Declare(gingoduino
GIT_REPOSITORY https://github.com/sauloverissimo/gingoduino.git
GIT_TAG v0.6.0)
FetchContent_MakeAvailable(gingoduino)
target_link_libraries(my_target PRIVATE gingoduino::gingoduino)Or install it (cmake --install) and use find_package(gingoduino CONFIG).
Manual:
- Download and copy to your Arduino libraries folder (
~/Arduino/libraries/).
#include <Gingoduino.h>
using namespace gingoduino;
void setup() {
Serial.begin(9600);
GingoNote note("C");
Serial.println(note.name()); // "C"
Serial.println(note.midiNumber(4)); // 60
Serial.println(note.frequency(4), 1); // 261.6
GingoChord chord("Dm7");
GingoNote notes[7];
chord.notes(notes, 7); // D, F, A, C
GingoScale scale("C", SCALE_MAJOR);
GingoField field("C", SCALE_MAJOR);
GingoChord triads[7];
field.chords(triads, 7); // CM, Dm, Em, FM, GM, Am, Bdim
}
void loop() {}GingoNote note("C#");
note.name(); // "C#"
note.natural(); // "C#" (sharp canonical: Bb -> A#, Eb -> D#)
note.semitone(); // 1 (0-11)
note.frequency(4); // Hz (float)
note.midiNumber(4); // 0-127
note.transpose(7); // GingoNote
note.distance(other); // shortest distance on the circle of fifths (0-6)
note.isEnharmonic(other); // bool
GingoNote::fromMIDI(60); // "C"
GingoNote::octaveFromMIDI(60); // 4GingoInterval iv("5J"); // or GingoInterval(7) or GingoInterval(noteA, noteB)
char buf[32];
iv.label(buf, sizeof(buf)); // "5J"
iv.semitones(); // 7
iv.degree(); // 5
iv.consonance(buf, sizeof(buf)); // "perfect", "imperfect" or "dissonant"
iv.isConsonant(); // true
iv.fullName(buf, sizeof(buf)); // "Perfect Fifth"
iv.fullNamePt(buf, sizeof(buf)); // "Quinta Justa"
iv.simple(); // reduce compound to simple
iv.invert(); // complement within octaveGingoChord chord("Dm7");
chord.name(); // "Dm7"
chord.root(); // GingoNote("D")
chord.type(); // "m7"
chord.size(); // 4
GingoNote notes[7];
chord.notes(notes, 7); // fill array with chord tones
GingoNote arr[3] = {GingoNote("C"), GingoNote("E"), GingoNote("G")};
char name[16];
GingoChord::identify(arr, 3, name, 16); // "CM"GingoScale scale("C", SCALE_MAJOR); // or GingoScale("C", "dorian")
char buf[22];
scale.modeName(buf, sizeof(buf)); // "Ionian"
scale.quality(); // "major" or "minor"
scale.signature(); // 0 (sharps > 0, flats < 0)
scale.brightness(); // 1-7 (higher = brighter)
GingoNote notes[12];
scale.notes(notes, 12); // fill with scale degrees
scale.mode(2); // Dorian
scale.pentatonic(); // pentatonic version
scale.relative(); // relative major or minor
scale.parallel(); // parallel major or minorGingoField field("C", SCALE_MAJOR);
GingoChord triads[7]; field.chords(triads, 7); // CM, Dm, Em, FM, GM, Am, Bdim
GingoChord sevs[7]; field.sevenths(sevs, 7); // C7M, Dm7, Em7, F7M, G7, Am7, Bm7(b5)
field.function(5); // FUNC_DOMINANT
field.functionOf(GingoChord("GM")); // FUNC_DOMINANT
char buf[12];
field.role(1, buf, sizeof(buf)); // "primary"
GingoNoteContext ctx = field.noteContext(GingoNote("E"));
ctx.degree; // 3
ctx.function; // FUNC_TONIC
ctx.inScale; // true
ctx.interval.semitones(); // 4GingoFretboard guitar = GingoFretboard::guitar(); // 6 strings, E A D G B E
// Also: ::violao(), ::cavaquinho(), ::mandolin(), ::bandolim(), ::ukulele()
// Alternate tunings: ::dropD(), ::openG(), ::dadgad()
guitar.noteAt(0, 5); // GingoNote("A"), string 0, fret 5
guitar.midiAt(0, 0); // 40 (E2)
GingoFingering fgs[5];
guitar.fingerings(GingoChord("CM"), fgs, 5); // up to 5 fingerings, sorted by score
GingoFingering opens[5];
guitar.openFingerings(GingoChord("GM"), opens, 5); // open-position only
GingoFingering ccs[7];
guitar.commonChords(GingoScale("G", SCALE_MAJOR), ccs, 7);
// ccs[]: GM, Am, Bm, CM, DM, Em, F#dim sorted by field degree
GingoFretboard capo2 = guitar.capo(2);GingoEvent ne = GingoEvent::noteEvent(GingoNote("C"), GingoDuration("quarter"), 4);
GingoEvent ce = GingoEvent::chordEvent(GingoChord("CM"), GingoDuration("half"));
GingoEvent re = GingoEvent::rest(GingoDuration("quarter"));
GingoSequence seq(GingoTempo(120), GingoTimeSig(4, 4));
seq.add(ne);
seq.totalBeats(); // 1.0
seq.totalSeconds(); // 0.5 at 120 BPM
seq.transpose(5); // transpose all eventsThe Monitor receives musical events and tracks held notes, sustain pedal, detected chord, deduced field, and per-note context. Inputs come from any external transport. The Monitor itself does not parse MIDI.
GingoMonitor monitor;
monitor.setChannel(0xFF); // accept all channels (default), or 0-15 to filter
// Feed events from your transport callbacks:
monitor.noteOn(0, 60, 100); // channel 0, C4, velocity 100
monitor.noteOn(0, 64, 100); // channel 0, E4
monitor.noteOn(0, 67, 100); // channel 0, G4
monitor.sustainOn();
monitor.sustainOff();
monitor.reset(); // all notes off
// Poll state:
monitor.hasChord(); // true
monitor.currentChord(); // GingoChord("CM")
monitor.currentField(); // GingoField
// Callbacks (std::function lambdas, not on AVR):
monitor.onChordDetected([](const GingoChord& c) { /* ... */ });
monitor.onFieldChanged([](const GingoField& f) { /* ... */ });
monitor.onNoteOn ([](const GingoNoteContext& ctx) { /* ... */ });GingoFlow is a live UMP stream interpreter. It captures raw Universal MIDI Packets without blocking or dropping, then weaves a logical event stream structured by the rhythmic axis: each event carries full MIDI 2.0 resolution, a duration once its note-off pairs, and an index family that links note-on/note-off pairs and groups simultaneous notes into chords. Capture is always on; the logical and raw-forward faces are opt-in. Header-only template (N = capture ring size), available where the toolchain ships <functional> (not AVR).
GingoFlow<256> flow; // N = capture ring size
// Logical face: structured events with note pairing, chord grouping, duration.
flow.onEvent([](const GingoFlowEvent& ev) {
ev.kind; // NOTE_ON / NOTE_OFF / CC / ...
ev.note; // note number
ev.velocity; // full 16-bit MIDI 2.0 resolution
ev.idx.event; // global order
ev.idx.note; // pairs on/off; the off carries ev.durationMs
ev.idx.chord; // simultaneous-note group
});
// Optional raw-UMP forward face (bridge or chain):
flow.onForward([](const uint32_t* words, uint8_t n) { /* re-emit */ });
// Feed raw UMP from any transport (USB MIDI 2.0, MIDI 1.0 upconverted, bridge):
uint32_t words[2] = { 0x40903C00u, 0x80000000u }; // MIDI 2.0 note-on, C4
flow.ingest(words, 2, millis()); // non-blocking (goes to the spine)
// Drain on your loop; capture never blocks, interpretation is opt-in:
while (flow.process()) { /* events fire via onEvent */ }
flow.activeNoteCount(); // held notes right now
flow.droppedTotal(); // packets the capture ring had to drop (backpressure)
// Calibratable chord-grouping rule (onset-cluster by default):
flow.config().chordRule = CHORD_ONSET_CLUSTER; // or CHORD_SIMULTANEITY
flow.config().onsetWindowMs = 50;Raw UMP comes from any transport: midi2cpp (USB MIDI 2.0), an ESP32_Host_MIDI connection, or MIDI 1.0 bytes upconverted to UMP. See the Flow and T-Display-S3-Piano-Flow examples.
// Single event -> MIDI 1.0 bytes (NoteOn + NoteOff, 6 bytes for note events).
uint8_t buf[6];
uint8_t n = GingoMIDI1::fromEvent(noteEvent, buf, sizeof(buf));
// Sequence -> MIDI 1.0 byte stream. Default keeps each event's own
// channel; pass an explicit 0-15 to override every event.
uint8_t out[256];
uint16_t total = GingoMIDI1::fromSequence(seq, out, sizeof(out));
// Or with explicit override:
uint16_t total2 = GingoMIDI1::fromSequence(seq, out, sizeof(out), 5);Input from MIDI 1.0 byte streams is intentionally not in scope. Use any external parser (Arduino MIDI Library, your own, etc.) and call GingoMonitor directly.
auto chordUMP = GingoMIDI2::chordName(GingoChord("CM"));
auto keySigUMP = GingoMIDI2::keySignature(scale); // group=0, channel=0 default
auto keySigCh5 = GingoMIDI2::keySignature(scale, /*group=*/0, /*channel=*/5);
GingoNoteContext ctx = field.noteContext(GingoNote("E"));
auto rccUMP = GingoMIDI2::perNoteController(ctx, /*midiNote=*/64);
chordUMP.wordCount; // 4 (128-bit Flex Data)
rccUMP.wordCount; // 2 (64-bit per-note CC)
chordUMP.byteCount(); // 16
uint8_t bytes[16];
chordUMP.toBytesBE(bytes, sizeof(bytes)); // big-endian wire serializationUMP receive dispatch and MIDI-CI are out of scope. Use midi2cpp (or any UMP library) for receive callbacks, and the midi2 C99 library for MIDI-CI responder/initiator flows.
GingoChordComparison cmp(GingoChord("CM"), GingoChord("Am"));
cmp.common_count; // 2 (C and E shared)
cmp.root_distance; // 3 semitones
cmp.same_quality; // false
cmp.voice_leading; // min semitone movement
cmp.transformation; // NEO_R (Relative)
cmp.interval_vector_a[6]; // Forte interval vectorThe Monitor is the single entry point for musical events. Glue between an external transport and the Monitor takes a few lines and lives in your sketch.
Receive UMP through midi2cpp and forward each event to the Monitor (noteOn, noteOff, sustainOn / sustainOff). On onChordDetected, render the chord with GingoMIDI2::chordName(...) and send the resulting UMP back through midi2cpp. See the midi2cpp examples for its receive and send API.
MIDI.setHandleNoteOn ([](byte ch, byte note, byte vel) {
monitor.noteOn(ch - 1, note, vel); // 1-16 -> 0-15 (UMP convention)
});
MIDI.setHandleNoteOff([](byte ch, byte note, byte vel) {
monitor.noteOff(ch - 1, note);
});
MIDI.setHandleControlChange([](byte ch, byte cc, byte val) {
if (cc == 64) { (val >= 64) ? monitor.sustainOn() : monitor.sustainOff(); }
else if (cc == 123) { monitor.reset(); }
});Parse the UART byte stream inline (running status, SysEx absorption, real-time bytes) and forward the musical events to the Monitor with noteOn / noteOff / sustainOn / sustainOff. A ~30-line parser is enough.
| Example | Description | Tier |
|---|---|---|
| BasicNote | Note creation, transposition, MIDI, frequency | 1 |
| ChordNotes | Chord notes, intervals, identify | 1 |
| ScaleExplorer | Scales, modes, pentatonic | 2 |
| HarmonicField | Triads, sevenths, harmonic functions | 2 |
| Gingoduino_to_MIDI | Build a sequence and serialize via GingoMIDI1::fromSequence |
3 |
| Flow | RP2040 flow harness: a device emits a paced UMP gabarito, a host interprets it live with GingoFlow (capture + interpretation) |
3 |
| T-Display-S3-Piano-Flow | Live flow piano on the T-Display S3: chord with inversion, held notes, duration, and a scrolling processed-event log, source-agnostic over a USB MIDI 2.0 host (GingoFlow) |
3 |
| V06_SelfTest | On-device acceptance suite covering the public surface through v0.6.0, including GingoFlow |
3 |
| Foundation | Exhaustive on-device foundation proof: 398 musical claims (every INTERVAL_TABLE entry, every CHORD_FORMULAS entry, every SCALE_MASKS scale, full field analysis pipeline) | 3 |
| Foundation_AVR | Reduced subset that fits 8-bit AVR: Note + Interval + Chord + Scale + MIDI primitives, 93 claims | 1 |
| Foundation_Daisy | libDaisy native port of the foundation proof for Daisy Seed (STM32H750, Cortex-M7). Builds with make, flashes via DFU |
3 |
Looking for USB/BLE MIDI input examples? They live in the transport library, not here. See:
ESP32_Host_MIDI/examples/USB-Host-MIDI2/- USB host that receives raw UMP from a MIDI 2.0 device and decodes Channel VoiceESP32_Host_MIDI/examples/T-Display-S3-BLE-Receiver/- BLE MIDI input with a real-time piano visualizer
This split is deliberate: gingoduino is a music theory engine and stays out of the wire; the transport library owns the byte-level integration examples.
g++ -std=c++11 -I. -Wall -Wextra -Werror \
-o extras/tests/test_native extras/tests/test_native.cpp \
&& ./extras/tests/test_native585 tests pass under -Wall -Wextra -Werror, plus 398 additional musical
correctness claims in extras/tests/foundation.cpp that exhaustively cover
the INTERVAL_TABLE, every CHORD_FORMULAS entry, every CHORD_TYPE_MAP alias
and every SCALE_MASKS scale. Both suites pass under gcc 13, clang LLVM 22
and gcc + AddressSanitizer + UndefinedBehaviorSanitizer.
The library is verified on the host (PC native gcc/clang + sanitizers) and
on real boards across the MCU families it claims to support. Same Foundation
sketch, only the FQBN changes; bytes-identical results across compilers and
architectures. On 8-bit AVR the full Foundation does not fit, so a reduced
Foundation_AVR sketch exercises the Note + Interval + Chord + Scale + MIDI
primitives (Field, Monitor, Progression and Compare are out of scope on AVR).
| Board | MCU | Foundation result | Flash used | Toolchain |
|---|---|---|---|---|
| Arduino Leonardo | ATmega32U4, 8-bit AVR @16 MHz | AVR subset 93/93 PASS | 51% (14.7 KB / 28.6 KB), SRAM 14% | avr-gcc |
| Seeed XIAO SAMD21 | ARM Cortex-M0+ @48 MHz | Full 398/398 PASS | 25% (67 KB / 256 KB) | arm-none-eabi (Seeed SAMD core) |
| Adafruit Feather RP2040 USB Host | RP2040, ARM Cortex-M0+ dual @133 MHz | Full 398/398 PASS | 1% (94 KB / 8 MB) | arm-none-eabi (arduino-pico) |
| Nice!Nano nRF52840 (Pro Micro class) | nRF52840, ARM Cortex-M4F @64 MHz + SoftDevice S140 | Full 398/398 PASS | 15% (125 KB / 815 KB) | arm-none-eabi 9.2 (adafruit-nrf52) |
| Daisy Seed | STM32H750, ARM Cortex-M7 @480 MHz | Full 398/398 PASS (libDaisy, Foundation_Daisy) |
91% (119 KB / 128 KB) | arm-none-eabi 13.2 + libDaisy |
| Teensy 4.1 | NXP i.MX RT1062, ARM Cortex-M7 @600 MHz | Full 398/398 PASS | <1% (82 KB / 8 MB) | arm-none-eabi (Teensyduino) |
| ESP32-S3 DevKitC | Espressif Xtensa LX7 dual @240 MHz | Full 398/398 PASS | 24% (319 KB / 1.25 MB) | xtensa-esp32-elf-g++ (arduino-esp32) |
Total: 980 PC-native asserts + 5Γ398 + 93 = 3 063 musical claims exercised on silicon across six MCU families (AVR 8-bit, Cortex-M0+, Cortex-M4F, Cortex-M7, Xtensa LX7) and five toolchains, validated on 2026-06-02.
The boundary is drawn so the engine stays focused. A few things deliberately do not belong here.
- Not a UMP parser. UMP receive dispatch belongs to a transport library. Use
midi2cppfor USB MIDI 2.0 or themidi2C99 core directly, and forward decoded callbacks intoGingoMonitor. - Not a USB stack. Gingoduino does not own descriptors, endpoints, or alt settings. Pair it with
midi2cpp(USB MIDI 2.0), Arduino MIDI Library (MIDI 1.0 DIN/Serial), or your own transport. - Not a MIDI-CI responder. Capability Inquiry, Profile negotiation, and Property Exchange live in
midi2. Gingoduino exposes no MIDI-CI surface. - Not a synthesizer. Musical events are detected and analyzed; sound generation is application territory.
- Not a MIDI File reader or writer. Standard MIDI File (SMF) I/O is out of scope.
GingoSequenceis a runtime structure, not a file format. - Not a desktop library. It targets MCU boards. Tests run on desktop with
g++, but the API and memory model assume embedded constraints.
MIT License. See LICENSE.
Saulo Verissimo
