Skip to content

Latest commit

 

History

History
596 lines (472 loc) · 30.6 KB

File metadata and controls

596 lines (472 loc) · 30.6 KB

Gingoduino

🪇 Gingo[duino]

Motor de Teoria Musical para Sistemas Embarcados

Mentioned in Awesome Music

MIDI 2.0 Platform License: MIT

Sponsor

EN-US


Conteúdo

Visão geral

Gingoduino é um motor de teoria musical para sistemas embarcados. Ele cobre o domínio musical (notas, intervalos, acordes, escalas, campos harmônicos, árvores harmônicas, análise de progressão, engine de braço), um monitor harmônico em tempo real, um interpretador de stream UMP ao vivo (GingoFlow), e adaptadores de saída sem estado que traduzem estruturas musicais em formatos serializados (bytes MIDI 1.0 e UMP Flex Data MIDI 2.0).

Não cobre o fio. Parsing de byte stream, dispatch de UMP recebido, e descoberta MIDI-CI com Property Exchange são responsabilidades de protocolo delegadas a bibliotecas dedicadas: midi2cpp para UMP, Arduino MIDI Library ou parser próprio para byte stream MIDI 1.0, biblioteca midi2 C99 para responders MIDI-CI.

Arquitetura zero-heap, tabelas em PROGMEM, compatível com C++11.

0.4.0 é uma release com breaking changes. Veja CHANGELOG.md para o guia de migração se você está atualizando da 0.3.x.

Papéis no ecossistema

Gingoduino é uma das três bibliotecas companheiras, com papéis distintos e complementares.

Biblioteca Papel Linguagem Fala
midi2 Infraestrutura: wire UMP, parsing, dispatch, MIDI-CI C99 MIDI 2.0
midi2cpp Plataforma: USB MIDI 2.0 device, host, bridge C++17 MIDI 2.0
gingoduino Motor de teoria musical. Notas, intervalos, acordes, escalas, campos, monitor harmônico, adaptadores de saída C++11 MIDI 1.0 e 2.0
            ┌─────────────────────────────────────────────┐
            │                Aplicação                    │
            └─────────────────────────────────────────────┘
                  │                              │
                  │ usa para teoria              │ usa para transporte
                  ▼                              ▼
        ┌────────────────────┐         ┌──────────────────────┐
        │     gingoduino     │         │       midi2cpp       │
        │  Teoria musical    │         │  USB MIDI 2.0        │
        │  Notas · Acordes   │         │  Device · Host       │
        │  Escalas · Campos  │         │  Bridge · CI         │
        │  MIDI 1.0 + 2.0    │         └──────────┬───────────┘
        │  adaptadores de    │                    │ usa
        │  saída             │                    ▼
        └────────────────────┘         ┌──────────────────────┐
                                       │        midi2         │
                                       │  Wire UMP · MIDI-CI  │
                                       │  Parsing · Dispatch  │
                                       └──────────────────────┘

Gingoduino é a única das três com um pé em MIDI 1.0. GingoMIDI1 serializa estruturas musicais em bytes MIDI 1.0 puros (DIN, USB MIDI 1.0, portas virtuais); GingoMIDI2 serializa as mesmas estruturas em UMP Flex Data MIDI 2.0, prontas para alimentar midi2cpp ou qualquer transporte UMP.

Arquitetura

┌─────────────────┐    evento musical     ┌──────────────────┐
│   Transporte    │ ──────────────────▶   │   GingoMonitor   │
│  (midi2cpp,     │ noteOn / noteOff /    │  acorde, campo,  │
│   Arduino MIDI, │ sustainOn / Off       │  contexto/nota   │
│   ESP32_Host_   │                       └────────┬─────────┘
│   MIDI, ...)    │                                │
└─────────────────┘                                │
        ▲                                          ▼
        │            ┌─────────────────────────────────┐
        │            │    Teoria + Análise              │
        │            │  Note · Interval · Chord ·       │
        │            │  Scale · Field · Tree ·          │
        │            │  Progression · Comparison ·      │
        │            │  Fretboard · Event · Sequence    │
        │            └─────────────────┬────────────────┘
        │                              │
        │                              ▼
        │            ┌─────────────────────────────────┐
        │            │     Adaptadores de saída         │
        │  bytes /   │  GingoMIDI1::fromEvent           │
        └────────────┤  GingoMIDI1::fromSequence        │
            UMP      │  GingoMIDI2::chordName           │
                     │  GingoMIDI2::keySignature        │
                     │  GingoMIDI2::perNoteController   │
                     └─────────────────────────────────┘

O transporte vive fora da biblioteca. Ele decodifica bytes ou pacotes UMP e alimenta o Monitor pela API de eventos musicais. Tudo o que Gingoduino calcula pode ser convertido de volta em bytes ou UMP através dos adaptadores de saída em namespaces dedicados.

Módulos

Teoria:

  • GingoNote: nota cromática, frequência, número MIDI, transposição
  • GingoInterval: intervalos rotulados, consonância, nome completo
  • GingoChord: 42 fórmulas de acorde, identificação, transposição
  • GingoScale: tipos de escala, modos, armadura, brilho, formalNotes (spelling diatônico)
  • GingoField: campo harmônico, funções T/S/D, role, relative/parallel, branchOf, appliedChords (dominantes secundários), compare (GingoFieldComparison com 21 dimensões)
  • GingoTree: grafo harmônico (tradições clássica e jazz)
  • GingoProgression: identify, deduce, predict
  • GingoChordComparison: 17 dimensões, Neo-Riemann, vetores de Forte
  • GingoFretboard: guitar, violão, cavaquinho, mandolim/bandolim, ukulele; afinações alternativas

Tempo e eventos:

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

Análise em tempo real:

  • GingoMonitor: detecção de acorde, dedução de campo, contexto por nota, onPrediction com history rotativo de branches
  • GingoFlow: interpretador de stream UMP ao vivo; captura não-bloqueante, um stream de eventos lógicos com pareamento de notas, agrupamento de acordes e durações, uma face opcional de forward de UMP bruto, e uma regra de agrupamento de acordes calibrável

Adaptadores de saída (sem estado):

  • GingoMIDI1::fromEvent, GingoMIDI1::fromSequence: bytes MIDI 1.0
  • GingoMIDI2::chordName, keySignature, perNoteController: UMP Flex Data MIDI 2.0

Portabilidade:

Gingoduino é C++11 portável, zero heap e tabelas de lookup em PROGMEM. Não depende de API de placa nenhuma e deve compilar em qualquer toolchain que entregue C++11 padrão. As famílias declaradas em library.properties (esp32, esp8266, samd, nrf52, rp2040, mbed_rp2040, renesas_uno, renesas_portenta, stm32, teensy) são os alvos que exercitamos; outros Cortex-M / RISC-V / desktop também compilam sem problema. Veja Validação em hardware para as placas onde rodamos a prova on-device.

Algumas notas práticas:

  • <functional> só é incluído quando o toolchain entrega. AVR (avr-libc) não tem, então os overloads std::function com lambda capture do GingoMonitor ficam indisponíveis em AVR; os overloads com ponteiro de função continuam funcionando.
  • AVR (Uno, Nano, Leonardo etc., 32 KB Flash / 2-2.5 KB SRAM) não comporta a biblioteca completa junto com o pipeline Field/Monitor/Progression. Escopo prático em AVR: GingoNote, GingoInterval, GingoChord, GingoScale (veja examples/Foundation_AVR).

Características

  • Sistema cromático de 12 notas com equivalentes enarmônicos
  • 42 fórmulas de acordes com lookup reverso (identify)
  • Mais de 40 tipos de escalas e modos com armadura, brilho, escala relativa e paralela
  • Análise de campo harmônico com funções T/S/D e roles, mais dedução a partir de notas e acordes
  • Árvore harmônica (grafo dirigido, maior e menor, tradições clássica e jazz)
  • Análise de progressão: identify, deduce (ranqueado), predict (próximo branch)
  • Engine de braço: guitar, violão, cavaquinho, mandolim/bandolim, ukulele; afinações alternativas (Drop D, Open G, DADGAD); acordes comuns e digitações em primeira posição
  • Eventos musicais (nota, acorde, pausa) e sequências com tempo e fórmula de compasso
  • Monitor harmônico em tempo real com detecção de acordes e campos e contexto por nota
  • Interpretador de stream UMP ao vivo (GingoFlow): captura não-bloqueante, eventos lógicos com pareamento de notas, agrupamento de acordes e durações, e uma face opcional de forward de UMP bruto
  • Adaptadores de saída MIDI 1.0: GingoMIDI1::fromEvent, GingoMIDI1::fromSequence
  • Adaptadores de saída MIDI 2.0 UMP Flex Data: GingoMIDI2::chordName, keySignature, perNoteController
  • Comparação de acordes em 17 dimensões, incluindo transformações Neo-Riemannianas e vetores Forte
  • Arrays de tamanho fixo, sem alocação dinâmica, suporte a PROGMEM
  • Compatível com Arduino IDE, PlatformIO, ESP-IDF e CMake (nativo)
  • 585 testes nativos passando com -Wall -Wextra -Werror

Cobertura de especificações

Spec Documento Superfície no gingoduino
MIDI 1.0 MIDI 1.0 Detailed Specification Saída: bytes NoteOn/NoteOff de GingoEvent e GingoSequence via GingoMIDI1::fromEvent, GingoMIDI1::fromSequence
MIDI 2.0 UMP M2-104-UM v1.1.2 Saída: 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 Interno: scaling de valores entre campos 7/14/16/32 bits com round-trip seguro

Parsing de entrada (byte streams MIDI 1.0, dispatch de UMP recebido, MIDI-CI) é responsabilidade da biblioteca de transporte.

Instalação

Arduino IDE Library Manager:

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

PlatformIO:

; platformio.ini
lib_deps = sauloverissimo/Gingoduino

ESP-IDF Component:

idf.py add-dependency "sauloverissimo/gingoduino"

CMake / nativo:

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)

Ou instale (cmake --install) e use find_package(gingoduino CONFIG).

Manual:

  • Baixe e copie para sua pasta de bibliotecas Arduino (~/Arduino/libraries/).

Uso rápido

#include <Gingoduino.h>

using namespace gingoduino;

void setup() {
    Serial.begin(9600);

    GingoNote nota("C");
    Serial.println(nota.name());           // "C"
    Serial.println(nota.midiNumber(4));    // 60
    Serial.println(nota.frequency(4), 1);  // 261.6

    GingoChord acorde("Dm7");
    GingoNote notas[7];
    acorde.notes(notas, 7);                // D, F, A, C

    GingoScale escala("C", SCALE_MAJOR);
    GingoField campo("C", SCALE_MAJOR);
    GingoChord triades[7];
    campo.chords(triades, 7);              // CM, Dm, Em, FM, GM, Am, Bdim
}

void loop() {}

Referência da API

GingoNote

GingoNote note("C#");
note.name();              // "C#"
note.natural();           // "C#" (forma canônica em sustenidos: 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);     // distância mínima no ciclo de quintas (0-6)
note.isEnharmonic(other); // bool
GingoNote::fromMIDI(60);  // "C"
GingoNote::octaveFromMIDI(60); // 4

GingoInterval

GingoInterval iv("5J");          // ou GingoInterval(7) ou 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" ou "dissonant"
iv.fullName(buf, sizeof(buf));   // "Perfect Fifth"
iv.fullNamePt(buf, sizeof(buf)); // "Quinta Justa"
iv.simple();                     // reduz composto para simples
iv.invert();                     // complemento dentro da oitava

GingoChord

GingoChord chord("Dm7");
chord.name();                          // "Dm7"
chord.root();                          // GingoNote("D")
chord.type();                          // "m7"
chord.size();                          // 4

GingoNote notes[7];
chord.notes(notes, 7);

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);    // ou GingoScale("C", "dorian")
char buf[22];
scale.modeName(buf, sizeof(buf));      // "Ionian"
scale.quality();                       // "major" ou "minor"
scale.signature();                     // 0 (sustenidos > 0, bemóis < 0)
scale.brightness();                    // 1-7 (maior = mais brilhante)

GingoNote notes[12];
scale.notes(notes, 12);
scale.mode(2);                         // Dorian
scale.pentatonic();
scale.relative();                      // relativa maior/menor
scale.parallel();                      // paralela maior/menor

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();
// Também: ::violao(), ::cavaquinho(), ::mandolin(), ::bandolim(), ::ukulele()
// Afinações alternativas: ::dropD(), ::openG(), ::dadgad()

guitar.noteAt(0, 5);                  // GingoNote("A")
guitar.midiAt(0, 0);                  // 40 (E2)

GingoFingering fgs[5];
guitar.fingerings(GingoChord("CM"), fgs, 5);

GingoFingering opens[5];
guitar.openFingerings(GingoChord("GM"), opens, 5);

GingoFingering ccs[7];
guitar.commonChords(GingoScale("G", SCALE_MAJOR), ccs, 7);

GingoFretboard capo2 = guitar.capo(2);

GingoEvent e 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 a 120 BPM
seq.transpose(5);

GingoMonitor

O Monitor recebe eventos musicais e mantém notas seguradas, pedal de sustain, acorde detectado, campo deduzido e contexto por nota. As entradas chegam de qualquer transporte externo. O Monitor em si não decodifica MIDI.

GingoMonitor monitor;

monitor.setChannel(0xFF);   // aceita todos os channels (default), ou 0-15 pra filtrar

// Alimentar a partir dos callbacks do transporte:
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

// Consultar estado:
monitor.hasChord();           // true
monitor.currentChord();       // GingoChord("CM")
monitor.currentField();       // GingoField

// Callbacks (lambdas std::function, fora do AVR):
monitor.onChordDetected([](const GingoChord& c)            { /* ... */ });
monitor.onFieldChanged([](const GingoField& f)             { /* ... */ });
monitor.onNoteOn      ([](const GingoNoteContext& ctx)     { /* ... */ });

GingoFlow

GingoFlow é um interpretador de stream UMP ao vivo. Ele captura Universal MIDI Packets brutos sem bloquear nem descartar, e então tece um stream de eventos lógicos estruturado pelo eixo rítmico: cada evento carrega resolução MIDI 2.0 completa, uma duração assim que o note-off pareia, e uma família de índices que liga pares note-on/note-off e agrupa notas simultâneas em acordes. A captura está sempre ligada; as faces lógica e de forward bruto são opcionais. Template header-only (N = tamanho do ring de captura), disponível onde o toolchain entrega <functional> (não em AVR).

GingoFlow<256> flow;            // N = tamanho do ring de captura

// Face lógica: eventos estruturados com pareamento de notas, agrupamento de acordes, duração.
flow.onEvent([](const GingoFlowEvent& ev) {
    ev.kind;        // NOTE_ON / NOTE_OFF / CC / ...
    ev.note;        // número da nota
    ev.velocity;    // resolução MIDI 2.0 completa de 16 bits
    ev.idx.event;   // ordem global
    ev.idx.note;    // pareia on/off; o off carrega ev.durationMs
    ev.idx.chord;   // grupo de notas simultâneas
});

// Face opcional de forward de UMP bruto (bridge ou chain):
flow.onForward([](const uint32_t* words, uint8_t n)        { /* re-emite */ });

// Alimente UMP bruto de qualquer transporte (USB MIDI 2.0, MIDI 1.0 convertido, bridge):
uint32_t words[2] = { 0x40903C00u, 0x80000000u };  // note-on MIDI 2.0, C4
flow.ingest(words, 2, millis());                    // não-bloqueante (vai pra spine)

// Drene no seu loop; a captura nunca bloqueia, a interpretação é opcional:
while (flow.process()) { /* eventos disparam via onEvent */ }

flow.activeNoteCount();   // notas seguradas agora
flow.droppedTotal();      // pacotes que o ring de captura teve que descartar (backpressure)

// Regra de agrupamento de acordes calibrável (onset-cluster por default):
flow.config().chordRule     = CHORD_ONSET_CLUSTER;  // ou CHORD_SIMULTANEITY
flow.config().onsetWindowMs = 50;

UMP bruto vem de qualquer transporte: midi2cpp (USB MIDI 2.0), uma conexão ESP32_Host_MIDI, ou bytes MIDI 1.0 convertidos para UMP. Veja os exemplos Flow e T-Display-S3-Piano-Flow.

GingoMIDI1, adaptadores de saída

// Evento único -> bytes MIDI 1.0 (NoteOn + NoteOff, 6 bytes pra eventos de nota).
uint8_t buf[6];
uint8_t n = GingoMIDI1::fromEvent(noteEvent, buf, sizeof(buf));

// Sequência -> stream de bytes MIDI 1.0. O default mantém o canal de
// cada evento; passe um 0-15 explícito pra forçar override em todos.
uint8_t out[256];
uint16_t total = GingoMIDI1::fromSequence(seq, out, sizeof(out));
// Ou com override explícito:
uint16_t total2 = GingoMIDI1::fromSequence(seq, out, sizeof(out), 5);

A entrada de byte stream MIDI 1.0 fica intencionalmente fora do escopo. Use qualquer parser externo (Arduino MIDI Library, parser próprio, etc.) e chame GingoMonitor direto.

GingoMIDI2, adaptadores UMP Flex Data

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 (Flex Data 128-bit)
rccUMP.wordCount;      // 2 (per-note CC 64-bit)
chordUMP.byteCount();  // 16
uint8_t bytes[16];
chordUMP.toBytesBE(bytes, sizeof(bytes));   // serialização big-endian pro fio

Dispatch de UMP recebido e MIDI-CI estão fora do escopo. Use midi2cpp (ou outra lib UMP) para callbacks de recepção, e a biblioteca midi2 C99 para fluxos de responder/initiator MIDI-CI.

GingoChordComparison

GingoChordComparison cmp(GingoChord("CM"), GingoChord("Am"));
cmp.common_count;          // 2 (C e E em comum)
cmp.root_distance;         // 3 semitons
cmp.same_quality;          // false
cmp.voice_leading;         // movimento mínimo em semitons
cmp.transformation;        // NEO_R (Relative)
cmp.interval_vector_a[6];  // vetor intervalar de Forte

Integração MIDI

O Monitor é o ponto único de entrada para eventos musicais. A cola entre o transporte externo e o Monitor leva poucas linhas e vive no seu sketch.

MIDI 2.0 UMP via midi2cpp

Receba UMP pela midi2cpp e encaminhe cada evento ao Monitor (noteOn, noteOff, sustainOn / sustainOff). No onChordDetected, renderize o acorde com GingoMIDI2::chordName(...) e envie o UMP resultante de volta pela midi2cpp. Veja os exemplos da midi2cpp para a API de recepção e envio.

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 (convenção UMP)
});
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(); }
});

DIN MIDI bruta na UART

Decodifique o byte stream da UART inline (running status, absorção de SysEx, bytes de real-time) e encaminhe os eventos musicais ao Monitor com noteOn / noteOff / sustainOn / sustainOff. Um parser de ~30 linhas resolve.

Exemplos

Exemplo Descrição Tier
BasicNote Criação de notas, transposição, MIDI, frequência 1
ChordNotes Notas do acorde, intervalos, identify 1
ScaleExplorer Escalas, modos, pentatônica 2
HarmonicField Tríades, tétrades, funções harmônicas 2
Gingoduino_to_MIDI Constrói uma sequência e serializa via GingoMIDI1::fromSequence 3
Flow Harness de flow RP2040: um device emite um gabarito UMP ritmado, um host o interpreta ao vivo com GingoFlow (captura + interpretação) 3
T-Display-S3-Piano-Flow Piano de flow ao vivo no T-Display S3: acorde com inversão, notas seguradas, duração, e um log rolante de eventos processados, agnóstico à fonte sobre um host USB MIDI 2.0 (GingoFlow) 3
V06_SelfTest Suite de aceitação on-device cobrindo a superfície pública até a v0.6.0, incluindo GingoFlow 3
Foundation Prova exaustiva on-device: 398 asserções musicais (cada entrada de INTERVAL_TABLE, cada CHORD_FORMULAS, cada SCALE_MASKS, pipeline de análise de campo completo) 3
Foundation_AVR Subconjunto reduzido que cabe em AVR 8-bit: primitivas Note + Interval + Chord + Scale + MIDI, 93 asserções 1
Foundation_Daisy Port nativo libDaisy da prova de fundação para Daisy Seed (STM32H750, Cortex-M7). Build com make, flash via DFU 3

Procurando exemplos de entrada MIDI USB/BLE? Eles vivem na biblioteca de transporte, não aqui:

A separação é deliberada: gingoduino é motor de teoria musical e fica fora do fio; a biblioteca de transporte cuida dos exemplos de integração no nível dos bytes.

Testes nativos

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

585 testes passam sob -Wall -Wextra -Werror, mais 398 asserções adicionais de correção musical em extras/tests/foundation.cpp que cobrem exaustivamente a INTERVAL_TABLE, cada entrada de CHORD_FORMULAS, cada alias de CHORD_TYPE_MAP e cada escala de SCALE_MASKS. As duas suítes passam em gcc 13, clang LLVM 22 e em gcc + AddressSanitizer + UndefinedBehaviorSanitizer.

Validação em hardware

A biblioteca é verificada no host (PC com gcc/clang nativos + sanitizers) e em placas reais nas famílias de MCU que ela diz suportar. Mesmo sketch Foundation, só muda o FQBN; resultados byte a byte idênticos entre compiladores e arquiteturas. Em AVR 8-bit o Foundation completo não cabe, então um sketch Foundation_AVR reduzido exercita as primitivas Note + Interval + Chord + Scale + MIDI (Field, Monitor, Progression e Compare ficam fora de escopo em AVR).

Placa MCU Resultado Foundation Flash usado Toolchain
Arduino Leonardo ATmega32U4, AVR 8-bit @16 MHz Subset AVR 93/93 PASS 51% (14.7 KB / 28.6 KB), SRAM 14% avr-gcc
Seeed XIAO SAMD21 ARM Cortex-M0+ @48 MHz 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 398/398 PASS 1% (94 KB / 8 MB) arm-none-eabi (arduino-pico)
Nice!Nano nRF52840 (classe Pro Micro) nRF52840, ARM Cortex-M4F @64 MHz + SoftDevice S140 398/398 PASS 15% (125 KB / 815 KB) arm-none-eabi 9.2 (adafruit-nrf52)
Daisy Seed STM32H750, ARM Cortex-M7 @480 MHz 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 398/398 PASS <1% (82 KB / 8 MB) arm-none-eabi (Teensyduino)
ESP32-S3 DevKitC Espressif Xtensa LX7 dual @240 MHz 398/398 PASS 24% (319 KB / 1.25 MB) xtensa-esp32-elf-g++ (arduino-esp32)

Total: 980 asserts nativos no PC + 5×398 + 93 = 3 063 asserções musicais exercitadas em silício através de seis famílias de MCU (AVR 8-bit, Cortex-M0+, Cortex-M4F, Cortex-M7, Xtensa LX7) e cinco toolchains, validadas em 2026-06-02.

O que esta biblioteca não é

A fronteira está cravada pra manter o motor focado. Algumas coisas deliberadamente não pertencem aqui.

  • Não é um parser UMP. Dispatch de UMP recebido pertence à biblioteca de transporte. Use midi2cpp pra USB MIDI 2.0 ou o core C99 midi2 direto, e encaminhe os callbacks decodificados pro GingoMonitor.
  • Não é um stack USB. Gingoduino não cuida de descriptors, endpoints, ou alt settings. Combine com midi2cpp (USB MIDI 2.0), Arduino MIDI Library (MIDI 1.0 DIN/Serial), ou seu transport próprio.
  • Não é um responder MIDI-CI. Capability Inquiry, negociação de Profile e Property Exchange vivem em midi2. Gingoduino não expõe superfície MIDI-CI.
  • Não é um sintetizador. Eventos musicais são detectados e analisados; geração de som é território da aplicação.
  • Não é leitor nem gravador de arquivo MIDI. Standard MIDI File (SMF) I/O está fora do escopo. GingoSequence é uma estrutura de runtime, não um formato de arquivo.
  • Não é biblioteca de desktop. O alvo são MCUs. Testes rodam em desktop com g++, mas a API e o modelo de memória assumem restrições de embedded.

Licença

MIT License. Veja LICENSE.

Autor

Saulo Veríssimo