Files
JamBuddy/src/lib/voicings.js
T
vadimwit ffc1bb1d18 feat(kb): pop piano cell + 8 guitar shapes for thin qualities (task P-62)
Pop piano: 5 progressions x 2 plays (182 total) — triads/inversions
only, one sourced add9 shimmer; ballad rolls vs voice-led pillars,
doo-wop triplets vs one-finger walks, Pachelbel inversion stair,
tonic-drone pads, octave drive. Shapes: dim7/half_dim/aug/maj6/min6
each reach >=3 placeable grips, add9 gains a movable A-shape; dim
honestly left at 2. Audit also surfaced two WRONG pre-existing shapes
(half_dim E-Barre spells {0,3,6,7}; "Open Dadd9" is Dsus2) -> backlog.
Critic PASS (~110 independent checks; all additions pc-spelled in two
keys; strictly additive diff).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:34:29 +01:00

598 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ─── Guitar voicing shapes ────────────────────────────────────────────────────
// Open string pitches (standard tuning): E A D G B e
const OPEN = [4, 9, 2, 7, 11, 4] // pitch class per string [s6 … s1]
// Barre shape: offsets[] are fret distances from the root fret, per string [s6…s1].
// 'x' = muted. rootStr = 1-indexed string that holds the root (6=low E, 5=A…).
// Open shape: frets[] are absolute fret numbers (0=open, 'x'=muted) for a specific root key.
const GUITAR_SHAPES = {
maj: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,2,1,0,0], fingers: [1,3,4,2,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,2,2,0], fingers: [0,1,3,4,2,1], barre: { fromStr: 1, toStr: 5, fo: 0 } },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,3,2], fingers: [0,0,1,3,4,2] },
{ label: 'G Shape', type: 'barre', rootStr: 6, offsets: [0,-1,-3,-3,-3,0], fingers: [4,3,1,1,1,4] },
{ label: 'Open E', type: 'open', onlyRoot: 4, frets: [0,2,2,1,0,0], fingers: [0,2,3,1,0,0] },
{ label: 'Open A', type: 'open', onlyRoot: 9, frets: ['x',0,2,2,2,0], fingers: [0,0,1,2,3,0] },
{ label: 'Open G', type: 'open', onlyRoot: 7, frets: [3,2,0,0,0,3], fingers: [2,1,0,0,0,3] },
{ label: 'Open C', type: 'open', onlyRoot: 0, frets: ['x',3,2,0,1,0], fingers: [0,3,2,0,1,0] },
{ label: 'Open D', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,3,2], fingers: [0,0,0,1,3,2] },
],
min: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,2,0,0,0], fingers: [1,3,4,1,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,2,1,0], fingers: [0,1,3,4,2,1], barre: { fromStr: 1, toStr: 5, fo: 0 } },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,3,1], fingers: [0,0,1,3,4,2] },
{ label: 'Open Em', type: 'open', onlyRoot: 4, frets: [0,2,2,0,0,0], fingers: [0,2,3,0,0,0] },
{ label: 'Open Am', type: 'open', onlyRoot: 9, frets: ['x',0,2,2,1,0], fingers: [0,0,2,3,1,0] },
{ label: 'Open Dm', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,3,1], fingers: [0,0,0,2,3,1] },
],
dom7: [
{ label: 'E7 Barre', type: 'barre', rootStr: 6, offsets: [0,2,0,1,0,0], fingers: [1,3,0,2,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A7 Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,0,2,0], fingers: [0,1,2,0,3,0] },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,1,2], fingers: [0,0,1,3,2,4] },
{ label: 'Open E7', type: 'open', onlyRoot: 4, frets: [0,2,0,1,0,0], fingers: [0,2,0,1,0,0] },
{ label: 'Open A7', type: 'open', onlyRoot: 9, frets: ['x',0,2,0,2,0], fingers: [0,0,2,0,3,0] },
{ label: 'Open G7', type: 'open', onlyRoot: 7, frets: [3,2,0,0,0,1], fingers: [3,2,0,0,0,1] },
{ label: 'Open D7', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,1,2], fingers: [0,0,0,2,1,3] },
{ label: 'Open B7', type: 'open', onlyRoot: 11, frets: ['x',2,1,2,0,2], fingers: [0,2,1,3,0,4] },
],
maj7: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,1,1,0,0], fingers: [1,3,2,2,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,1,2,0], fingers: [0,1,3,2,4,1], barre: { fromStr: 1, toStr: 2, fo: 0 } },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,2,2], fingers: [0,0,1,2,3,4], barre: { fromStr: 1, toStr: 3, fo: 2 } },
{ label: 'Open Cmaj7', type: 'open', onlyRoot: 0, frets: ['x',3,2,0,0,0], fingers: [0,3,2,0,0,0] },
{ label: 'Open Amaj7', type: 'open', onlyRoot: 9, frets: ['x',0,2,1,2,0], fingers: [0,0,2,1,3,0] },
{ label: 'Open Emaj7', type: 'open', onlyRoot: 4, frets: [0,2,1,1,0,0], fingers: [0,2,1,1,0,0] },
],
min7: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,0,0,0,0], fingers: [1,3,1,1,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,0,1,0], fingers: [0,1,3,1,2,1], barre: { fromStr: 1, toStr: 5, fo: 0 } },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,1,1], fingers: [0,0,1,3,2,2] },
{ label: 'Open Em7', type: 'open', onlyRoot: 4, frets: [0,2,0,0,0,0], fingers: [0,2,0,0,0,0] },
{ label: 'Open Am7', type: 'open', onlyRoot: 9, frets: ['x',0,2,0,1,0], fingers: [0,0,2,0,1,0] },
{ label: 'Open Dm7', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,1,1], fingers: [0,0,0,3,1,2] },
],
// dim intentionally stays at 2 shapes: in real guitar practice the bare
// diminished TRIAD is almost always played as dim7 or m7♭5 (which contain
// it) — a third stand-alone triad grip would be contrived. (Task P-62 audit.)
dim: [
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,1,2,1,'x'], fingers: [0,1,2,4,3,0] },
{ label: 'Compact', type: 'barre', rootStr: 4, offsets: ['x','x',0,1,3,1], fingers: [0,0,1,2,4,3] },
],
dim7: [
{ label: 'Movable Box', type: 'barre', rootStr: 5, offsets: ['x',0,1,2,1,2], fingers: [0,1,2,4,3,4] },
{ label: 'Compact', type: 'barre', rootStr: 4, offsets: ['x','x',0,1,0,1], fingers: [0,0,1,2,3,4] },
// P-62: standard drop-3 dim7 with the root on the 6th string (R–♭♭7–♭3–♭5;
// e.g. G°7 = 3x232x) — jazzguitar.be diminished-chord chart shape.
{ label: 'Drop 3', type: 'barre', rootStr: 6, offsets: [0,'x',-1,0,-1,'x'], fingers: [2,0,1,3,1,0] },
],
aug: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,3,2,1,1,0], fingers: [1,4,3,2,2,1] },
{ label: 'Compact', type: 'barre', rootStr: 5, offsets: ['x',0,3,2,2,'x'], fingers: [0,1,4,2,3,0] },
// P-62: the top-four-string aug grip (R–♯5R3; e.g. D+ = xx0332) —
// standard chord-dictionary shape, movable like the file's other D shapes.
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,3,3,2], fingers: [0,0,1,3,4,2] },
],
sus4: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,2,2,0,0], fingers: [1,2,3,4,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,2,3,0], fingers: [0,1,2,3,4,0] },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,3,3], fingers: [0,0,1,2,3,4] },
{ label: 'Open Asus4', type: 'open', onlyRoot: 9, frets: ['x',0,2,2,3,0], fingers: [0,0,1,2,4,0] },
{ label: 'Open Dsus4', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,3,3], fingers: [0,0,0,1,2,3] },
],
sus2: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,4,4,0,0], fingers: [1,2,4,4,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,4,0,0], fingers: [0,1,2,4,1,1], barre: { fromStr: 1, toStr: 2, fo: 0 } },
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,3,0], fingers: [0,0,1,2,3,0] },
{ label: 'Open Asus2', type: 'open', onlyRoot: 9, frets: ['x',0,2,2,0,0], fingers: [0,0,1,2,0,0] },
{ label: 'Open Dsus2', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,3,0], fingers: [0,0,0,1,3,0] },
],
half_dim: [
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,1,0,1,'x'], fingers: [0,1,2,0,3,0] },
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,1,2,0,0,'x'], fingers: [1,2,3,1,1,0], barre: { fromStr: 2, toStr: 6, fo: 0 } },
// P-62: standard drop-3 m7♭5 with the root on the 6th string (R–♭7–♭3–♭5;
// e.g. Gø7 = 3x332x) — jazzguitar.be m7♭5 chart shape.
{ label: 'Drop 3', type: 'barre', rootStr: 6, offsets: [0,'x',0,0,-1,'x'], fingers: [2,0,3,4,1,0] },
// P-62: the one-finger-barre m7♭5 (R–♭5–♭7–♭3; e.g. Dø7 = xx0111) —
// standard chord-dictionary grip, movable.
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,1,1,1], fingers: [0,0,1,3,3,3], barre: { fromStr: 1, toStr: 3, fo: 1 } },
],
maj6: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,2,1,2,0], fingers: [1,3,4,2,4,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,2,2,2], fingers: [0,1,2,3,4,4], barre: { fromStr: 1, toStr: 2, fo: 2 } },
// P-62: the D6 grip (R563; e.g. D6 = xx0202) — standard chord-dictionary
// shape; the swing/Western-swing four-string 6th, movable.
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,0,2], fingers: [0,0,1,3,1,4], barre: { fromStr: 2, toStr: 4, fo: 0 } },
],
min6: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,2,0,2,0], fingers: [1,3,4,1,4,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
// P-62: the Dm6 grip (R56–♭3; e.g. Dm6 = xx0201) — standard
// chord-dictionary shape, movable.
{ label: 'D Shape', type: 'barre', rootStr: 4, offsets: ['x','x',0,2,0,1], fingers: [0,0,1,3,1,2], barre: { fromStr: 2, toStr: 4, fo: 0 } },
// P-62: the movable open-Am6 shape (R5R–♭36; x02212 open, e.g.
// Bm6 = x24344) — standard barre-chart m6 entry.
{ label: 'A Barre', type: 'barre', rootStr: 5, offsets: ['x',0,2,2,1,2], fingers: [0,1,3,3,2,4], barre: { fromStr: 3, toStr: 4, fo: 2 } },
],
add9: [
{ label: 'E Barre', type: 'barre', rootStr: 6, offsets: [0,2,4,1,0,2], fingers: [1,2,4,3,1,1], barre: { fromStr: 1, toStr: 6, fo: 0 } },
// P-62: the movable core of the standard Aadd9 chart grip (x02420 open;
// the open high-e doubling doesn't move, so it is muted here): R593.
{ label: 'A Shape', type: 'barre', rootStr: 5, offsets: ['x',0,2,4,2,'x'], fingers: [0,1,2,4,3,0] },
{ label: 'Open Cadd9', type: 'open', onlyRoot: 0, frets: ['x',3,2,0,3,0], fingers: [0,3,2,0,4,0] },
{ label: 'Open Gadd9', type: 'open', onlyRoot: 7, frets: [3,2,0,2,3,3], fingers: [2,1,0,3,4,4] },
{ label: 'Open Dadd9', type: 'open', onlyRoot: 2, frets: ['x','x',0,2,3,0], fingers: [0,0,0,1,3,0] },
],
}
// ─── Piano techniques ─────────────────────────────────────────────────────────
// Each technique has intervals for left hand (LH) and right hand (RH) in semitones above root.
// Negative values go one octave below root. Labels appear on the keys.
export const PIANO_TECHNIQUES = {
maj: [
{
name: 'Full Chord',
desc: 'Classic voicing — root in left, triad in right',
tip: 'Anchor the root alone in your left hand, lock into the rhythm, let right hand sing.',
lh: [0],
rh: [0, 4, 7],
},
{
name: 'Root + 5th',
desc: 'Open, powerful — ambiguous major/minor quality',
tip: 'Stack octave + fifth in left hand. Works over major or minor — great for tense moments.',
lh: [0, 7],
rh: [0, 4, 7, 12],
},
{
name: 'Root + Octave',
desc: 'Thunderous low end — fills space in a band',
tip: 'Octave doubles in left hand, full chord in right. Huge sound in lower registers.',
lh: [0, 12],
rh: [4, 7, 12],
},
{
name: 'Spread Voicing',
desc: 'Wide, orchestral — two octaves apart',
tip: 'Split the chord wide: 5th in left, 3rd + 5th above the octave in right. Very cinematic.',
lh: [0, 7],
rh: [12, 16, 19],
},
{
name: 'Suspended Approach',
desc: 'Play sus4 → resolve to major — creates motion',
tip: 'Hit sus4 (replace 3rd with 4th) then release to the major 3rd. Works great in slow ballads.',
lh: [0],
rh: [0, 5, 7],
},
],
min: [
{
name: 'Full Chord',
desc: 'Classic minor voicing — dark and rich',
tip: 'The minor 3rd is everything. Let it ring — do not rush to resolve.',
lh: [0],
rh: [0, 3, 7],
},
{
name: 'Root + 5th',
desc: 'Open ambiguity — dramatic without the sadness',
tip: 'Power chord in both hands. Hides the minor quality — use when you want tension without gloom.',
lh: [0, 7],
rh: [0, 7, 12],
},
{
name: 'Root + Octave',
desc: 'Deep anchor — gives bass player space',
tip: 'Octave in left only. Right hand plays the minor chord high up for contrast.',
lh: [0, 12],
rh: [3, 7, 12],
},
{
name: 'Spread Minor',
desc: 'Atmospheric and wide — film score territory',
tip: 'Wide spacing on minor chords feels melancholic and vast. Popular in ambient and cinematic styles.',
lh: [0, 7],
rh: [12, 15, 19],
},
{
name: 'Minor + Add9',
desc: 'Add the 9th (2nd) — aching, bittersweet quality',
tip: 'Replace or add the 9th to minor. Radiohead, Portishead, modern soul — this interval is gold.',
lh: [0],
rh: [0, 3, 7, 14],
},
],
dom7: [
{
name: 'Full Dom7',
desc: 'Classic dominant — loaded with tension',
tip: 'The tritone between the 3rd and ♭7th creates all the tension. Let it ring before resolving.',
lh: [0],
rh: [0, 4, 7, 10],
},
{
name: 'Shell Voicing (1-3-♭7)',
desc: 'Skip the 5th — lean, jazz-approved',
tip: 'Root + major 3rd + ♭7th. The tritone is intact, 5th is redundant. Classic jazz comp technique.',
lh: [0],
rh: [4, 10],
},
{
name: 'Rootless Voicing',
desc: 'Advanced comping — let bass hold the root',
tip: 'No root in your hands at all. 3rd in left, upper structure in right. Very sophisticated sound.',
lh: [4],
rh: [7, 10, 14],
},
{
name: 'Blues Stomp',
desc: 'Root + 5th left, add ♭7 right — R&B classic',
tip: 'Left hand pumps root-5th pattern, right hand stabs the dominant 7th chord. Classic gospel/blues.',
lh: [0, 7],
rh: [0, 4, 10],
},
{
name: 'Tritone Sub',
desc: 'Replace with the chord a tritone away',
tip: 'G7 can be replaced with Db7 — they share the same tritone (F and B). Mind-bending jazz move.',
lh: [6],
rh: [10, 14, 16],
},
],
maj7: [
{
name: 'Full Maj7',
desc: 'Dreamy, floating — jazz and bossa nova',
tip: 'The major 7th creates a luminous, slightly unresolved quality. Do not over-play it — let it breathe.',
lh: [0],
rh: [0, 4, 7, 11],
},
{
name: 'Shell (1-3-7)',
desc: 'Root + 3rd + maj7 — lush and clean',
tip: 'Skip the 5th. The major 7th directly above the root defines the chord without clutter.',
lh: [0],
rh: [4, 11],
},
{
name: 'Spread Maj7',
desc: 'Maj7 in left hand — wide, orchestral texture',
tip: 'Place the 7th below the root (or an octave down). Creates a spacious, choir-like sound.',
lh: [0, 11],
rh: [12, 16, 19],
},
{
name: 'Add9 Variation',
desc: 'Add the 9th for extra colour',
tip: 'Maj9 territory. Remove the root in the right hand, add the 9th (D for Cmaj9). Very smooth.',
lh: [0],
rh: [4, 7, 11, 14],
},
],
min7: [
{
name: 'Full Min7',
desc: 'Smooth and mellow — soul and jazz workhorse',
tip: 'The minor 7th chord is the most versatile in jazz. Comp behind everything with this.',
lh: [0],
rh: [0, 3, 7, 10],
},
{
name: 'Shell (1-♭3-♭7)',
desc: 'Just the 3rd and 7th in right — spacious',
tip: 'Root in left, minor 3rd + minor 7th in right. Leaves maximum space for the soloist.',
lh: [0],
rh: [3, 10],
},
{
name: 'Rootless Min7',
desc: 'No root — upper structure only',
tip: '♭3rd in left, build up from there. Very advanced jazz voicing — trust the bass player.',
lh: [3],
rh: [10, 14, 15],
},
{
name: 'Spread Atmospheric',
desc: 'Wide voicing — ambient and cinematic',
tip: 'Minor 7ths voiced wide feel endless. Great for intro sections or building tension.',
lh: [0, 7],
rh: [10, 15, 19],
},
],
dim: [
{
name: 'Full Diminished',
desc: 'All three tones — tense and unstable',
tip: 'Always wants to resolve. Use as a passing chord between diatonic chords.',
lh: [0],
rh: [0, 3, 6],
},
{
name: 'Octave + Dim',
desc: 'Root octave in left — more weight',
tip: 'Dim triads are thin — doubling the root in left hand adds body.',
lh: [0, 12],
rh: [3, 6, 12],
},
],
dim7: [
{
name: 'Full Dim7',
desc: 'Symmetrical — repeats every 3 frets',
tip: 'Any note in a dim7 chord can be the root. It modulates effortlessly. Horror/drama gold.',
lh: [0],
rh: [0, 3, 6, 9],
},
{
name: 'Arpeggiated',
desc: 'Roll the notes — tension without crash',
tip: 'Roll from bottom to top quickly. Dim7 arpeggios feel like falling — use before a big resolve.',
lh: [0, 3],
rh: [6, 9, 12],
},
],
aug: [
{
name: 'Full Augmented',
desc: 'Dreamy, unresolved — whole-tone territory',
tip: 'Aug chords are symmetrical like dim7 — every inversion sounds the same. Very otherworldly.',
lh: [0],
rh: [0, 4, 8],
},
{
name: 'Spread Aug',
desc: 'Wide voicing — maximises the instability',
tip: 'Spread over two octaves. The raised 5th wants to resolve up — let the listener feel the pull.',
lh: [0, 8],
rh: [12, 16, 20],
},
],
sus4: [
{
name: 'Full Sus4',
desc: 'Suspended — neither major nor minor',
tip: 'Ambiguous and open. Resolve down to the major 3rd for instant satisfaction.',
lh: [0],
rh: [0, 5, 7],
},
{
name: 'Sus4 → Major',
desc: 'Play sus4 then resolve — creates motion',
tip: 'Hit sus4 on the beat, release to major on the off-beat. The oldest trick in the book.',
lh: [0],
rh: [5, 7, 12],
},
{
name: 'Root + 5th + Sus',
desc: 'Power chord with the fourth on top',
tip: 'Very rock and dramatic. The sus4 on top gives it an anthemic, U2-esque quality.',
lh: [0, 7],
rh: [7, 12, 17],
},
],
sus2: [
{
name: 'Full Sus2',
desc: 'Open, airy — the 2nd instead of 3rd',
tip: 'Sus2 chords feel free and unanchored. Great for intros and ambient sections.',
lh: [0],
rh: [0, 2, 7],
},
{
name: 'Spread Sus2',
desc: 'Wide and spacious — very ambient',
tip: 'The 2nd voiced wide feels like an open landscape. Sigur Rós territory.',
lh: [0, 7],
rh: [12, 14, 19],
},
],
half_dim: [
{
name: 'Full m7♭5',
desc: 'Half-diminished — minor 7th with flat 5',
tip: 'The ii chord in minor keys (e.g. Bø in C minor). Tense but smoother than full diminished.',
lh: [0],
rh: [0, 3, 6, 10],
},
{
name: 'Shell (1-♭3-♭7)',
desc: 'Skip the flat 5 — cleaner jazz comp',
tip: 'The ♭5 is optional in jazz. Root + minor 3rd + minor 7th is clean and functional.',
lh: [0],
rh: [3, 10],
},
],
maj6: [
{
name: 'Full Maj6',
desc: 'Major with added 6th — vintage jazz sound',
tip: 'Maj6 and min7 are inversions of each other. Interchangeable in many jazz contexts.',
lh: [0],
rh: [0, 4, 7, 9],
},
{
name: 'Shell (1-3-6)',
desc: 'Clean and retro — skip the 5th',
tip: 'The 6th adds sweetness without too much colour. Django Reinhardt loved this voicing.',
lh: [0],
rh: [4, 9],
},
],
min6: [
{
name: 'Full Min6',
desc: 'Minor with major 6th — exotic and dark',
tip: 'The major 6th over a minor triad is a flamenco and tango staple. Very striking colour.',
lh: [0],
rh: [0, 3, 7, 9],
},
{
name: 'Shell (1-♭3-6)',
desc: 'Tense and colourful — the ♭3+6 tension',
tip: 'The minor 3rd + major 6th interval is the characteristic clash of min6. Lean into it.',
lh: [0],
rh: [3, 9],
},
],
add9: [
{
name: 'Full Add9',
desc: 'Major chord + 9th — no 7th, stays bright',
tip: 'The 9th adds colour without the jazz sophistication of maj9. Feels modern and open.',
lh: [0],
rh: [0, 4, 7, 14],
},
{
name: 'No Root Add9',
desc: 'Skip root in right — 3rd + 9th float',
tip: 'Root in left, right hand plays 3rd + 5th + 9th. Airy and modern — Radiohead / Coldplay territory.',
lh: [0],
rh: [4, 7, 14],
},
{
name: 'Sus2-style',
desc: 'Add9 voiced as sus2 clusters',
tip: 'Place the 9th close to the root (2nd instead of 9th). Creates a shimmering cluster effect.',
lh: [0, 7],
rh: [2, 4, 7],
},
],
}
// Fallback for chord types not in the map
const GENERIC_PIANO = [
{
name: 'Full Chord',
desc: 'Root in left, chord tones in right',
tip: 'Play the chord tones in right hand while anchoring the root in the left.',
lh: [0],
rh: [0], // will be replaced by computed intervals
},
]
// ─── Compute functions ────────────────────────────────────────────────────────
// Parse a chord name like "C#m7" → { rootPc: 1, type: 'min7' }
// Mirrors the logic in theory.js parseChord
function parseChord(name) {
if (!name) return null
const NOTES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
const NOTES_FLAT = ['C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B']
const suffixMap = {
'': 'maj', 'm': 'min', 'min': 'min', 'maj': 'maj',
'7': 'dom7', 'maj7': 'maj7', 'm7': 'min7', 'min7': 'min7',
'dim': 'dim', 'dim7': 'dim7', 'm7b5': 'half_dim', 'ø': 'half_dim', 'ø7': 'half_dim',
'aug': 'aug', '+': 'aug',
'sus4': 'sus4', 'sus2': 'sus2',
'6': 'maj6', 'm6': 'min6', 'min6': 'min6',
'add9': 'add9',
}
let rest = name
let root = ''
if (rest.length > 1 && (rest[1] === '#' || rest[1] === 'b')) {
root = rest.slice(0, 2); rest = rest.slice(2)
} else {
root = rest.slice(0, 1); rest = rest.slice(1)
}
let rootPc = NOTES.indexOf(root)
if (rootPc === -1) rootPc = NOTES_FLAT.indexOf(root)
if (rootPc === -1) return null
const type = suffixMap[rest] ?? 'maj'
return { rootPc, type }
}
/**
* Returns an array of voicing objects for a given chord name.
* Each voicing: { label, frets[6], fingers[6], barre?, baseFret }
* frets values: number (fret) or 'x' (muted) or 0 (open)
*/
export function getGuitarVoicings(chordName) {
const parsed = parseChord(chordName)
if (!parsed) return []
const { rootPc, type } = parsed
const shapes = GUITAR_SHAPES[type] ?? GUITAR_SHAPES.maj
const result = []
for (const shape of shapes) {
if (shape.type === 'open') {
if (shape.onlyRoot !== undefined && shape.onlyRoot !== rootPc) continue
result.push({
label: shape.label,
frets: shape.frets,
fingers: shape.fingers,
barre: null,
baseFret: 1,
})
continue
}
// barre shape: compute rootFret on rootStr.
// OPEN is ordered [s6 … s1] and rootStr is 1-indexed with 6 = low E,
// so string N lives at index 6 - N (same convention as ChordDiagram.jsx
// and chordAudio.guitarShapeToNotes).
const strIdx = 6 - shape.rootStr // 0=s6 … 5=s1
const openPc = OPEN[strIdx]
let rootFret = (rootPc - openPc + 12) % 12
// Compute absolute frets
const frets = shape.offsets.map((off, i) => {
if (off === 'x') return 'x'
return rootFret + off
})
// Skip impossible positions
if (frets.some(f => typeof f === 'number' && f < 0)) continue
const maxFret = Math.max(...frets.filter(f => f !== 'x'))
if (maxFret > 15) continue
// baseFret: start diagram so the chord fits in 5 frets
const minFret = Math.min(...frets.filter(f => f !== 'x' && f > 0))
const baseFret = rootFret === 0 ? 1 : Math.max(1, minFret)
// Compute barre position if applicable
let barre = null
if (shape.barre) {
barre = {
fret: rootFret + shape.barre.fo,
fromStr: shape.barre.fromStr,
toStr: shape.barre.toStr,
}
}
// For open-string E/A chords (rootFret=0), no barre needed
if (rootFret === 0) barre = null
result.push({
label: shape.label + (rootFret === 0 ? ' (Open)' : rootFret > 5 ? ` — Fret ${rootFret}` : ''),
frets,
fingers: shape.fingers,
barre,
baseFret,
})
}
return result
}
/**
* Returns piano techniques for a chord name.
* Each technique has: { name, desc, tip, lh (intervals), rh (intervals) }
*/
export function getPianoTechniques(chordName) {
const parsed = parseChord(chordName)
if (!parsed) return []
const { type } = parsed
return PIANO_TECHNIQUES[type] ?? GENERIC_PIANO
}
export { parseChord }
// D-21: expose the raw shape library for the VoicingBrowser (read-only consumer —
// it renders shapes via ChordDiagram and plays them via chordAudio.guitarShapeToNotes).
export { GUITAR_SHAPES }