Lesson 16: The voicings of a chord
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Lesson 15 asked the chatbot which notes make a chord. This lesson asks the question a guitarist asks next: where to put the fingers. The router sends “voicings for Cmaj7” to the intent skill.chordvoicings, which runs ChordVoicingsSkill (GaPlugin.cs line 108); a comment in GA calls it the chatbot’s most used skill (ChordVoicingsSkill.cs line 14). It calls no model. TypedMusicalQueryExtractor reads a chord, a mode and technique words from the question, MusicalQueryEncoder turns them into a query vector, and the OPTIC-K index of lesson 3 returns the nearest voicings, filtered by the chord’s name (lines 75-167).
All GA links point to commit a826864, the course’s pin, unless they name another. Most of the voicing pipeline changed on GA’s main after the pin: between the pin and f4f5b4a (2026-10-01), git diff --stat lists 17 changed files among the skill, VoicingAgent, Search/, Embeddings/, VoicingDocumentFactory, the voicing domain services and the CLI that writes the index. The code that reads the question didn’t change: TypedMusicalQueryExtractor.cs is identical, and so are ChordPitchClasses, the skill’s CanHandle and its two expressions. So the program reads at the pin, and asks for the answers twice: at the pin, and in a second program, GaMain, built from a second clone of GA at f4f5b4a. Both compile the same questions, from code/ga-ai/Shared/ChordVoicingsProbe.cs. The outputs come from:
dotnet run --project code/ga-ai/GaAi -c Release -- l16dotnet run --project code/ga-ai/GaMain -c Release -- l16Which prompts reach the skill
Section titled “Which prompts reach the skill”On main, when the router can’t embed a question, it falls back on each skill’s CanHandle (SemanticIntentRouter.cs line 321, OrchestratorSkillIntent.cs line 29). ChordVoicingsSkill.CanHandle wants a voicing word and a chord (lines 57-73):
public bool CanHandle(string message) { if (string.IsNullOrWhiteSpace(message)) return false; var q = message.ToLowerInvariant(); // Whole-word match so a keyword embedded in an unrelated word doesn't // fire (e.g. "shell" inside "PowerShell" — see ga#261). var hasVoicingIntent = VoicingKeywords.Any(k => ChordIntentMatching.ContainsWord(q, k)); if (!hasVoicingIntent) return false; // Require a real chord token. The two regexes are case-sensitive on the // root so bare lowercase "a"/"e" in normal English ("show me a shape") // won't trigger. Strict form requires an accidental/quality/digit // immediately after the root (Cmaj7, G7, F#m); spaced form allows // "[A-G] major/minor/dim/aug/sus" with whitespace between root and // quality (Bb major, C minor). return ChordSuffixRegex().IsMatch(message) || ChordWithSpacedQualityRegex().IsMatch(message); }A chord is a capital root followed by a suffix, or by a quality word after a space (lines 178-185):
[GeneratedRegex(@"\b[A-G][#b]?(?:maj|min|m|M|dim|aug|sus|add|alt|°|Δ|11|13|5|6|7|9)\w*\b")] private static partial Regex ChordSuffixRegex();
// Spaced quality form: "C major", "Bb minor", "F# augmented". Quality word // accepts upper- and lower-case first letters but requires the root to be // an uppercase chord letter. [GeneratedRegex(@"\b[A-G][#b]?\s+(?:[Mm]ajor|[Mm]inor|[Mm]aj|[Mm]in|[Dd]im|[Aa]ug|[Ss]us)\b")] private static partial Regex ChordWithSpacedQualityRegex();The program starts the chatbot’s host as in lesson 15, takes the skill behind the intent and asks CanHandle each of its 12 example prompts (lines 31-45), then asks the host’s extractor what it reads in them:
== The skill's example prompts: CanHandle, and the chord TypedMusicalQueryExtractor readsprompt CanHandle chord read also readvoicings for Cmaj7 yes Cmaj7: C E G B tags forshow me Dm7 voicings yes Dm7: C D F Ashapes for F major yes F: C F A mode major, tags forfingerings for G7 yes G7: D F G B tags forCmaj9 voicings yes Cmaj9: C D E G Bdrop2 voicings of Cmaj7 yes Cmaj7: C E G B tags drop2shell voicing for Dm7 yes Dm7: C D F A tags shell forrootless A7 voicings yes A7: C# E G A tags rootlessquartal voicings in C no C: C E G tags quartalall C major voicings on guitar yes C: C E G mode major, tags all, instrument guitaropen chord shape for E minor yes E: E G# B mode minor, tags open forbarre voicings for Bb major yes Bb: D F A# mode major, tags for== Other phrasingsprompt CanHandle chord read also readvoicings for C no C: C E G tags forG chord shapes no G: D G Bhow do I play a D chord no D: D F# Avoicings for A minor yes A: C# E A mode minor, tags forvoicings for Am yes Am: C E A tags forvoicings for am no no chord tags forBb voicings please no Bb: D F A#voicings for F#m7b5 yes F#m7b5: C E F# A tags forvoicings for C7(b9) yes C7(b9): C C# E G A# tags forvoicings for C/G no C/G: C E G tags forCanHandlerejects one of its own examples, and every bare major chord. “quartal voicings in C” has a voicing word, but a root alone isn’t a chord for either expression. “voicings for C”, “G chord shapes” and “Bb voicings please” are rejected the same way, and “voicings for C/G” too:/isn’t a suffix. “how do I play a D chord” has no voicing word.- “minor” is read as a mode, and the chord as a major triad. The extractor takes the first capitalized word that
ChordPitchClassesparses as the chord (TypedMusicalQueryExtractor.cslines 90-102); “A” parses as A major, and “minor” is in the list of modes (lines 104-111). “voicings for A minor” reads A major, and so does the example “open chord shape for E minor” for E.CanHandle’s comment gives “C minor” as a form it accepts. - A partial reading keeps the model away. The host’s extractor asks a model only when the typed reading finds nothing (lines 217-226). “voicings for am” reads no chord, and in production a model would get it, which the course doesn’t run. “voicings for A minor” reads A major, so no model ever sees it.
The symbols and the spelled-out qualities
Section titled “The symbols and the spelled-out qualities”ChordPitchClasses builds a chord from its suffix rather than looking it up. It folds the suffix’s variants, takes its alterations, additions and omissions, then its triad and its extensions, and refuses a suffix that leaves something it can’t explain (MusicalQueryEncoder.cs lines 240-336):
// ── normalize symbol variants (case matters: 'M7' = major7, 'm' = minor) ── w = Regex.Replace(w, "△|Δ", "maj"); w = Regex.Replace(w, "ø|Ø", "m7b5"); w = Regex.Replace(w, @"M(?=7|9|11|13|6)", "maj"); // M7 / M9 / M13 → major-7th family w = w.Replace("Maj", "maj").Replace("MAJ", "maj").Replace("Major", "maj").Replace("major", "maj"); w = w.Replace("Min", "min").Replace("MIN", "min").Replace("minor", "min"); w = w.Replace("M", "maj"); // any lone uppercase M ⇒ major // The major-7th marker is now canonical, so folding to lower case leaves a residual // 'm' meaning unambiguously minor. w = w.ToLowerInvariant(); w = w.Replace("°", "dim").Replace("o7", "dim7"); // residue check: anything left (besides separators) means it was not a chord symbol. var residue = Regex.Replace(w, @"[\s/()+\-]", ""); if (residue.Length > 0) return false;The program asks for the 51 symbols of lesson 15 as “voicings for C…” and for the 31 spelled-out qualities as “voicings for C …”, and compares the chord the extractor reads with a textbook’s:
== The 51 symbols of lesson 15, asked as "voicings for C…"read as a textbook spells them: 47 of 51 Cdom7: no chord, a textbook's C E G A# Cma7: no chord, a textbook's C E G B C-7: C-7: C E G A#, a textbook's C D# G A# C7+5: no chord, a textbook's C E G# A#symbols ChordPitchClasses and ChordVocabulary read as different chords: 5: Cdom7 Cma7 CΔ7 C-7 C7+5== The 31 spelled-out qualities, asked as "voicings for C …"CanHandle accepts 13 of 31; read as a textbook spells them: 1 read as C: C E G: 31, major, minor, diminished, augmented, power, dominant, …- 47 of the 51 symbols are read as a textbook spells them.
dom7,ma7and7+5leave letters or a digit the parser can’t explain, and the question reads no chord.C-7is read as C7: the residue check takes-for a separator, so the minor sign is dropped and the seventh is a dominant one. - The chatbot has two readers of chord symbols, and they disagree on five of the 51.
ChordVocabulary, whichChordInfoSkilluses (lesson 15), mapsdom7to a dominant seventh,ma7to a major seventh,-7to a minor seventh and7+5to a dominant seventh with a sharp fifth (ChordVocabulary.cslines 81-87);ChordPitchClassesreads no chord for three of them and a dominant seventh for-7. The fifth,CΔ7, is the vocabulary’sδ7of lesson 15. - Every spelled-out quality is read as C major. The parser reads “C” alone, and the words after it become a mode, tags or nothing. Only “major” comes out right.
CanHandleaccepts 13 of the 31, those that start with “major” or “minor”: in its spaced expression,dimandaugmust end a word, and “diminished” and “augmented” go on.
What the answers play, at the pin
Section titled “What the answers play, at the pin”The skill’s answer lists each voicing with a name and a diagram. The program plays the diagram: each string that sounds gives its open note plus its fret, and the voicing’s pitch classes are the set of those notes. It compares them with the chord the prompt asks for, as a textbook spells it: exact when the voicing plays those notes and no other, more when it plays them and others, part when it plays only some of them, other otherwise. At the pin, the diagram is GA’s string, string 1, the high E, first (difference 13 of the journal’s entry of 2026-09-14, fixed on main):
== The example prompts' answers, at the pin: what each voicing playsprompt voicings exact more part othervoicings for Cmaj7 2 2 0 0 0show me Dm7 voicings 1 0 0 0 1shapes for F major 8 8 0 0 0fingerings for G7 8 1 0 7 0Cmaj9 voicings 8 0 0 8 0drop2 voicings of Cmaj7 2 2 0 0 0shell voicing for Dm7 1 0 0 0 1rootless A7 voicings 5 0 0 5 0quartal voicings in C 8 0 0 0 8all C major voicings on guitar 8 2 0 3 3open chord shape for E minor 8 0 0 0 8barre voicings for Bb major 8 6 0 2 0== "show me Dm7 voicings": the answer, and what each voicing plays (asked: C D F A)Found 1 voicing:name diagram score playsBm7(shell)/D x-0-2-0-x-x 0.342 D A B other- At the pin, Dm7 gets one voicing, and it isn’t Dm7.
Bm7(shell)/Dplays D, A and B. At the pin, the name filter looked for the quality as a substring of the stored name and took the lowest note for the root (OptickSearchStrategy.cslines 315-324):m7is inBm7(shell)/D, and its lowest note is D. That is difference 11 of the journal, fixed onmain.
The program then asks for 144 chords, 12 qualities on 12 roots, as “voicings for
== A grid of 144 chords, at the pin: "voicings for <chord>" on 12 rootsFor each quality, the roots whose answer has only voicings that play the chord, some, or none;the corpus holds every chord of the grid. Then what the voicings that aren't the chord play.quality all some none the other voicings playmajor 2 9 1 other 20, part 15m 1 9 2 other 20, part 87 0 5 7 other 7, part 38maj7 1 8 3 more 2, other 9, part 26m7 0 6 6 other 12, part 26dim 3 8 1 more 11, other 10, part 1aug 8 4 0 more 1, other 2, part 1sus4 2 5 5 more 2, other 12, part 34m7b5 0 6 6 other 5, part 32dim7 6 1 5 other 1, part 466 0 2 10 more 1, other 1, part 849 1 1 10 other 7, part 70voicings returned: 841; exact 337, more 17, part 381, other 106- At the pin, 337 of 841 voicings play the chord asked, and for 56 of the 144 chords none does. Most of the others play part of the chord (381), like the G7 shells without their fifth.
The same questions on main
Section titled “The same questions on main”GaMain builds lesson 3’s corpus and index with main’s generator, analysis, embedding and writer, and builds the skill as the host does (ServiceCollectionExtensions.cs lines 78-111, unchanged on main). On main, the skill writes its diagrams in chart order, low E first (ChordVoicingsSkill.cs line 136):
== Corpus on main: GA's VoicingGenerator, standard tuning, 3 frets, window 3, at least 3 notesvoicings 15360the names main's analysis gives the voicings of five chords, with their counts: C6, C E G A Am7/G 24, Am7/E 17, Am7 6, Am7/C 2 C6/9, C D E G A C6/E 11, C6/G 11, C6 1, C6/A 1 Dsus4, D G A Gsus2 24, Gsus2/A 13, Gsus2/D 2 Dbaug, Db F A Faug 14, Faug/A 7 Caug, C E G# Caug/E 14, Caug 6, Caug/Ab 1== The example prompts' answers, on main: what each voicing playsprompt voicings exact more part othervoicings for Cmaj7 8 8 0 0 0show me Dm7 voicings 8 8 0 0 0shapes for F major 8 8 0 0 0fingerings for G7 8 8 0 0 0Cmaj9 voicings 8 8 0 0 0drop2 voicings of Cmaj7 8 8 0 0 0shell voicing for Dm7 8 8 0 0 0rootless A7 voicings 8 8 0 0 0quartal voicings in C 8 0 0 0 8all C major voicings on guitar 8 8 0 0 0open chord shape for E minor 8 0 0 0 8barre voicings for Bb major 8 8 0 0 0== A grid of 144 chords, on main: "voicings for <chord>" on 12 rootsFor each quality, the roots whose answer has only voicings that play the chord, some, or none;the corpus holds every chord of the grid. Then what the voicings that aren't the chord play.quality all some none the other voicings playmajor 10 2 0 more 4m 10 2 0 more 37 12 0 0maj7 10 2 0 more 2m7 10 2 0 part 2dim 10 2 0 more 6aug 4 0 8 more 61sus4 3 3 6 more 57, part 1m7b5 11 1 0 more 1dim7 12 0 06 0 0 12 more 749 8 4 0 more 7voicings returned: 1097; exact 879, more 215, part 3, other 0- On
main, every voicing the skill returns plays the chord it read. 10 of the 12 examples get 8 voicings of their chord; the two others get the chord misread, E major for “E minor” and C major for “quartal voicings in C”. On the grid, 879 of 1,097 voicings play the chord exactly, none plays another chord (other 0), and the rest play the chord and more (215) or part of it (3). - The 6th chords, eight augmented and six sus4 triads get no voicing of their own, though the corpus holds them all. The index names C E G A “Am7”, never “C6”, and names the 6/9 C D E G A “C6”: “voicings for C6” gets 6/9 voicings named C6. It names D G A “Gsus2” and D♭ F A “Faug”. The name filter compares the root and the quality of the stored name with the request’s (
OptickSearchStrategy.cslines 307-319), and cuts the stored name at its first parenthesis (lines 348-349), soDbaug(maj7)/C, D♭ F A plus C, passes as D♭ augmented. When no stored name passes, the skill searches again without the filter (lines 109-115), and the nearest voicings to Dsus4 are D7sus4.
var filterChord = filters.ChordName is { Length: > 0 } cn ? ParseChordName(cn) : null;
foreach (var r in pool) { var d = r.Document; if (filterChord is { } fc) { if (ParseChordName(d.ChordName) is not { } docChord) continue; if (docChord.RootPitchClass != fc.RootPitchClass) continue; if (!string.Equals(docChord.Quality, fc.Quality, StringComparison.Ordinal)) continue; if (fc.BassPitchClass is int bass && (d.MidiNotes.Length == 0 || ((d.MidiNotes.Min() % 12) + 12) % 12 != bass)) continue; }The techniques
Section titled “The techniques”Six of the 12 example prompts name a technique, and the skill’s description promises them: “drop2, shell, rootless, quartal, barre” (lines 24-29). The program checks each voicing of main’s answers with the course’s definitions, all read from the diagram:
- drop2: four notes on four strings, the chord’s four notes, and raising the lowest by an octave gives a close position in which it is the second note from the top;
- shell: the root, the third and the seventh, nothing else;
- rootless: at least three chord tones, without the root;
- quartal: at least three notes stacked in perfect fourths from the root; “quartal voicings in C” is taken as C F B♭;
- open: the chord with at least one open string;
- barre: the chord with no open string, and the same fret on the lowest and the highest strings that sound.
The table counts the voicings that play the chord, those that are the technique, and the corpus voicings that are the technique:
== The techniques the example prompts name, on mainprompt voicings chord technique in the corpusdrop2 voicings of Cmaj7 8 8 2 3shell voicing for Dm7 8 8 0 19rootless A7 voicings 8 8 0 45quartal voicings in C 8 0 0 1open chord shape for E minor 8 0 0 60barre voicings for Bb major 8 8 3 11== "open chord shape for E minor": the answer, and what each voicing plays (asked: E G B)Found 8 voicings:name diagram score playsE/B x-2-x-1-x-0 0.635 E G# B otherE 0-x-x-1-0-x 0.635 E G# B otherE 0-2-x-1-x-x 0.635 E G# B otherE 0-2-x-1-x-0 0.635 E G# B otherE 0-2-x-1-0-0 0.635 E G# B otherE/Ab x-x-x-1-0-0 0.632 E G# B otherE x-x-2-1-0-x 0.632 E G# B otherE x-x-2-1-0-0 0.632 E G# B other- 5 of the 48 voicings are the technique asked, 2 drop2 and 3 barre voicings; none is a shell, a rootless A7, a quartal voicing or an open E minor, though the corpus holds 19, 45, 1 and 60 of them.
- GA drops the techniques on purpose, and tells its telemetry, not the guitarist. Its decision record ADR-0002 explains that the index carries only the diagram, the inferred name, the instrument and the MIDI notes, so the index path honours the chord name, the pitch range, the instrument and the comfort filters, and declares the others dropped,
TagsandModeNameamong them (OptickSearchStrategy.cslines 101-130,0002-voicing-filter-parity-cpu-gpu-only.md).VoicingAgentwrites the dropped filters to its telemetry log (VoicingAgent.cslines 110-126);ChordVoicingsSkillrecords nothing, and its answer starts “Found 8 voicings:”. - The six techniques can be read from the diagram and the tuning, as the course does here. ADR-0002 applies the comfort filters on the index path for that reason: every strategy carries the diagram.
Where the course stops
Section titled “Where the course stops”- The corpus is lesson 3’s, 15,360 voicings on three frets, not the production index. Among more voicings, the nearest ones may differ (to verify).
- The router and the model aren’t run. Which intent the router picks for these prompts, and what a model reads in “voicings for am”, need the embeddings and the model (to verify).
- The definitions of the techniques are the course’s, and so is the reading of “quartal voicings in C”.
GaMainbuilds the skill as the host does, without the host. The host’s wiring is unchanged onmain; that it builds the same objects comes from reading it.
Reported upstream
Section titled “Reported upstream”- Reported after this lesson was written: GA issue #785 for “minor” and the spelled-out qualities read as a major triad,
CanHandle’s bare chords andChordPitchClasses’s four symbols; #784 for the index’s names that the filter can’t find and the techniques dropped without a word in the answer. All are listed in the journal.
Exercises
Section titled “Exercises”- Ask
main’s skill “voicings for Am”, then “voicings for A minor”. Which chord do the voicings of each answer play? - How many of the grid’s 144 prompts does
CanHandleaccept, and which does it reject? - What does
CanHandleanswer to “voicings for C-7”? What wouldmain’s skill answer if the router sent it the question anyway? - On
main, for which four of the 12 roots does “voicings foraug” get only augmented triads? Why four?
Solutions
- “voicings for Am” gets eight voicings of A minor, A C E; “voicings for A minor” gets eight voicings of A major, A C♯ E. The first is read as the symbol
Am, the second as the chordAand the modeminor, which the index drops. Checked by runningGaMain’s skill outside the course’s expected output. - 132: it rejects the 12 bare major chords, “voicings for C” to “voicings for B”, whose root has neither a suffix nor a quality word. Checked by running
CanHandleon the 144 prompts, outside the course’s expected output. - It rejects it: after the root,
-matches neither expression. Asked directly,main’s skill reads C7 and answers eight voicings of C E G B♭. Checked by runningGaMain’s skill outside the course’s expected output. - C, D, F and G. An augmented triad divides the octave into three major thirds, so the same three notes have three names, and the index stores one of them: Caug for C E G♯, Daug for D F♯ A♯, Faug for D♭ F A and Gaug for E♭ G B. The filter accepts only the stored name. The eight other roots get augmented triads with a major seventh, whose name the filter cuts at its parenthesis. Checked by running
GaMain’s skill outside the course’s expected output.
Key takeaways
Section titled “Key takeaways”- A voicing can be checked without a textbook: the diagram and the tuning give its notes.
- On
main, every voicing the skill returns plays the chord it read; what remains wrong is in the reading and in the names. - A partial reading keeps the fallback away: the typed reader takes “A” from “A minor”, and the model never sees the question.
- A filter on names needs one name per chord: C6 and Am7, Dsus4 and Gsus2, and each augmented triad are one set of notes with several names.
- A filter that is dropped should reach the answer, not only the telemetry.
Sources
Section titled “Sources”- GuitarAlchemist/ga at
a826864:Common/GA.Business.ML/Agents/Skills/ChordVoicingsSkill.cs,Common/GA.Business.ML/Search/TypedMusicalQueryExtractor.cs,Common/GA.Business.ML/Search/MusicalQueryEncoder.cs(ChordPitchClasses),Common/GA.Business.ML/Agents/ChordVocabulary.cs,Apps/GaChatbot.Api/Extensions/ServiceCollectionExtensions.cs,Common/GA.Business.Core.Orchestration/Plugins/GaPlugin.cs. - GuitarAlchemist/ga at
f4f5b4a: the same skill and extractor,Common/GA.Business.ML/Search/OptickSearchStrategy.cs,Common/GA.Business.ML/Agents/Intents/SemanticIntentRouter.cs,Common/GA.Business.Core.Orchestration/Intents/OrchestratorSkillIntent.cs,docs/adr/0002-voicing-filter-parity-cpu-gpu-only.md. - The course’s programs:
code/ga-ai/GaAi/Lesson16.cs,code/ga-ai/GaMain/Program.cs,code/ga-ai/Shared/ChordVoicingsProbe.cs,code/ga-ai/fetch-ga.sh.