// ─── 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–♯5–R–3; 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 (R–5–6–3; 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 (R–5–6–♭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 (R–5–R–♭3–6; 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): R–5–9–3. { 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 }