char-slop/chordpad.vibed.software

git clone https://git.t4t.associates/char-slop/chordpad.vibed.software

Charlotte Somexperiment: chord connectionsa427fe3

main
2.2 KiB70 linesraw
1import { assertEquals } from "@std/assert";
2import { type Chord, chordName, parseChord } from "./chord.ts";
3import type { Key, ModeId } from "./key.ts";
4import { parseNote } from "./pitch.ts";
5import { parallel, relative, suspend, toggleSeventh, tritoneSub } from "./transform.ts";
6
7const key = (tonic = "C", mode: ModeId = "ionian"): Key => ({
8  tonic: parseNote(tonic)!.note,
9  mode,
10});
11const C = key();
12const apply = (f: (c: Chord) => Chord | undefined, ...symbols: string[]) =>
13  symbols.map(s => {
14    const out = f(parseChord(s, C)!);
15    return out && chordName(out);
16  });
17
18Deno.test("parallel swaps the third", () => {
19  assertEquals(apply(parallel, "C", "Am", "Cmaj7", "Dm7", "Bdim", "Csus4"), [
20    "Cm",
21    "A",
22    "CmMaj7",
23    "D7",
24    undefined,
25    undefined,
26  ]);
27});
28
29Deno.test("relative moves a minor third, keeping the shared notes", () => {
30  assertEquals(apply(relative, "C", "Cmaj7", "Am", "Am7", "Eb", "G7", "Bdim"), [
31    "Am",
32    "Am7",
33    "C",
34    "Cmaj7",
35    "Cm",
36    undefined,
37    undefined,
38  ]);
39});
40
41Deno.test("tritone substitution only applies to dominants", () => {
42  assertEquals(apply(tritoneSub, "G7", "D9", "C", "Cmaj7"), [
43    "D♭7",
44    "A♭9",
45    undefined,
46    undefined,
47  ]);
48});
49
50Deno.test("sevenths come from the key", () => {
51  const seventh = (s: string, k = C) => chordName(toggleSeventh(parseChord(s, k)!, k)!);
52  assertEquals(
53    ["C", "Dm", "G", "Bdim", "F"].map(s => seventh(s)),
54    ["Cmaj7", "Dm7", "G7", "Bm7♭5", "Fmaj7"],
55  );
56  assertEquals(seventh("E", key("A", "harmonicMinor")), "E7");
57  assertEquals(seventh("G#dim", key("A", "harmonicMinor")), "G♯dim7");
58  // outside the key: whichever seventh the key has
59  assertEquals([seventh("Bb"), seventh("E"), seventh("Ab")], ["B♭maj7", "E7", "A♭maj7"]);
60  assertEquals(
61    ["Cmaj7", "G7", "Bdim7"].map(s => seventh(s)),
62    ["C", "G", "Bdim"],
63  );
64});
65
66Deno.test("suspending, and unsuspending back to the key's third", () => {
67  const sus = (s: string, tone: 2 | 5) => chordName(suspend(parseChord(s, C)!, C, tone)!);
68  assertEquals([sus("C", 5), sus("Am", 2), sus("G7", 5)], ["Csus4", "Asus2", "G7sus4"]);
69  assertEquals([sus("Csus4", 5), sus("Dsus4", 5), sus("Csus2", 5)], ["C", "Dm", "Csus4"]);
70});