Skip to content

Commit 4dfc780

Browse files
feat(fretboard): add guitar/mandolin aliases, alternate tunings, open chords, commonChords
- guitar()/violao() and mandolin()/bandolim() as English/PT-BR alias pairs - dropD(), openG(), dadgad() factory methods with PROGMEM tuning tables - setString(string, midiNote) for single-string retuning - commonChords(scale, output, max) — best fingering per diatonic degree - isOpenFingering(fg) and openFingerings(chord, output, max) — first-position shapes - 32 new tests added (409 total, 0 failures)
1 parent 94173d6 commit 4dfc780

5 files changed

Lines changed: 240 additions & 13 deletions

File tree

README.md

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Tiers auto-select based on platform, or override with `#define GINGODUINO_TIER N
4343
- Harmonic field analysis with T/S/D functions and roles (+ deduce from notes/chords)
4444
- Harmonic tree (directed graph, major/minor, classical + jazz traditions)
4545
- Progression analysis: identify, deduce (ranked), predict (next branch)
46-
- Fretboard engine: violao, cavaquinho, bandolim, ukulele with fingering scoring
46+
- Fretboard engine: guitar/violao, cavaquinho, mandolin/bandolim, ukulele; alternate tunings (Drop D, Open G, DADGAD); common chords, open-position fingerings
4747
- Musical events (note, chord, rest) and sequences with tempo/time signature
4848
- Real-time harmonic monitor with chord/field detection and per-note context
4949
- MIDI 1.0 parser (running status, SysEx) and stateless dispatcher
@@ -52,7 +52,7 @@ Tiers auto-select based on platform, or override with `#define GINGODUINO_TIER N
5252
- Chord comparison: 17 dimensions including Neo-Riemannian transforms and Forte vectors
5353
- Fixed-size arrays, no dynamic allocation, PROGMEM support
5454
- Compatible with Arduino IDE, PlatformIO, and ESP-IDF
55-
- 384 native tests passing
55+
- 409 native tests passing
5656

5757
### Installation
5858

@@ -200,23 +200,45 @@ field.signature(); // 0
200200
201201
#### GingoFretboard
202202
```cpp
203-
GingoFretboard guitar = GingoFretboard::violao(); // 6 strings, 19 frets
204-
// Also: ::cavaquinho(), ::bandolim(), ::ukulele()
203+
// Standard instruments
204+
GingoFretboard guitar = GingoFretboard::guitar(); // 6 strings, E A D G B E
205+
GingoFretboard viol = GingoFretboard::violao(); // same tuning, Portuguese alias
206+
// Also: ::cavaquinho(), ::mandolin(), ::bandolim(), ::ukulele()
205207
206-
guitar.noteAt(0, 5); // GingoNote("A") — string 0, fret 5
207-
guitar.midiAt(0, 0); // 40 (E2)
208+
// Alternate tunings
209+
GingoFretboard dd = GingoFretboard::dropD(); // D A D G B E
210+
GingoFretboard og = GingoFretboard::openG(); // D G D G B D
211+
GingoFretboard dg = GingoFretboard::dadgad(); // D A D G A D
212+
213+
// Retune a single string
214+
guitar.setString(0, 38); // lower string 0 to D2 (drop D by hand)
215+
216+
// Note / MIDI lookup
217+
guitar.noteAt(0, 5); // GingoNote("A") — string 0, fret 5
218+
guitar.midiAt(0, 0); // 40 (E2)
208219
209220
GingoFretPos positions[48];
210221
guitar.positions(GingoNote("E"), positions, 48);
211222
guitar.scalePositions(scale, positions, 48, 0, 4);
212223
224+
// Fingerings
213225
GingoFingering fg;
214-
guitar.fingering(GingoChord("CM"), 0, fg); // best fingering at position 0
226+
guitar.fingering(GingoChord("CM"), 0, fg); // fingering at position 0
215227
216228
GingoFingering fgs[5];
217-
guitar.fingerings(GingoChord("CM"), fgs, 5); // up to 5 fingerings
229+
guitar.fingerings(GingoChord("CM"), fgs, 5); // up to 5 fingerings, sorted by score
230+
231+
// Open-position shapes only (open string + frets 1-4)
232+
GingoFingering opens[5];
233+
guitar.openFingerings(GingoChord("GM"), opens, 5);
234+
guitar.isOpenFingering(opens[0]); // true
235+
236+
// Common chords for a scale (best fingering per degree)
237+
GingoFingering ccs[7];
238+
guitar.commonChords(GingoScale("G", SCALE_MAJOR), ccs, 7);
239+
// ccs[]: GM, Am, Bm, CM, DM, Em, F#dim — sorted by field degree
218240
219-
GingoFretboard capo2 = guitar.capo(2); // transposed fretboard
241+
GingoFretboard capo2 = guitar.capo(2); // transposed fretboard
220242
```
221243

222244
#### GingoEvent & GingoSequence (Tier 3)
@@ -478,7 +500,7 @@ Tiers auto-detectados por plataforma, ou force com `#define GINGODUINO_TIER N`.
478500
- Analise de campo harmonico com funcoes T/S/D e roles (+ deduce a partir de notas/acordes)
479501
- Arvore harmonica (grafo dirigido, major/minor, tradicoes classica + jazz)
480502
- Analise de progressao: identify, deduce (ranked), predict (proximo branch)
481-
- Engine de braco: violao, cavaquinho, bandolim, ukulele com scoring de digitacao
503+
- Engine de braco: guitar/violao, cavaquinho, mandolin/bandolim, ukulele; afinacoes alternativas (Drop D, Open G, DADGAD); acordes comuns, digitacoes em primeira posicao
482504
- Eventos musicais (nota, acorde, pausa) e sequencias com tempo/compasso
483505
- Monitor harmonico em tempo real com deteccao de acordes/campos e contexto por nota
484506
- Parser MIDI 1.0 (running status, SysEx) e dispatcher stateless
@@ -487,7 +509,7 @@ Tiers auto-detectados por plataforma, ou force com `#define GINGODUINO_TIER N`.
487509
- Comparacao de acordes: 17 dimensoes incluindo transformacoes Neo-Riemannian e vetores Forte
488510
- Arrays de tamanho fixo, sem alocacao dinamica, suporte PROGMEM
489511
- Compativel com Arduino IDE, PlatformIO e ESP-IDF
490-
- 384 testes nativos passando
512+
- 409 testes nativos passando
491513

492514
### Instalacao
493515

extras/tests/test_native.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,78 @@ void testFretboard() {
792792
GingoFretboard uke = GingoFretboard::ukulele();
793793
CHECK(uke.numStrings() == 4, "ukulele numStrings=4");
794794
CHECK(strcmp(uke.name(), "Ukulele") == 0, "ukulele name");
795+
796+
// English aliases
797+
GingoFretboard eng = GingoFretboard::guitar();
798+
CHECK(eng.numStrings() == 6, "guitar() numStrings=6");
799+
CHECK(strcmp(eng.name(), "Guitar") == 0, "guitar() name=Guitar");
800+
CHECK(eng.openMidi(0) == 40, "guitar() same tuning as violao");
801+
802+
GingoFretboard mand = GingoFretboard::mandolin();
803+
CHECK(mand.numStrings() == 4, "mandolin() numStrings=4");
804+
CHECK(strcmp(mand.name(), "Mandolin") == 0, "mandolin() name=Mandolin");
805+
CHECK(mand.openMidi(0) == 55, "mandolin() open string 0 = G3 (55)");
806+
807+
// Alternate tunings
808+
GingoFretboard dd = GingoFretboard::dropD();
809+
CHECK(dd.numStrings() == 6, "dropD numStrings=6");
810+
CHECK(dd.openMidi(0) == 38, "dropD string 0 = D2 (38)");
811+
CHECK(dd.openMidi(1) == 45, "dropD string 1 = A2 (45)");
812+
{
813+
GingoNote dn = dd.noteAt(0, 0);
814+
CHECK(strcmp(dn.name(), "D") == 0, "dropD string 0 open = D");
815+
}
816+
817+
GingoFretboard og = GingoFretboard::openG();
818+
CHECK(og.openMidi(0) == 38, "openG string 0 = D2 (38)");
819+
CHECK(og.openMidi(1) == 43, "openG string 1 = G2 (43)");
820+
{
821+
GingoNote gn = og.noteAt(1, 0);
822+
CHECK(strcmp(gn.name(), "G") == 0, "openG string 1 open = G");
823+
}
824+
825+
GingoFretboard dg = GingoFretboard::dadgad();
826+
CHECK(dg.openMidi(0) == 38, "dadgad string 0 = D2 (38)");
827+
CHECK(dg.openMidi(4) == 57, "dadgad string 4 = A3 (57)");
828+
{
829+
GingoNote an = dg.noteAt(4, 0);
830+
CHECK(strcmp(an.name(), "A") == 0, "dadgad string 4 open = A");
831+
}
832+
833+
// setString (retune)
834+
GingoFretboard custom = GingoFretboard::guitar();
835+
custom.setString(0, 38); // lower to D2 (drop D)
836+
CHECK(custom.openMidi(0) == 38, "setString: string 0 retuned to 38");
837+
{
838+
GingoNote dn2 = custom.noteAt(0, 0);
839+
CHECK(strcmp(dn2.name(), "D") == 0, "setString: string 0 = D after retune");
840+
}
841+
CHECK(custom.openMidi(1) == 45, "setString: other strings unchanged");
842+
843+
// commonChords
844+
GingoScale gMaj("G", SCALE_MAJOR);
845+
GingoFingering ccs[7];
846+
uint8_t ncc = guitar.commonChords(gMaj, ccs, 7);
847+
CHECK(ncc > 0, "commonChords(G major) returns results");
848+
printf(" commonChords(G major): %d chords\n", ncc);
849+
for (uint8_t i = 0; i < ncc; i++) {
850+
printf(" [%d] %s score=%d\n", i, ccs[i].chordName.c_str(), ccs[i].score);
851+
}
852+
853+
// openFingerings / isOpenFingering
854+
GingoFingering opens[5];
855+
uint8_t nopen = guitar.openFingerings(GingoChord("GM"), opens, 5);
856+
CHECK(nopen > 0, "openFingerings(GM) found open shape(s)");
857+
if (nopen > 0) {
858+
CHECK(guitar.isOpenFingering(opens[0]), "first result of openFingerings is open");
859+
printf(" openFingerings(GM): %d open shapes found\n", nopen);
860+
}
861+
862+
// Bandolim alias
863+
GingoFretboard bnd = GingoFretboard::bandolim();
864+
CHECK(bnd.numStrings() == 4, "bandolim numStrings=4");
865+
CHECK(strcmp(bnd.name(), "Bandolim") == 0, "bandolim name=Bandolim");
866+
CHECK(bnd.openMidi(0) == mand.openMidi(0), "bandolim same tuning as mandolin");
795867
}
796868

797869
// =====================================================================

src/GingoFretboard.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ GingoFretboard::GingoFretboard(const char* name,
3333
// Factories
3434
// ---------------------------------------------------------------------------
3535

36+
GingoFretboard GingoFretboard::guitar(uint8_t numFrets) {
37+
return GingoFretboard("Guitar", data::TUNING_VIOLAO, 6, numFrets);
38+
}
39+
40+
GingoFretboard GingoFretboard::mandolin(uint8_t numFrets) {
41+
return GingoFretboard("Mandolin", data::TUNING_BANDOLIM, 4, numFrets);
42+
}
43+
3644
GingoFretboard GingoFretboard::violao(uint8_t numFrets) {
3745
return GingoFretboard("Violao", data::TUNING_VIOLAO, 6, numFrets);
3846
}
@@ -49,6 +57,18 @@ GingoFretboard GingoFretboard::ukulele(uint8_t numFrets) {
4957
return GingoFretboard("Ukulele", data::TUNING_UKULELE, 4, numFrets);
5058
}
5159

60+
GingoFretboard GingoFretboard::dropD(uint8_t numFrets) {
61+
return GingoFretboard("DropD", data::TUNING_DROP_D, 6, numFrets);
62+
}
63+
64+
GingoFretboard GingoFretboard::openG(uint8_t numFrets) {
65+
return GingoFretboard("OpenG", data::TUNING_OPEN_G, 6, numFrets);
66+
}
67+
68+
GingoFretboard GingoFretboard::dadgad(uint8_t numFrets) {
69+
return GingoFretboard("DADGAD", data::TUNING_DADGAD, 6, numFrets);
70+
}
71+
5272
// ---------------------------------------------------------------------------
5373
// Info
5474
// ---------------------------------------------------------------------------
@@ -308,6 +328,76 @@ bool GingoFretboard::identify(const uint8_t* stringFrets, uint8_t count,
308328
return GingoChord::identify(notes, noteCount, output, maxLen);
309329
}
310330

331+
// ---------------------------------------------------------------------------
332+
// Retune
333+
// ---------------------------------------------------------------------------
334+
335+
void GingoFretboard::setString(uint8_t string, uint8_t midiNote) {
336+
if (string < numStrings_) {
337+
openMidi_[string] = midiNote;
338+
}
339+
}
340+
341+
// ---------------------------------------------------------------------------
342+
// Common chords
343+
// ---------------------------------------------------------------------------
344+
345+
uint8_t GingoFretboard::commonChords(const GingoScale& scale,
346+
GingoFingering* output, uint8_t maxResults) const {
347+
GingoField field(scale.tonic().name(), scale.parent());
348+
GingoChord fieldChords[7];
349+
uint8_t numChords = field.chords(fieldChords, 7);
350+
uint8_t written = 0;
351+
352+
for (uint8_t i = 0; i < numChords && written < maxResults; i++) {
353+
// Try the first few positions and keep the best-scoring fingering
354+
GingoFingering best;
355+
bool bestValid = false;
356+
for (uint8_t pos = 0; pos < 4; pos++) {
357+
GingoFingering fg;
358+
if (fingering(fieldChords[i], pos, fg)) {
359+
if (!bestValid || fg.score < best.score) {
360+
best = fg;
361+
bestValid = true;
362+
}
363+
}
364+
}
365+
if (bestValid) {
366+
output[written++] = best;
367+
}
368+
}
369+
return written;
370+
}
371+
372+
// ---------------------------------------------------------------------------
373+
// Open fingerings
374+
// ---------------------------------------------------------------------------
375+
376+
bool GingoFretboard::isOpenFingering(const GingoFingering& fg) const {
377+
bool hasOpen = false;
378+
for (uint8_t i = 0; i < fg.numStrings; i++) {
379+
if (fg.strings[i].action == STRING_OPEN) {
380+
hasOpen = true;
381+
} else if (fg.strings[i].action == STRING_FRETTED && fg.strings[i].fret > 4) {
382+
return false;
383+
}
384+
}
385+
return hasOpen;
386+
}
387+
388+
uint8_t GingoFretboard::openFingerings(const GingoChord& chord,
389+
GingoFingering* output, uint8_t maxResults) const {
390+
GingoFingering all[GINGODUINO_MAX_FINGERINGS + 4];
391+
uint8_t count = fingerings(chord, all, GINGODUINO_MAX_FINGERINGS + 4);
392+
uint8_t written = 0;
393+
for (uint8_t i = 0; i < count && written < maxResults; i++) {
394+
if (isOpenFingering(all[i])) {
395+
output[written++] = all[i];
396+
}
397+
}
398+
return written;
399+
}
400+
311401
// ---------------------------------------------------------------------------
312402
// Capo
313403
// ---------------------------------------------------------------------------

src/GingoFretboard.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "GingoNote.h"
1515
#include "GingoChord.h"
1616
#include "GingoScale.h"
17+
#include "GingoField.h"
1718

1819
namespace gingoduino {
1920

@@ -77,18 +78,33 @@ class GingoFretboard {
7778
uint8_t numStrings,
7879
uint8_t numFrets = 19);
7980

80-
/// Factory: standard 6-string guitar (violao).
81+
/// Factory: standard 6-string guitar (standard tuning).
82+
static GingoFretboard guitar(uint8_t numFrets = 19);
83+
84+
/// Factory: mandolin (4 strings, G D A E).
85+
static GingoFretboard mandolin(uint8_t numFrets = 17);
86+
87+
/// Factory: standard 6-string guitar — Portuguese alias for guitar().
8188
static GingoFretboard violao(uint8_t numFrets = 19);
8289

8390
/// Factory: cavaquinho (4 strings).
8491
static GingoFretboard cavaquinho(uint8_t numFrets = 17);
8592

86-
/// Factory: bandolim / mandolin (4 strings).
93+
/// Factory: bandolim — Portuguese alias for mandolin().
8794
static GingoFretboard bandolim(uint8_t numFrets = 17);
8895

8996
/// Factory: ukulele (4 strings).
9097
static GingoFretboard ukulele(uint8_t numFrets = 17);
9198

99+
/// Factory: drop D tuning (D A D G B E).
100+
static GingoFretboard dropD(uint8_t numFrets = 19);
101+
102+
/// Factory: open G tuning (D G D G B D).
103+
static GingoFretboard openG(uint8_t numFrets = 19);
104+
105+
/// Factory: DADGAD tuning (D A D G A D).
106+
static GingoFretboard dadgad(uint8_t numFrets = 19);
107+
92108
/// Instrument name.
93109
const char* name() const { return name_.c_str(); }
94110

@@ -140,6 +156,24 @@ class GingoFretboard {
140156
bool identify(const uint8_t* stringFrets, uint8_t count,
141157
char* output, uint8_t maxLen) const;
142158

159+
/// Retune a single string to a new MIDI note.
160+
void setString(uint8_t string, uint8_t midiNote);
161+
162+
/// Find the best fingering for each diatonic chord of the given scale.
163+
/// Uses the scale's harmonic field (triads). Returns number of fingerings written.
164+
/// Note: uses the parent scale type — modes return their parallel major/minor field.
165+
uint8_t commonChords(const GingoScale& scale,
166+
GingoFingering* output, uint8_t maxResults) const;
167+
168+
/// Return true if this fingering uses at least one open string
169+
/// and all fretted notes are within the first 4 frets.
170+
bool isOpenFingering(const GingoFingering& fg) const;
171+
172+
/// Find open-position fingerings for a chord (open strings, frets 1-4).
173+
/// Returns the number of fingerings written.
174+
uint8_t openFingerings(const GingoChord& chord,
175+
GingoFingering* output, uint8_t maxResults) const;
176+
143177
/// Create a new fretboard with a capo at the given fret.
144178
GingoFretboard capo(uint8_t fret) const;
145179

src/gingoduino_progmem.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,15 @@ static const uint8_t TUNING_BANDOLIM[4] PROGMEM = { 55, 62, 69, 76 };
540540
// Ukulele — G4 C4 E4 A4
541541
static const uint8_t TUNING_UKULELE[4] PROGMEM = { 67, 60, 64, 69 };
542542

543+
// Drop D — D2 A2 D3 G3 B3 E4
544+
static const uint8_t TUNING_DROP_D[6] PROGMEM = { 38, 45, 50, 55, 59, 64 };
545+
546+
// Open G — D2 G2 D3 G3 B3 D4
547+
static const uint8_t TUNING_OPEN_G[6] PROGMEM = { 38, 43, 50, 55, 59, 62 };
548+
549+
// DADGAD — D2 A2 D3 G3 A3 D4
550+
static const uint8_t TUNING_DADGAD[6] PROGMEM = { 38, 45, 50, 55, 57, 62 };
551+
543552
#endif // GINGODUINO_HAS_FRETBOARD
544553

545554
// ===================================================================

0 commit comments

Comments
 (0)