Skip to content

Latest commit

 

History

History
595 lines (471 loc) · 29.8 KB

File metadata and controls

595 lines (471 loc) · 29.8 KB

Gingoduino

🪇 Gingo[duino]

Music theory engine for embedded systems. Build musical instruments and controllers that interact with people and speak MIDI 1.0 and 2.0.

Mentioned in Awesome Music

MIDI 2.0 Platform License: MIT

Sponsor

PT-BR


Contents

Overview

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.

Roles in the ecosystem

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.

Architecture

┌─────────────────┐    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.

Modules

Theory:

  • GingoNote: chromatic note, frequency, MIDI number, transposition
  • GingoInterval: labeled intervals, consonance, full name
  • GingoChord: 42 chord formulas, identification, transposition
  • GingoScale: scale types, modes, signature, brightness, formalNotes (diatonic spelling)
  • GingoField: harmonic field, T/S/D functions, role, relative/parallel, branchOf, appliedChords (secondary dominants), compare (21-dim GingoFieldComparison)
  • GingoTree: harmonic graph (classical and jazz traditions)
  • GingoProgression: identify, deduce, predict
  • GingoChordComparison: 17 dimensions, Neo-Riemannian, Forte vectors
  • GingoFretboard: guitar, violao, cavaquinho, mandolim/bandolim, ukulele; alternate tunings

Time and events:

  • GingoDuration (rational, operator+, operator*), GingoTempo, GingoTimeSig
  • GingoEvent, GingoSequence

Real-time analysis:

  • GingoMonitor: chord detection, field deduction, per-note context, onPrediction with rolling branch history
  • GingoFlow: 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 bytes
  • GingoMIDI2::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 the std::function lambda-with-capture overloads on GingoMonitor are 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 (see examples/Foundation_AVR).

Features

  • 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 coverage

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.

Installation

Arduino IDE Library Manager:

  • Sketch > Include Library > Manage Libraries > search Gingoduino > Install

PlatformIO:

; platformio.ini
lib_deps = sauloverissimo/Gingoduino

ESP-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/).

Quick start

#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() {}

API reference

GingoNote

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); // 4

GingoInterval

GingoInterval 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 octave

GingoChord

GingoChord 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

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 minor

GingoField

GingoField 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();              // 4

GingoFretboard

GingoFretboard 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 and GingoSequence

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 events

GingoMonitor

The 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

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.

GingoMIDI1, output adapters

// 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.

GingoMIDI2, UMP Flex Data adapters

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 serialization

UMP 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

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 vector

MIDI integration

The 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.

MIDI 2.0 UMP via midi2cpp

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 1.0 via Arduino MIDI Library

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

Raw DIN MIDI on UART

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.

Examples

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:

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.

Native testing

g++ -std=c++11 -I. -Wall -Wextra -Werror \
    -o extras/tests/test_native extras/tests/test_native.cpp \
    && ./extras/tests/test_native

585 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.

Hardware validation

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.

What this library is not

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 midi2cpp for USB MIDI 2.0 or the midi2 C99 core directly, and forward decoded callbacks into GingoMonitor.
  • 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. GingoSequence is 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.

License

MIT License. See LICENSE.

Author

Saulo Verissimo