Lesson 21: The voicing search
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 20 ended on a note of ga_generate_progression: “Compose by passing each chord to ga_search_voicings”. ga_search_voicings is GaMcpServer’s search of the OPTIC-K index: it reads a chord, a mode and style or technique tags out of a query, turns them into a query vector, and returns the nearest voicings (VoicingSearchTool.cs lines 92-236). A second tool, ga_voicing_vocabulary, lists what the search reads, so that an agent can rewrite its user’s words before searching (VoicingVocabularyTool.cs lines 24-78). GA’s voicing-search skill tells Claude Code to call the vocabulary once, then the search (.claude/skills/voicing-search/SKILL.md line 30). Lessons 3 and 16 opened the reader and the search from the chatbot’s side; this lesson asks the tool.
All GA links point to commit a826864, the course’s pin, unless they name another. On GA’s main at f4f5b4a (2026-10-01), the two tools differ only in a comment and a description, and the reader and the tag registry are unchanged; the query encoder and the search changed, so GaMain asks again the questions whose answers depend on them. The program compiles GA’s own VoicingSearchTool.cs, which now also serves lessons 19 and 20 in place of the course’s stand-in. It finds the course’s index through GA_OPTICK_INDEX_PATH (lines 64-80), and its telemetry log is switched off through GA_VOICING_NO_TELEMETRY (VoicingTelemetryLog.cs lines 33-36). The output comes from:
dotnet run --project code/ga-ai/GaAi -c Release -- l21dotnet run --project code/ga-ai/GaMain -c Release -- l21How the tool reads a query
Section titled “How the tool reads a query”TypedMusicalQueryExtractor reads the query one word at a time (lines 86-148). The first word that starts with a capital and parses as a chord is the chord. Then a word, or two, from a list of 23 modes is the mode, an instrument is a filter, a few filler words like “chord” and “voicing” are skipped, and any other word of three letters or more that SymbolicTagRegistry knows is a tag:
// 2. Mode: single-word modes or two-word ("harmonic minor"). if (modeName is null) { if (KnownModesSet.Contains(tok)) { modeName = tok; continue; } if (i + 1 < tokens.Length) { var twoWord = tok + " " + tokens[i + 1]; if (KnownModesSet.Contains(twoWord)) { modeName = twoWord; continue; } } }
// 3. Instrument filter — first hit wins. Consumes the token so it never // leaks into the tag stream (where "bass" would otherwise miss the // registry and silently return no voicings from the wrong population). if (instrument is null && InstrumentAliases.TryGetValue(tok, out var inst)) { instrument = inst; continue; }
// 4. Linguistic filler ("chord", "voicing", "shape", …) never becomes a tag. // Without this, the registry's substring fallback maps "chord" onto the // first tag whose name contains it and poisons the SYMBOLIC vector. if (TagStopWords.Contains(tok)) { continue; }
// 5. Symbolic tag — matches the corpus's vocabulary (case-insensitive, // hyphen-normalized, with prefix/substring fallback). Require tokens // ≥ 3 chars so the registry's substring-contains fallback doesn't fire // on stop-words ("a", "me", "to") that happen to be substrings of a tag. if (tok.Length >= 3 && registry.GetBitIndex(tok).HasValue) { tags.Add(tok.ToLowerInvariant()); }The registry knows a word when it is one of its tags, or when either contains the other (SymbolicTagRegistry.cs lines 196-220):
public int? GetBitIndex(string tag) { if (string.IsNullOrWhiteSpace(tag)) { return null; }
var normalized = tag.ToLowerInvariant().Trim().Replace(" ", "-").Replace("_", "-");
if (_tagToBitMap.TryGetValue(normalized, out var bit)) { return bit; }
// Partial match fallback (e.g. "sweep" matches "sweep-picking") foreach (var kvp in _tagToBitMap) { if (normalized.Contains(kvp.Key) || kvp.Key.Contains(normalized)) { return kvp.Value; } }
return null; }MusicalQueryEncoder then turns the reading into a vector (MusicalQueryEncoder.cs lines 43-108). STRUCTURE, MODAL and ROOT come from the chord’s pitch classes, SYMBOLIC from the tags, and MORPHOLOGY and CONTEXT stay at zero. The encoder never reads the mode:
// SYMBOLIC — technique/style tags if (q.Tags is { Count: > 0 }) { var symbolicVec = SymbolicVectorService.ComputeEmbedding(q.Tags); EmbeddingSchema.WriteInto(raw, "SYMBOLIC", symbolicVec); }
// ROOT — 12-dim one-hot (v1.8). Query carries root signal IF the chord symbol // specified one; otherwise zero. Low weight (0.05) in the weighted cosine, so // root match adds a small discriminating boost on top of set-class-level STRUCTURE. if (root2.HasValue) { var rootVec = RootVectorService.ComputeEmbedding(root2); EmbeddingSchema.WriteInto(raw, "ROOT", rootVec); }
// MORPHOLOGY (24 dim) and CONTEXT (12 dim) remain zero — a text query carries no // fretboard realization or temporal-motion information. Their cosine contribution // is therefore zero, which is the correct behavior.
return ExtractCompactAndNormalize(raw);SymbolicVectorService gives the tags 12 dimensions, one per bit of the registry, and sets the bit of each tag to 1 (SymbolicVectorService.cs lines 13-32). The answer repeats the reading under interpreted, mode included, “to confirm the parser understood what the user wanted”, as the skill puts it (SKILL.md line 92).
The vocabulary
Section titled “The vocabulary”ga_voicing_vocabulary returns the roots and chord qualities of ChordPitchClasses, the reader’s modes, and every name the registry knows as a tag. The registry gives each tag a bit, by the file or the category it comes from (SymbolicTagRegistry.cs lines 46-111). The program reads the vocabulary, groups the tags by bit, and asks the search each entry alone:
== What ga_voicing_vocabulary lists, and how ga_search_voicings reads it, at the pinroots 18, chord-quality suffixes 45, modes 23, symbolic tags 188the query's SYMBOLIC partition: 12 dimensions; a tag sets the one its bit namesbit where the registry takes it from tags the first four0 SemanticNomenclature, Structure 6 closed-voicing, dense, open-voicing, resonant1 SemanticNomenclature, Register 5 register:high, register:low, register:mid, register:mid-high2 SemanticNomenclature, Playability 2 beginner-friendly, campfire-chord3 GuitarTechniques.yaml 16 bending, caged-system, chicken-picking, economy-picking4 ArticulationTechniques.yaml 4 accents, legato, slides, staccato5 SemanticNomenclature, CAGED 5 a-shape, c-shape, d-shape, e-shape6 SemanticNomenclature, Mood 8 aggressive, bright, dreamy, melancholy7 SemanticNomenclature, Genre 4 flamenco, jazz, neo-soul, rock-guitar8 AdvancedHarmony.yaml 23 added-note-chords, augmented-scale, cluster-chords, eleventh-chords9 VoiceLeading.yaml 28 augmented-fourth, chord-melody-technique, chromatic-voice-leading, contrary-motion10 AtonalTechniques.yaml, KeyModulationTechniques.yaml 27 chromatic-mediant, chromatic-trichord, circle-of-fifths-modulation, direct-modulation11 IconicChords 60 a-hard-day's-night-chord, add9(no3), beatles-chord, beatles-intro-chordentries listed twice, with and without hyphens: 8, cmaj9floating, drop2voicings, drop3voicings, messiaen’smode2(octatonic)tags that also give the query a chord, a famous chord's: 53entries read otherwise when typed alone: g5: nothing schoenberg-op.16-chord: tags schoenberg-op 16-chord schoenbergop.16chord: tags schoenbergop 16chord harmonic minor: mode harmonic minor, tags minor lydian dominant: mode lydian, tags dominant melodic minor: mode melodic minor, tags minor phrygian dominant: mode phrygian, tags dominant whole tone: mode whole tone, tags tone- 188 tags, 12 bits. A tag sets its bit, and nothing else:
jazz,rock-guitar,flamencoandneo-soulset the same one, and the 60 famous chords another. - Eight entries are another one without its hyphens. The registry adds a copy without hyphens to every tag with a digit, so that “drop2” finds the bit of
drop-2-voicings(lines 176-193); the vocabulary lists both. - 53 tags also give the query a chord. When a query names no chord, a famous chord’s tag gives it that chord’s notes: “Hendrix chord” searches for an E7#9 (
TypedMusicalQueryExtractor.cslines 157-174). - Three tags are read otherwise, and five modes read a tag.
g5is shorter than the three letters a tag needs, and the.ofschoenberg-op.16-chordsplits it in two. “lydian dominant” and “phrygian dominant” are read as Lydian and Phrygian, since one word is tried before two, and “dominant” becomes a tag. “harmonic minor”, “melodic minor” and “whole tone” keep their mode and add “minor” or “tone” as a tag.
The vocabulary’s own examples
Section titled “The vocabulary’s own examples”The vocabulary ends with five examples: what a user said, and the query an agent should send instead (VoicingVocabularyTool.cs lines 63-70). The program asks both:
== The vocabulary's five examples, as the user said them and as it rewrites them, at the pinthe user said read as rewritten read as same voicings top scoresCmaj7 jazz voicing chord Cmaj7, tags jazz Cmaj7 jazz chord Cmaj7, tags jazz yes 0.5085, 0.5085F# Lydian drop 2 chord F#, mode Lydian, tags drop F# Lydian drop2 chord F#, mode Lydian, tags drop2 yes 0.5310, 0.5310rootless Dm7 chord Dm7, tags rootless Dm7 rootless chord Dm7, tags rootless yes 0.5085, 0.5085something jazzy in F minor chord F, mode minor, tags jazzy Fm jazz chord Fm, tags jazz no 0.4810, 0.4810shell voicing for G7 chord G7, tags shell for G7 shell chord G7, tags shell yes 0.4932, 0.5078F minor, read as chord F, mode minor: x-x-x-2-1-1 F/A, x-x-3-2-1-1 F, x-0-x-x-1-1 F/AFm, read as chord Fm: x-x-x-1-1-1 Fm/Ab, x-x-3-1-1-1 Fm, x-3-x-1-x-1 Fm/C== The vocabulary's five examples, as the user said them and as it rewrites them, on mainthe user said read as rewritten read as same voicings top scoresCmaj7 jazz voicing chord Cmaj7, tags jazz Cmaj7 jazz chord Cmaj7, tags jazz yes 0.6447, 0.6447F# Lydian drop 2 chord F#, mode Lydian, tags drop F# Lydian drop2 chord F#, mode Lydian, tags drop2 yes 0.6500, 0.6500rootless Dm7 chord Dm7, tags rootless Dm7 rootless chord Dm7, tags rootless yes 0.6408, 0.6408something jazzy in F minor chord F, mode minor, tags jazzy Fm jazz chord Fm, tags jazz no 0.6000, 0.6000shell voicing for G7 chord G7, tags shell for G7 shell chord G7, tags shell yes 0.6316, 0.6447F minor, read as chord F, mode minor: x-x-x-2-1-1 F/A, x-x-3-2-1-x F, x-x-3-2-1-1 FFm, read as chord Fm: x-x-x-1-1-1 Fm/Ab, x-x-3-1-1-x Fm, x-x-3-1-1-1 Fm- “something jazzy in F minor” gets F major voicings, at the pin and on
main. The reader takes “F” for a chord, a major triad, and “minor” for a mode, which the vector ignores. “Fm” gets F minor. - “shell voicing for G7” reads “for” as a tag. It gets the same ten voicings as “G7 shell”, with a lower top score.
- The three other examples get the same voicings as their rewrite.
The tool’s description promises “mode names (Lydian, Dorian)” (line 95), and the skill describes the MODAL partition as “mode flavor” (SKILL.md line 14). The program asks four chords followed by each of the 23 modes, then each mode alone:
== The 23 modes after a chord, and alone, at the pinchord modes the same ten voicings as alone the others, as readC 23 19 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominantAm 23 19 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominantG7 23 19 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominantDm7 23 18 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominant; whole tone: mode whole tone, tags tonemodes alone: 18 of 23 answer with every score 0.0000, all the same ten voicings: yes, the index's first ten: yes x-x-x-0-0-0 Em/G, x-x-x-0-0-1 G7(shell), x-x-x-0-0-2 Gmaj7(shell), x-x-x-0-0-3 G + B (Major 3rd), x-x-x-0-1-0 C/G, x-x-x-0-1-1 Csus4/G, x-x-x-0-1-2 C5/G, x-x-x-0-1-3 C5, x-x-x-0-2-0 Dbdim/G, x-x-x-0-2-1 Gm7b5 harmonic minor: mode harmonic minor, tags minor, top score 0.0577 lydian dominant: mode lydian, tags dominant, top score 0.0577 melodic minor: mode melodic minor, tags minor, top score 0.0577 phrygian dominant: mode phrygian, tags dominant, top score 0.0577 whole tone: mode whole tone, tags tone, top score 0.0577== The 23 modes after a chord, and alone, on mainchord modes the same ten voicings as alone the others, as readC 23 19 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominantAm 23 19 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominantG7 23 21 lydian dominant: mode lydian, tags dominant; phrygian dominant: mode phrygian, tags dominantDm7 23 18 harmonic minor: mode harmonic minor, tags minor; lydian dominant: mode lydian, tags dominant; melodic minor: mode melodic minor, tags minor; phrygian dominant: mode phrygian, tags dominant; whole tone: mode whole tone, tags tonemodes alone: 18 of 23 answer with every score 0.0000, all the same ten voicings: yes, the index's first ten: yes x-x-x-0-0-0 Em/G, x-x-x-0-0-1 G7(shell), x-x-x-0-0-2 Gmaj7(shell), x-x-x-0-0-3 G + B (Major 3rd), x-x-x-0-1-0 C/G, x-x-x-0-1-1 Csus4/G, x-x-x-0-1-2 C5/G, x-x-x-0-1-3 C5, x-x-x-0-2-0 Dbdim/G, x-x-x-0-2-1 Gm7b5 harmonic minor: mode harmonic minor, tags minor, top score 0.0577 lydian dominant: mode lydian, tags dominant, top score 0.0577 melodic minor: mode melodic minor, tags minor, top score 0.0577 phrygian dominant: mode phrygian, tags dominant, top score 0.0577 whole tone: mode whole tone, tags tone, top score 0.0577- A mode changes nothing. “C Lydian” answers the same ten voicings as “C”. The only modes that change an answer are modes that read a tag. MODAL is computed from the chord’s notes, and the mode’s name is left in
interpreted. - A mode alone answers the index’s first ten voicings. With no chord and no tag, every partition of the query is zero: 18 of the 23 modes get a score of 0 for every voicing, and the search returns the first ten it reads.
HasIntentcounts a mode as something to search for (lines 238-242), so the tool never gives its answer for a query it can’t read, “No chord, mode, or known tag recognized” (lines 155-177). The vocabulary itself counts on “STRUCTURE and/or MODAL partitions” to rank voicings (VoicingVocabularyTool.cslines 72-76), and only a chord fills them.
The skill presents the tags as technique and style, “drop2, shell, jazz, rootless” (SKILL.md line 3). The program adds each of the 188 tags to three chords:
== The 188 symbolic tags after a chord, at the pinchord tags different answers the same ten as the chord alone bits whose tags all answer alikeCmaj7 188 7 53 12 of 12Dm7 188 7 53 12 of 12G7 188 5 76 12 of 12for Cmaj7, the bits whose tags change nothing: 3, 4, 5, 10for Cmaj7, jazz and rock-guitar: the same answer yesfor Cmaj7, shell-voicing and rootless: the same answer yesfor Cmaj7, jazz and shell-voicing: the same answer no== The 188 symbolic tags after a chord, on mainchord tags different answers the same ten as the chord alone bits whose tags all answer alikeCmaj7 188 5 76 12 of 12Dm7 188 4 53 12 of 12G7 188 3 135 12 of 12for Cmaj7, the bits whose tags change nothing: 3, 4, 5, 8, 10for Cmaj7, jazz and rock-guitar: the same answer yesfor Cmaj7, shell-voicing and rootless: the same answer yesfor Cmaj7, jazz and shell-voicing: the same answer yes- 188 tags give Cmaj7 and Dm7 seven answers at the pin, and G7 five, the chord’s own answer among them. Within each bit, the tags read as typed all give the same answer:
jazzanswers asrock-guitar, andshell-voicingasrootless. Onmainthe answers are fewer still, andjazzandshell-voicinggive Cmaj7 the same answer too. - Some bits change nothing. For Cmaj7, the tags of bits 3, 4, 5 and 10, among them
caged-system,legatoand the CAGED shapes, leave the answer as it was; onmain, bit 8 joins them.
rootless and shell-voicing come from the same category of SemanticNomenclature.yaml, Structure, and set bit 0 (lines 129-140). A rootless voicing leaves out the chord’s root. The program asks ten seventh chords with “rootless”, and counts the voicings of the index that play three notes or more, all of them the chord’s, none of them its root:
== "rootless" after a seventh chord, at the pinchord ten voicings, rootless rootless in the index the first threeCmaj7 0 of 10 63 x-2-2-x-1-x Cmaj7(shell)/B, 0-2-x-x-1-x Cmaj7(shell)/E, 0-2-2-x-1-x Cmaj7(shell)/EDm7 0 of 10 35 x-3-3-x-3-x Dm7(shell)/C, 1-3-x-x-3-x Dm7(shell)/F, 1-3-0-x-x-x Dm7(shell)/FEm7 0 of 10 35 x-x-x-0-3-0 Em7(shell)/G, x-x-0-0-x-0 Em7(shell)/D, x-x-0-0-3-0 Em7(shell)/DFmaj7 0 of 10 35 x-x-2-2-x-1 Fmaj7(shell)/E, 0-x-3-2-x-1 Fmaj7(shell)/E, 0-0-3-x-x-1 Fmaj7(shell)/EG7 0 of 10 19 x-2-3-x-x-3 G7(shell)/B, 1-x-x-x-0-3 G7(shell)/F, 1-x-x-0-0-3 G7(shell)/FAm7 0 of 10 63 3-3-x-2-x-x Am7(shell)/G, x-3-2-2-x-x Am/C, 0-3-x-2-x-x Am/EC7 0 of 10 35 x-x-2-3-1-x C7(shell)/E, x-1-2-x-1-x C7(shell)/Bb, 0-x-x-3-1-x C7(shell)/ED7 0 of 10 15 2-3-x-x-3-x D7(shell)/Gb, 2-3-0-x-x-x D7(shell)/Gb, 2-3-0-x-3-x D7(shell)/GbE7 0 of 10 5 x-x-x-1-3-0 E7(shell)/Ab, 0-x-0-1-3-0 E7(shell), x-x-0-1-x-0 E7(shell)/DA7 0 of 10 21 3-0-x-2-x-x G + A (Major 2nd), 3-x-2-2-x-x A5/G, 3-0-2-2-x-x A5/Gvoicings answered 100: rootless 0== "rootless" after a seventh chord, on mainchord ten voicings, rootless rootless in the index the first threeCmaj7 0 of 10 63 x-2-2-x-1-3 Cmaj7/B, x-3-2-x-0-3 Cmaj7, x-3-2-0-0-3 Cmaj7Dm7 0 of 10 35 x-3-0-2-3-1 Dm7/C, 1-3-x-2-3-x Dm7/F, 1-3-x-2-3-1 Dm7/FEm7 0 of 10 35 x-2-0-0-x-0 Em7/B, x-2-0-0-0-0 Em7/B, x-2-0-0-3-0 Em7/BFmaj7 0 of 10 35 1-0-3-2-1-0 Fmaj7, x-3-2-2-x-1 Fmaj7/C, x-3-2-2-1-1 Fmaj7/CG7 0 of 10 19 x-2-3-0-3-1 G7/B, 1-2-x-x-3-3 G7/F, 1-2-x-0-3-1 G7/FAm7 0 of 10 63 x-3-2-2-1-3 Am7/C, 0-3-x-2-1-3 Am7/E, 0-3-2-2-1-3 Am7/EC7 0 of 10 35 x-x-2-3-1-3 C7/E, x-1-2-x-1-3 C7/Bb, x-1-2-0-1-3 C7/BbD7 0 of 10 15 x-3-0-2-1-2 D7/C, 2-3-x-2-3-2 D7/Gb, 2-3-0-2-1-x D7/GbE7 0 of 10 5 x-2-2-1-3-x E7/B, x-x-0-1-0-0 E7/D, x-2-x-1-3-0 E7/BA7 0 of 10 21 x-x-2-2-2-3 A7/E, x-0-x-0-2-0 A7, x-0-2-x-2-3 A7voicings answered 100: rootless 0- None of the 100 voicings answered is rootless, at the pin or on
main, while the index holds between 5 and 63 for each chord. At the pin, the first answers are voicings GA’s analysis calls shells, root included. The tag can only move SYMBOLIC, worth 0.10 of the score; the query’s STRUCTURE, worth 0.45, holds the whole chord, root included.
Words read as tags
Section titled “Words read as tags”A word becomes a tag when it contains a tag, or a tag contains it; the vocabulary calls this a “substring fallback” (VoicingVocabularyTool.cs lines 58-61). The program asks GA’s own example queries, from the tool’s description, the skill and the parked draft, then three of the course’s:
== GA's example queries, and words read as tags, at the pinquery read as top score words no tag spells, and the tag they matchCmaj7 drop2 jazz chord Cmaj7, tags drop2 jazz 0.4938 drop2 → drop2voicingsF# Lydian chord F#, mode Lydian 0.4810Dm7 chord Dm7 0.4585something warm and mellow nothing noneshow me Cmaj7 voicings chord Cmaj7 0.4585drop-2 Dm7 chord Dm7, tags drop-2 0.4942 drop-2 → drop-2-voicingsrootless G7 shapes chord G7, tags rootless 0.5078Lydian on guitar mode Lydian 0.0000jazz voicings tags jazz 0.0577shell voicings tags shell 0.0707 shell → shell-voicingvoicings with a similar quality to F#m7b5 chord F#m7b5 0.4368something warm and dreamy tags dreamy 0.0577something jazzy in F minor for a ballad chord F, mode minor, tags jazzy for 0.4810 jazzy → jazz, for → normal-formFind me a mellow Cm9 chord Cm9 0.3887Rootless Dm7 voicing chord Dm7, tags rootless 0.5085Drop-2 voicing for Gmaj7 chord Gmaj7, tags drop-2 for 0.4585 drop-2 → drop-2-voicings, for → normal-formBright open-position Em chord Em, tags bright 0.5310the course's:the best G7 voicing chord G7, tags the 0.4578 the → pitch-axis-theorywhat is a good Am7 voicing chord Am7, tags what 0.4995 what → so-what-chorda sad chord for the end of a song tags sad for the end 0.0500 for → normal-form, the → pitch-axis-theory, end → beginner-friendly- Common words become tags. “for” matches
normal-form, “the”pitch-axis-theory, “what”so-what-chordand “end”beginner-friendly. The reader skips “chord” and “voicing” for that reason, and words under three letters, “a”, “me”, “to” (lines 132-147); “for”, “the”, “what” and “end” pass both guards. Each sets a bit, and once the partition is normalized a second bit lowers the weight of the first: “G7 shell for” scores below “G7 shell”. The skill’s own example, “something jazzy in F minor for a ballad”, reads “for” (SKILL.mdline 33). - Without a chord, only SYMBOLIC can match. “Lydian on guitar”, “jazz voicings” and “shell voicings” are among the skill’s uses (line 19), and score 0, 0.0577 and 0.0707. The skill gives “something warm and dreamy” as a query the reader gets nothing from, with “arbitrary results” (line 26); the tool reads
dreamy, a tag, and scores 0.0577. - “something warm and mellow”, the tool’s own example of fuzzy phrasing, reads nothing and returns no voicing (line 99). Its
allowSamplingwould ask the client’s model to read it (lines 109-111); the course leaves it off.
The scores
Section titled “The scores”The skill explains the scores under “Why Scores Cluster 0.4–0.98”: “STRUCTURE (0.45) + MORPHOLOGY (0.25) + CONTEXT (0.20) + SYMBOLIC (0.10) + MODAL (0.10)”, and “Near-perfect matches across all partitions approach 1.0” (lines 94-96); its example answer scores 0.9831 (line 81). The program reads the weights from EmbeddingSchema (lines 120-136), and asks every root, alone and with every quality:
== The scores, at the pinpartitions a query fills: STRUCTURE 0.45, SYMBOLIC 0.10, MODAL 0.10, ROOT 0.05; always empty: MORPHOLOGY 0.25, CONTEXT 0.20the highest score a query can reach: 0.70chord queries 828: the highest top score 0.5250, for E5 Cmaj7: 0.4585 Cmaj7 jazz: 0.5085 Cmaj7 drop2 jazz: 0.4938 Cmaj7 shell dreamy jazz: 0.5451 G7: 0.4578 G7 shell: 0.5078 G7 shell for: 0.4932== The scores, on mainpartitions a query fills: STRUCTURE 0.45, SYMBOLIC 0.10, MODAL 0.10, ROOT 0.05; always empty: MORPHOLOGY 0.25, CONTEXT 0.20the highest score a query can reach: 0.70chord queries 828: the highest top score 0.6000, for A Cmaj7: 0.6000 Cmaj7 jazz: 0.6447 Cmaj7 drop2 jazz: 0.6577 Cmaj7 shell dreamy jazz: 0.6775 G7: 0.6000 G7 shell: 0.6447 G7 shell for: 0.6316- No query can score above 0.70. The encoder leaves MORPHOLOGY and CONTEXT at zero, so their 0.45 never counts. The 828 chord queries peak at 0.5250 at the pin; on
main, Cmaj7, G7 and A reach 0.6000, the weights of STRUCTURE, MODAL and ROOT added up, and only tags can add the last 0.10. The example’s 0.9831 is out of reach. - Adding
jazzraises the score here. The skill warns that a style tag “currently lowers the score” because the index’s voicings lack style bits (line 47). On the index the pinned code writes, “Cmaj7 jazz” scores above “Cmaj7”, at the pin and onmain. The course’s index isn’t GA’s production index, where the warning may still hold. - The skill’s warning about “drop2” is out of date. “
drop2does NOT match — usedrop-2-voicings” (line 39); with the registry’s copies without hyphens, “drop2” reads as a tag.
The skill draft that would call it
Section titled “The skill draft that would call it”skills-dev/_pending-tools/voicing-search/DRAFT.md is a chatbot skill written for voicing search, parked like those of lessons 19 and 20 (skills-dev/_pending-tools/README.md line 47). It calls another tool, ga_search_voicings_by_query (lines 22-26):
- that tool is in
GaMcpServer, inVoicingEmbeddingTool.cs, and posts the query to GaApi, which embeds it with Ollama (lines 93-132); the course can’t run it offline; - the draft says it is “not yet implemented in Common/GA.Business.ML/Agents/Mcp/” (line 22), and points to
Common/GA.Business.ML/Agents/Mcp/VoicingMcpTools.cs(line 83), a file GA has at neither commit; - it expects
topK,instrumentand an answer ofResultswithcontextTagsandsourceCorpus(lines 35-44), where the tool takesqueryandlimitand returns GaApi’s answer; - its example answer for “mellow Cm9” scores 0.91 and shows a voicing with three tags (lines 57-68):
== The parked draft's example answer for "mellow Cm9", at the pinx-3-5-3-3-3 plays C G A# D G; Cm9 is C D D# G A#, and the voicing leaves out D#its tag low-density: in the vocabulary no, read as chord Cm9its tag rootless-on-bass: in the vocabulary no, read as chord Cm9, tags rootless-on-bassits tag quartal-flavour: in the vocabulary no, read as chord Cm9The voicing has no minor third: its chart calls the G string’s third fret E♭ and the high E’s D, where they play B♭ and G. None of its three tags is in the vocabulary.
Where the course stops
Section titled “Where the course stops”- The index is lesson 3’s, 15,360 guitar voicings on the first three frets, not GA’s production index: the answers and the scores can differ there; the readings don’t depend on the index.
- The instrument filter isn’t tried: the course’s index holds only guitar voicings.
allowSamplingstays off, andga_search_voicings_by_queryisn’t run: both need a model.- The rootless test is the course’s, on the notes each voicing plays.
- The program calls the tools’ methods directly, not through an MCP client and GA’s server.
Reported upstream
Section titled “Reported upstream”- Reported after this lesson was written, in GA issue #795: the mode read and dropped, the 188 tags on 12 bits that don’t filter, the words matched by substring, the voicing-search skill’s scores and gotchas, and the parked skill draft.
Exercises
Section titled “Exercises”- “Lydian on guitar” returns ten voicings, all scored 0. Why doesn’t the tool answer that it can’t read the query, and why these ten?
- “G7 shell” scores 0.5078 at the pin and “G7 shell for” 0.4932, with “G7” at 0.4578. Where do the two differences come from?
jazzandrock-guitargive Cmaj7 the same answer. Why, and which other tags give it too?- The parked draft’s answer for “mellow Cm9” is
x-3-5-3-3-3. Which of Cm9’s notes does it leave out, and what does that make the chord?
Solutions
HasIntentcounts the mode “Lydian” as something to search for (lines 238-242), so the tool searches. The encoder ignores the mode, so the query vector is zero, every voicing scores 0, and the search keeps the first ten it reads, the index’s first ten.- “shell” reads
shell-voicing, of bit 0: SYMBOLIC adds 0.5078 − 0.4578 = 0.0500. “for” readsnormal-form, of bit 10. Normalized, the query’s two bits weigh 1/√2 each, so bit 0 now adds 0.0500/√2 and bit 10 nothing: 0.4578 + 0.0354 = 0.4932. - They set the same bit, 7, the registry’s Genre category, and the query’s SYMBOLIC partition is the same vector.
flamencoandneo-soul, the bit’s two other tags, give that answer too: the program found that every bit’s tags, read as typed, answer alike. - It plays C G A# D G: it leaves out D#, E♭, the minor third. Without a third, it is neither minor nor major, a C9 with no third.
Key takeaways
Section titled “Key takeaways”- A reader that recognizes a word isn’t a search that uses it: the mode is read, printed in
interpreted, and dropped. - 188 tags over 12 bits are 12 words for the search, not 188.
- A tag that names a property isn’t a filter: check the answers against the property.
- Matching by substring turns common words into signals.
- A documented score range can be checked against the weights a query can fill.
Sources
Section titled “Sources”- GuitarAlchemist/ga at
a826864:GaMcpServer/Tools/VoicingSearchTool.cs,GaMcpServer/Tools/VoicingVocabularyTool.cs,GaMcpServer/Tools/VoicingEmbeddingTool.cs,Common/GA.Business.ML/Search/TypedMusicalQueryExtractor.cs,Common/GA.Business.ML/Search/MusicalQueryEncoder.cs,Common/GA.Business.ML/Embeddings/Services/SymbolicVectorService.cs,Common/GA.Business.Config/Configuration/SymbolicTagRegistry.cs,Common/GA.Business.Config/SemanticNomenclature.yaml,.claude/skills/voicing-search/SKILL.md,skills-dev/_pending-tools/voicing-search/DRAFT.md. - GuitarAlchemist/ga at
f4f5b4a: the same tools, reader and registry, and the search of lesson 16. - The course’s programs:
code/ga-ai/GaAi/Lesson21.cs,code/ga-ai/GaMain/Program.cs,code/ga-ai/Shared/SearchVoicingsProbe.cs,code/ga-ai/Shared/SearchIndex.cs.