import { type Chord, chordFrom } from "./chord.ts"; import { type Key, MODES } from "./key.ts"; import { mod, pcOf, spellDegree } from "./pitch.ts"; // all in root position: where the bass goes is left to voicing const has = (c: Chord, ...tones: number[]) => tones.every(t => c.tones.includes(t)); const without = (c: Chord, ...tones: number[]) => c.tones.filter(t => !tones.includes(t)); const inKey = (key: Key, pc: number) => MODES[key.mode].steps.includes(mod(pc - pcOf(key.tonic), 12)); const isMajor = (c: Chord) => has(c, 4) && !has(c, 3); const isMinor = (c: Chord) => has(c, 3) && !has(c, 4); export const isDominant = (c: Chord): boolean => has(c, 4, 10) && !has(c, 3); /** C ↔ Cm, keeping everything but the third */ export function parallel(c: Chord): Chord | undefined { if (has(c, 6) && !has(c, 7)) return undefined; // diminished: no major counterpart if (isMajor(c)) return chordFrom(c.root, [...without(c, 4), 3]); if (isMinor(c)) return chordFrom(c.root, [...without(c, 3), 4]); return undefined; } /** C ↔ Am, Cmaj7 ↔ Am7: the chord a minor third away sharing most of its notes */ export function relative(c: Chord): Chord | undefined { if (!has(c, 7)) return undefined; if (isMajor(c) && !has(c, 10)) { return chordFrom(spellDegree(c.root, 5, 9), has(c, 11) ? [0, 3, 7, 10] : [0, 3, 7]); } if (isMinor(c) && !has(c, 11)) { return chordFrom(spellDegree(c.root, 2, 3), has(c, 10) ? [0, 4, 7, 11] : [0, 4, 7]); } return undefined; } /** G7 → D♭7: the dominant a tritone away, with the same tritone inside it */ export function tritoneSub(c: Chord): Chord | undefined { return isDominant(c) ? chordFrom(spellDegree(c.root, 4, 6), c.tones) : undefined; } /** add a 7th, picking whichever fits the key (C → Cmaj7, G → G7 in C), or take it away */ export function toggleSeventh(c: Chord, key: Key): Chord | undefined { const dim = has(c, 3, 6) && !has(c, 7); if (has(c, 10) || has(c, 11) || (dim && has(c, 9))) return chordFrom(c.root, without(c, 9, 10, 11)); const root = pcOf(c.root); const [preferred, fallback] = dim ? [10, 9] : isMajor(c) ? [11, 10] : [10, 11]; const seventh = inKey(key, root + preferred) ? preferred : inKey(key, root + fallback) ? fallback : dim ? 9 : 10; return chordFrom(c.root, [...c.tones, seventh]); } /** replace the third with a 2nd or 4th; asked again, put the key's third back */ export function suspend(c: Chord, key: Key, tone: 2 | 5): Chord | undefined { if (has(c, tone) && !has(c, 3) && !has(c, 4)) { const third = inKey(key, pcOf(c.root) + 4) ? 4 : 3; return chordFrom(c.root, [...without(c, tone), third]); } return chordFrom(c.root, [...without(c, 3, 4, 2, 5), tone]); }