edu panel
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
import { NOTES, CHORD_TYPES } from './theory'
|
||||
|
||||
// ─── Parse helper (self-contained, mirrors voicings.js) ───────────────────────
|
||||
export function parseChord(name) {
|
||||
if (!name) return null
|
||||
const FLAT = ['C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B']
|
||||
let rest = name
|
||||
let root = rest.length > 1 && (rest[1] === '#' || rest[1] === 'b')
|
||||
? (rest = rest.slice(2), name.slice(0, 2))
|
||||
: (rest = rest.slice(1), name.slice(0, 1))
|
||||
let rootPc = NOTES.indexOf(root)
|
||||
if (rootPc === -1) rootPc = FLAT.indexOf(root)
|
||||
if (rootPc === -1) return null
|
||||
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',
|
||||
'aug': 'aug', '+': 'aug',
|
||||
'sus4': 'sus4', 'sus2': 'sus2',
|
||||
'6': 'maj6', 'm6': 'min6', 'add9': 'add9',
|
||||
}
|
||||
return { rootPc, type: suffixMap[rest] ?? 'maj' }
|
||||
}
|
||||
|
||||
function chordName(rootPc, type) {
|
||||
return NOTES[rootPc] + (CHORD_TYPES[type]?.suffix ?? '')
|
||||
}
|
||||
|
||||
// ─── Famous progressions ──────────────────────────────────────────────────────
|
||||
// degrees[] — semitone offsets from tonic
|
||||
// qualities[] — chord type keys (CHORD_TYPES) for each degree
|
||||
// mode — the mode this progression is naturally written in
|
||||
// styleVariations — how genre players re-harmonise these chords
|
||||
|
||||
export const FAMOUS_PROGRESSIONS = [
|
||||
{
|
||||
id: 'axis',
|
||||
name: 'Axis Progression',
|
||||
pattern: 'I – V – vi – IV',
|
||||
degrees: [0, 7, 9, 5],
|
||||
qualities: ['maj', 'maj', 'min', 'maj'],
|
||||
mode: 'major',
|
||||
genre: ['Pop', 'Rock'],
|
||||
songs: ['Let It Be — Beatles', 'With or Without You — U2', 'Someone Like You — Adele', "Don't Stop Believin' — Journey", 'Wonderwall — Oasis', 'Demons — Imagine Dragons'],
|
||||
description: 'The defining progression of modern pop. Works in every genre and tempo.',
|
||||
tip: 'Try starting on the vi instead — suddenly it feels darker and more yearning.',
|
||||
styleVariations: [
|
||||
{ label: 'Jazz', pattern: 'Imaj7 – V7 – vim7 – IVmaj7', qualities: ['maj7','dom7','min7','maj7'] },
|
||||
{ label: 'Soul', pattern: 'Imaj9 – V9 – vim9 – IVmaj9', qualities: ['maj7','dom7','min7','maj7'] },
|
||||
{ label: 'Blues', pattern: 'I7 – V7 – vi7 – IV7', qualities: ['dom7','dom7','dom7','dom7'] },
|
||||
{ label: 'Ambient', pattern: 'Isus2 – Vsus2 – vim7 – IVadd9', qualities: ['sus2','sus2','min7','add9'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'fifties',
|
||||
name: '50s / Doo-Wop',
|
||||
pattern: 'I – vi – IV – V',
|
||||
degrees: [0, 9, 5, 7],
|
||||
qualities: ['maj', 'min', 'maj', 'maj'],
|
||||
mode: 'major',
|
||||
genre: ['Pop', 'Doo-Wop', 'Rock'],
|
||||
songs: ['Stand By Me — Ben E. King', 'Earth Angel', 'Blue Moon', 'Unchained Melody', 'Every Breath You Take — Police'],
|
||||
description: 'The doo-wop backbone. Sweet, nostalgic, universally singable and timeless.',
|
||||
tip: "The vi chord is the emotional pivot — it's the same root as in the Axis, just a different order.",
|
||||
styleVariations: [
|
||||
{ label: 'Jazz', pattern: 'Imaj7 – vim7 – IVmaj7 – V7', qualities: ['maj7','min7','maj7','dom7'] },
|
||||
{ label: 'Soul', pattern: 'I6 – vim7 – IV – V7sus4', qualities: ['maj6','min7','maj','sus4'] },
|
||||
{ label: 'Funk', pattern: 'Imaj9 – vim9 – IV9 – V9', qualities: ['maj7','min7','dom7','dom7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'canon',
|
||||
name: 'Canon / Pachelbel',
|
||||
pattern: 'I – V – vi – iii – IV – I – IV – V',
|
||||
degrees: [0, 7, 9, 4, 5, 0, 5, 7],
|
||||
qualities: ['maj','maj','min','min','maj','maj','maj','maj'],
|
||||
mode: 'major',
|
||||
genre: ['Classical', 'Pop', 'Rock'],
|
||||
songs: ['Canon in D — Pachelbel', 'Basket Case — Green Day', 'Go West — Pet Shop Boys', 'Graduation — Vitamin C'],
|
||||
description: 'Baroque timelessness. The descending bass line creates inevitable forward motion.',
|
||||
tip: 'The iii chord is the secret ingredient — it bridges vi and IV with aristocratic weight.',
|
||||
styleVariations: [
|
||||
{ label: 'Rock', pattern: 'Power chords all the way down', qualities: [] },
|
||||
{ label: 'Neo-soul',pattern: 'Imaj9 – V9 – vim9 – iiim7 – IVmaj9', qualities: ['maj7','dom7','min7','min7','maj7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'two_five_one',
|
||||
name: 'ii – V – I (Jazz)',
|
||||
pattern: 'iim7 – V7 – Imaj7',
|
||||
degrees: [2, 7, 0],
|
||||
qualities: ['min7', 'dom7', 'maj7'],
|
||||
mode: 'major',
|
||||
genre: ['Jazz', 'Bossa Nova'],
|
||||
songs: ['Autumn Leaves', 'All The Things You Are', 'Fly Me To The Moon', 'The Girl From Ipanema', 'Misty'],
|
||||
description: 'The bedrock of jazz harmony. The tritone in V7 resolves to I with beautiful tension.',
|
||||
tip: 'The ii chord pre-resolves the V — together they create an inevitable pull to the I.',
|
||||
styleVariations: [
|
||||
{ label: 'Bossa', pattern: 'iim7 – V7(9) – Imaj9', qualities: ['min7','dom7','maj7'] },
|
||||
{ label: 'Bebop', pattern: 'iim7 – V7(♭9) – Imaj7(#11)', qualities: ['min7','dom7','maj7'] },
|
||||
{ label: 'Modal', pattern: 'im7 – IV7 (Dorian vamp)', qualities: ['min7','dom7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'blues_12',
|
||||
name: '12-Bar Blues',
|
||||
pattern: 'I – I – I – I – IV – IV – I – I – V – IV – I – V',
|
||||
degrees: [0, 0, 0, 0, 5, 5, 0, 0, 7, 5, 0, 7],
|
||||
qualities: ['dom7','dom7','dom7','dom7','dom7','dom7','dom7','dom7','dom7','dom7','dom7','dom7'],
|
||||
mode: 'major',
|
||||
genre: ['Blues', 'Rock', 'Jazz'],
|
||||
songs: ['Johnny B. Goode — Chuck Berry', 'Pride & Joy — SRV', 'Crossroads — Robert Johnson', 'Hound Dog', 'Folsom Prison Blues — Cash'],
|
||||
description: 'The foundation of rock and blues. Master this and you can jam with anyone on Earth.',
|
||||
tip: 'All three chords are dominant 7ths — that dissonance is what makes blues feel so restless.',
|
||||
styleVariations: [
|
||||
{ label: 'Shuffle', pattern: 'I7 – IV7 – V7 with shuffle rhythm', qualities: ['dom7','dom7','dom7'] },
|
||||
{ label: 'Jazz', pattern: 'Imaj7 – IV7 – iim7 – V7 quick changes', qualities: ['maj7','dom7','min7','dom7'] },
|
||||
{ label: 'Minor', pattern: 'im7 – ivm7 – vm7 (minor blues)', qualities: ['min7','min7','min7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'andalusian',
|
||||
name: 'Andalusian Cadence',
|
||||
pattern: 'i – ♭VII – ♭VI – V',
|
||||
degrees: [0, 10, 8, 7],
|
||||
qualities: ['min', 'maj', 'maj', 'maj'],
|
||||
mode: 'minor',
|
||||
genre: ['Flamenco', 'Rock', 'Classical'],
|
||||
songs: ['Stairway to Heaven intro — Led Zeppelin', 'Hit the Road Jack', 'Sultans of Swing — Dire Straits', 'White Christmas'],
|
||||
description: 'Descending bass line creates unstoppable forward motion. Timeless, dramatic, inevitable.',
|
||||
tip: 'The final V chord (major) is the harmonic surprise in a minor context — forces resolution.',
|
||||
styleVariations: [
|
||||
{ label: 'Flamenco', pattern: 'im – ♭VII – ♭VI – V7', qualities: ['min','maj','maj','dom7'] },
|
||||
{ label: 'Rock', pattern: 'im5 – ♭VII5 – ♭VI5 – V5 (power)', qualities: ['min','maj','maj','maj'] },
|
||||
{ label: 'Jazz', pattern: 'im(maj7) – ♭VIImaj7 – ♭VImaj7 – V7(#9)', qualities: ['min7','maj7','maj7','dom7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'minor_anthem',
|
||||
name: 'Minor Anthem',
|
||||
pattern: 'i – ♭VI – ♭III – ♭VII',
|
||||
degrees: [0, 8, 3, 10],
|
||||
qualities: ['min', 'maj', 'maj', 'maj'],
|
||||
mode: 'minor',
|
||||
genre: ['Rock', 'Pop', 'Metal'],
|
||||
songs: ['Numb — Linkin Park', 'In The End — Linkin Park', 'Boulevard of Broken Dreams — Green Day', 'Creep — Radiohead', 'Smells Like Teen Spirit — Nirvana'],
|
||||
description: 'The anthem of angst. Powerful, relentless, emotionally direct.',
|
||||
tip: 'Every chord is major except the tonic — the contrast makes the i feel even more desperate.',
|
||||
styleVariations: [
|
||||
{ label: 'Stripped', pattern: 'im7 – ♭VImaj7 – ♭IIImaj7 – ♭VIImaj7', qualities: ['min7','maj7','maj7','maj7'] },
|
||||
{ label: 'Epic', pattern: 'im – ♭VI – ♭III – ♭VII with sus2 variants', qualities: ['min','maj','maj','maj'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'minor_oscillate',
|
||||
name: 'Minor Oscillation',
|
||||
pattern: 'i – ♭VII – ♭VI – ♭VII',
|
||||
degrees: [0, 10, 8, 10],
|
||||
qualities: ['min', 'maj', 'maj', 'maj'],
|
||||
mode: 'minor',
|
||||
genre: ['Rock', 'Folk', 'Pop'],
|
||||
songs: ['All Along the Watchtower — Dylan/Hendrix', "Knockin' on Heaven's Door — Dylan", 'Pumped Up Kicks — Foster the People', 'Africa — Toto'],
|
||||
description: 'The ♭VII oscillates back and forth — creates hypnotic looping energy.',
|
||||
tip: 'Works as a 2-bar vamp (i – ♭VII) or a full 4-bar loop. The ♭VI adds breathing room.',
|
||||
styleVariations: [
|
||||
{ label: 'Folk', pattern: 'im – ♭VII – ♭VI – ♭VII fingerpicked', qualities: ['min','maj','maj','maj'] },
|
||||
{ label: 'Rock', pattern: 'im – ♭VII – ♭VI power chords', qualities: ['min','maj','maj','maj'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'mixolydian',
|
||||
name: 'Mixolydian Rock',
|
||||
pattern: 'I – ♭VII – IV',
|
||||
degrees: [0, 10, 5],
|
||||
qualities: ['maj', 'maj', 'maj'],
|
||||
mode: 'major',
|
||||
genre: ['Rock', 'Folk', 'Celtic'],
|
||||
songs: ['Sweet Home Alabama — Lynyrd Skynyrd', 'La Grange — ZZ Top', 'Werewolves of London — Warren Zevon', 'Norwegian Wood — Beatles'],
|
||||
description: 'The ♭VII chord defines Mixolydian mode. Swagger and swagger only.',
|
||||
tip: 'In standard major, the VII is diminished. Flattening it to ♭VII gives you a major chord — that shift is everything.',
|
||||
styleVariations: [
|
||||
{ label: 'Celtic', pattern: 'I – ♭VII – IV – I alternating', qualities: ['maj','maj','maj','maj'] },
|
||||
{ label: 'Funk', pattern: 'I9 – ♭VII9 – IV9 dominant 9ths', qualities: ['dom7','dom7','dom7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dorian_vamp',
|
||||
name: 'Dorian Vamp',
|
||||
pattern: 'i – IV',
|
||||
degrees: [0, 5],
|
||||
qualities: ['min', 'maj'],
|
||||
mode: 'minor',
|
||||
genre: ['Rock', 'Jazz', 'Funk'],
|
||||
songs: ['Oye Como Va — Santana', 'So What — Miles Davis', 'Scarborough Fair', 'Eleanor Rigby verse — Beatles', 'Mad World — Tears for Fears'],
|
||||
description: 'The major IV over a minor i chord signals Dorian mode. Smooth, open, sophisticated.',
|
||||
tip: 'Natural minor would have a minor iv. The major IV is Dorian\'s signature — it feels both sad and groovy.',
|
||||
styleVariations: [
|
||||
{ label: 'Jazz', pattern: 'im7 – IV7 (comp beneath a soloist)', qualities: ['min7','dom7'] },
|
||||
{ label: 'Funk', pattern: 'im9 – IV13 (layered synth and guitar)', qualities: ['min7','dom7'] },
|
||||
{ label: 'Rock', pattern: 'im – IV – im – IV (guitar riff)', qualities: ['min','maj'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'jazz_turnaround',
|
||||
name: 'Jazz Turnaround',
|
||||
pattern: 'I – vi – ii – V',
|
||||
degrees: [0, 9, 2, 7],
|
||||
qualities: ['maj7', 'min7', 'min7', 'dom7'],
|
||||
mode: 'major',
|
||||
genre: ['Jazz', 'Swing'],
|
||||
songs: ['I Got Rhythm — Gershwin', 'Rhythm Changes', 'How High the Moon', 'countless jazz standards'],
|
||||
description: 'The jazz turnaround — loops back to the top of the form with elegant inevitability.',
|
||||
tip: 'Each chord resolves down a fifth to the next. That chain of fifths is what makes jazz sound "right".',
|
||||
styleVariations: [
|
||||
{ label: 'Bebop', pattern: 'Imaj7 – vim7 – iim7 – V7(♭9)', qualities: ['maj7','min7','min7','dom7'] },
|
||||
{ label: 'Tritone', pattern: 'Imaj7 – ♭III7 – iim7 – ♭II7 (tritone subs)', qualities: ['maj7','dom7','min7','dom7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'phrygian',
|
||||
name: 'Phrygian Tension',
|
||||
pattern: 'i – ♭II',
|
||||
degrees: [0, 1],
|
||||
qualities: ['min', 'maj'],
|
||||
mode: 'minor',
|
||||
genre: ['Flamenco', 'Metal', 'Film'],
|
||||
songs: ['Game of Thrones theme', 'Spanish flamenco standards', 'heavy metal riffs', 'El Tango de Roxanne'],
|
||||
description: 'The ♭II (Neapolitan) chord creates extreme tension. Spanish fire and metal darkness.',
|
||||
tip: 'Just two chords — the half-step relationship between roots is what makes it feel so tense.',
|
||||
styleVariations: [
|
||||
{ label: 'Flamenco', pattern: 'im – ♭II – im with fast strumming', qualities: ['min','maj','min'] },
|
||||
{ label: 'Metal', pattern: 'im5 – ♭II5 power chord riff', qualities: ['min','maj'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'minor_pop',
|
||||
name: 'Sad Pop Minor',
|
||||
pattern: 'vi – IV – I – V',
|
||||
degrees: [9, 5, 0, 7],
|
||||
qualities: ['min', 'maj', 'maj', 'maj'],
|
||||
mode: 'major',
|
||||
genre: ['Pop', 'Rock', 'Indie'],
|
||||
songs: ['Zombie — Cranberries', 'Torn — Natalie Imbruglia', 'Apologize — Timbaland', 'Fix You — Coldplay'],
|
||||
description: 'Same chords as the Axis — just starting on the vi. Instantly feels darker and more yearning.',
|
||||
tip: 'Which chord you start on changes everything emotionally. This is the Axis heard through minor eyes.',
|
||||
styleVariations: [
|
||||
{ label: 'Soul', pattern: 'vim7 – IVmaj7 – Imaj7 – V9', qualities: ['min7','maj7','maj7','dom7'] },
|
||||
{ label: 'Indie', pattern: 'vim7 – IVadd9 – Iadd9 – Vsus4', qualities: ['min7','add9','add9','sus4'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'lydian_float',
|
||||
name: 'Lydian Float',
|
||||
pattern: 'I – II',
|
||||
degrees: [0, 2],
|
||||
qualities: ['maj', 'maj'],
|
||||
mode: 'major',
|
||||
genre: ['Film', 'Jazz', 'Pop'],
|
||||
songs: ['The Simpsons theme', 'Joe Satriani — Flying in a Blue Dream', 'many John Williams cues', 'Man or Muppet'],
|
||||
description: 'The raised ♯4 in Lydian makes the II chord major instead of minor. Dreamy, floating, magical.',
|
||||
tip: 'In normal major, the II chord is minor. Making it major (Lydian) lifts the whole progression off the ground.',
|
||||
styleVariations: [
|
||||
{ label: 'Film', pattern: 'Imaj7 – IImaj7 slowly held', qualities: ['maj7','maj7'] },
|
||||
{ label: 'Jazz', pattern: 'Imaj7(#11) — the Lydian 7th chord', qualities: ['maj7','maj7'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'sensitive',
|
||||
name: 'Sensitive Oscillation',
|
||||
pattern: 'vi – V – IV – V',
|
||||
degrees: [9, 7, 5, 7],
|
||||
qualities: ['min', 'maj', 'maj', 'maj'],
|
||||
mode: 'major',
|
||||
genre: ['Pop', 'Indie'],
|
||||
songs: ['Mad World — Tears for Fears', 'Every Breath You Take — Police', 'Losing My Religion — REM'],
|
||||
description: 'Oscillates between vi and IV through V. Aching, introspective, perpetually unresolved.',
|
||||
tip: 'The V never quite resolves to I — it keeps bouncing back to the IV or vi. That suspension is the emotion.',
|
||||
styleVariations: [
|
||||
{ label: 'Indie', pattern: 'vim7 – Vsus4 – IVadd9 – V', qualities: ['min7','sus4','add9','maj'] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
// ─── Chord substitution options ───────────────────────────────────────────────
|
||||
// For each chord type: array of { type, label, tip } suggestions
|
||||
export const CHORD_SUBSTITUTIONS = {
|
||||
maj: [
|
||||
{ type: 'maj7', tip: 'Add the major 7th — dreamy jazz colour' },
|
||||
{ type: 'add9', tip: 'Add the 9th — modern, open, Coldplay-ish' },
|
||||
{ type: 'maj6', tip: 'Add major 6th — vintage Django jazz sweetness' },
|
||||
{ type: 'sus2', tip: 'Replace 3rd with 2nd — airy and ambiguous' },
|
||||
{ type: 'sus4', tip: 'Suspend then resolve — creates rhythmic motion' },
|
||||
],
|
||||
min: [
|
||||
{ type: 'min7', tip: 'Add the minor 7th — smooth soul and jazz' },
|
||||
{ type: 'add9', tip: 'Add 9th over minor — bittersweet modern ache (Radiohead)' },
|
||||
{ type: 'min6', tip: 'Add major 6th over minor — flamenco and tango colour' },
|
||||
{ type: 'sus2', tip: 'Remove 3rd entirely — ambiguous and floating' },
|
||||
{ type: 'half_dim', tip: 'Flatten the 5th — half-diminished, much darker' },
|
||||
],
|
||||
dom7: [
|
||||
{ type: 'maj7', tip: 'Raise the 7th — softer, much less tension' },
|
||||
{ type: 'min7', tip: 'Lower the 3rd too — darkens the dominant' },
|
||||
{ type: 'sus4', tip: 'Replace 3rd with 4th — funky unresolved suspension' },
|
||||
{ type: 'dim7', tip: 'Diminished substitute — extreme tension before resolution' },
|
||||
],
|
||||
maj7: [
|
||||
{ type: 'maj', tip: 'Simplify — strip back to triad, rawer feel' },
|
||||
{ type: 'add9', tip: 'Drop 7th, add 9th — brighter and more open' },
|
||||
{ type: 'maj6', tip: 'Swap 7th for 6th — retro jazz, less ethereal' },
|
||||
{ type: 'dom7', tip: 'Flatten 7th — suddenly bluesy with tension' },
|
||||
],
|
||||
min7: [
|
||||
{ type: 'min', tip: 'Strip back — rawer, more aggressive feel' },
|
||||
{ type: 'half_dim', tip: 'Flatten the 5th — half-dim, darker and more tense' },
|
||||
{ type: 'min6', tip: 'Add major 6th — sophisticated jazz minor colour' },
|
||||
{ type: 'dom7', tip: 'Make it dominant — strong pull to resolve' },
|
||||
],
|
||||
dim: [
|
||||
{ type: 'dim7', tip: 'Add dim7 — fully symmetric, equally unstable' },
|
||||
{ type: 'half_dim', tip: 'Half-dim — softer, more melodic tension' },
|
||||
{ type: 'min', tip: 'Simplify to minor — much less dissonant' },
|
||||
],
|
||||
dim7: [
|
||||
{ type: 'dim', tip: 'Remove 7th — triad version, slightly less tense' },
|
||||
{ type: 'half_dim', tip: 'Raise one note to half-dim — softer resolution' },
|
||||
{ type: 'min7', tip: 'Raise ♭5 — from dark to smooth in one note' },
|
||||
],
|
||||
aug: [
|
||||
{ type: 'maj', tip: 'Resolve the aug5 down to 5 — release the tension' },
|
||||
{ type: 'dom7', tip: 'Add ♭7 over aug — double tension before a big resolution' },
|
||||
],
|
||||
sus4: [
|
||||
{ type: 'maj', tip: 'Resolve — drop the 4th to the 3rd (classic sus → maj)' },
|
||||
{ type: 'sus2', tip: 'Switch suspension — from 4th to 2nd, different feel' },
|
||||
{ type: 'dom7', tip: 'Add ♭7 too — 7sus4, funky and unresolved' },
|
||||
],
|
||||
sus2: [
|
||||
{ type: 'maj', tip: 'Fill in the 3rd — resolve the suspension clearly' },
|
||||
{ type: 'sus4', tip: 'Swap 2nd for 4th — different flavour of openness' },
|
||||
{ type: 'add9', tip: 'Add the 3rd back — sus2 becomes a richer add9' },
|
||||
],
|
||||
half_dim: [
|
||||
{ type: 'dim7', tip: 'Add dim7 — more symmetric and tense' },
|
||||
{ type: 'min7', tip: 'Raise the ♭5 to 5 — suddenly much smoother' },
|
||||
{ type: 'min', tip: 'Strip back — simple minor triad' },
|
||||
],
|
||||
maj6: [
|
||||
{ type: 'maj7', tip: 'Swap 6th for 7th — more modern jazz, more ethereal' },
|
||||
{ type: 'add9', tip: 'Replace 6th with 9th — brighter, contemporary feel' },
|
||||
],
|
||||
min6: [
|
||||
{ type: 'min7', tip: 'Swap 6th for 7th — smoother, less exotic' },
|
||||
{ type: 'half_dim', tip: 'Enharmonic trick — min6 and half-dim share notes' },
|
||||
],
|
||||
add9: [
|
||||
{ type: 'maj7', tip: 'Add the 7th — full maj9 sound, very lush' },
|
||||
{ type: 'sus2', tip: 'Remove the 3rd — purely suspended' },
|
||||
{ type: 'maj', tip: 'Strip the 9th — clean triad' },
|
||||
],
|
||||
}
|
||||
|
||||
// ─── Compute chord names for a famous progression in a given key ──────────────
|
||||
export function progressionInKey(famousProg, keyRoot) {
|
||||
const rootPc = NOTES.indexOf(keyRoot)
|
||||
if (rootPc === -1) return []
|
||||
return famousProg.degrees.map((d, i) => {
|
||||
const pc = (rootPc + d) % 12
|
||||
const type = famousProg.qualities[i] ?? 'maj'
|
||||
return chordName(pc, type)
|
||||
})
|
||||
}
|
||||
|
||||
// Style variation chords in a given key
|
||||
export function styleVariationInKey(styleVar, famousProg, keyRoot) {
|
||||
if (!styleVar.qualities?.length) return []
|
||||
const rootPc = NOTES.indexOf(keyRoot)
|
||||
if (rootPc === -1) return []
|
||||
return famousProg.degrees.slice(0, styleVar.qualities.length).map((d, i) => {
|
||||
const pc = (rootPc + d) % 12
|
||||
return chordName(pc, styleVar.qualities[i] ?? 'maj')
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Similarity matching ──────────────────────────────────────────────────────
|
||||
export function findSimilarProgressions(detectedChords, keyInfo) {
|
||||
if (!detectedChords?.length || !keyInfo?.root) return []
|
||||
const rootPc = NOTES.indexOf(keyInfo.root)
|
||||
if (rootPc === -1) return []
|
||||
|
||||
// Convert detected chords to semitone offsets from key root
|
||||
const detectedPcs = detectedChords
|
||||
.map(c => { const p = parseChord(c); return p ? (p.rootPc - rootPc + 12) % 12 : null })
|
||||
.filter(v => v !== null)
|
||||
if (!detectedPcs.length) return []
|
||||
|
||||
const detectedSet = new Set(detectedPcs)
|
||||
const results = []
|
||||
|
||||
for (const prog of FAMOUS_PROGRESSIONS) {
|
||||
const progPcs = prog.degrees.map(d => d % 12)
|
||||
const progSet = new Set(progPcs)
|
||||
|
||||
// Rotation match: does detected sequence appear in famous prog (as cyclic rotation)?
|
||||
let rotScore = 0
|
||||
const compareLen = Math.min(detectedPcs.length, progPcs.length)
|
||||
for (let rot = 0; rot < progPcs.length; rot++) {
|
||||
let hits = 0
|
||||
for (let i = 0; i < compareLen; i++) {
|
||||
if (detectedPcs[i] === progPcs[(rot + i) % progPcs.length]) hits++
|
||||
}
|
||||
rotScore = Math.max(rotScore, hits / compareLen)
|
||||
}
|
||||
|
||||
// Jaccard similarity (chord-set overlap regardless of order)
|
||||
const inter = [...detectedSet].filter(d => progSet.has(d)).length
|
||||
const union = new Set([...detectedSet, ...progSet]).size
|
||||
const jaccardScore = inter / union
|
||||
|
||||
const score = Math.max(rotScore, jaccardScore * 0.8)
|
||||
if (score >= 0.35) results.push({ ...prog, score })
|
||||
}
|
||||
|
||||
return results.sort((a, b) => b.score - a.score).slice(0, 4)
|
||||
}
|
||||
|
||||
// ─── Substitutions for a chord name ──────────────────────────────────────────
|
||||
export function getChordSubstitutions(chordName) {
|
||||
const p = parseChord(chordName)
|
||||
if (!p) return []
|
||||
const subs = CHORD_SUBSTITUTIONS[p.type] ?? []
|
||||
return subs.map(sub => ({
|
||||
...sub,
|
||||
chord: NOTES[p.rootPc] + (CHORD_TYPES[sub.type]?.suffix ?? ''),
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
// ─── 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: [
|
||||
{ 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] },
|
||||
],
|
||||
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] },
|
||||
],
|
||||
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 } },
|
||||
],
|
||||
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 } },
|
||||
],
|
||||
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 } },
|
||||
],
|
||||
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 } },
|
||||
{ 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
|
||||
const strIdx = shape.rootStr - 1 // 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 }
|
||||
Reference in New Issue
Block a user