char-slop/chordpad.vibed.software

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

Charlotte Somtrack reordering + chord bass togglingee3b50a

main
4.4 KiB136 linesraw
1import { assertEquals } from "@std/assert";
2import {
3  canonBeat,
4  type ChordEvent,
5  copyChords,
6  insertLeadIn,
7  moveTrack,
8  newChordTrack,
9  newProject,
10  pasteChords,
11  placeChord,
12} from "./model.ts";
13
14const layout = (chords: ChordEvent[]) => chords.map(c => [c.symbol, c.start, c.start + c.dur]);
15
16Deno.test(
17  "a placed chord cuts away what it overlaps, splitting a chord it lands inside",
18  () => {
19    const track = newChordTrack();
20    placeChord(track, { id: "a", start: 0, dur: 4, symbol: "C" });
21    placeChord(track, { id: "b", start: 1, dur: 0.25, symbol: "F" });
22    assertEquals(layout(track.chords), [
23      ["C", 0, 1],
24      ["F", 1, 1.25],
25      ["C", 1.25, 4],
26    ]);
27    placeChord(track, { id: "c", start: 0.75, dur: 1, symbol: "G" });
28    assertEquals(layout(track.chords), [
29      ["C", 0, 0.75],
30      ["G", 0.75, 1.75],
31      ["C", 1.75, 4],
32    ]);
33  },
34);
35
36Deno.test("chords on a triplet grid meet exactly, without slivers left over", () => {
37  const third = 1 / 3;
38  const track = newChordTrack();
39  // a chord placed by clicking: snapped straight onto the grid, at exactly 2
40  placeChord(track, { id: "g", start: 6 * third, dur: third, symbol: "G" });
41  // then six typed from the start, the cursor adding up thirds as it goes (to 1.9999999999999998)
42  let at = 0;
43  for (const symbol of ["C", "F", "G", "C", "Am", "Dm", "E"]) {
44    placeChord(track, { id: symbol + at, start: at, dur: third, symbol });
45    at += third;
46  }
47
48  assertEquals(
49    track.chords.map(c => c.symbol),
50    ["C", "F", "G", "C", "Am", "Dm", "E"],
51  );
52  for (const [a, b] of track.chords.slice(1).map((c, i) => [track.chords[i], c])) {
53    assertEquals(canonBeat(a.start + a.dur), b.start);
54  }
55});
56
57Deno.test("a lead-in takes the back half of the chord before, so the target stays put", () => {
58  const track = newChordTrack();
59  placeChord(track, { id: "a", start: 0, dur: 4, symbol: "C" });
60  placeChord(track, { id: "b", start: 4, dur: 4, symbol: "Am" });
61  assertEquals(insertLeadIn(track, "b", ["E7"], 0.5), true);
62  assertEquals(layout(track.chords), [
63    ["C", 0, 2],
64    ["E7", 2, 4],
65    ["Am", 4, 8],
66  ]);
67  // a ii–V shares the space
68  assertEquals(insertLeadIn(track, "a", ["Dm7", "G7"], 0.5), true);
69  assertEquals(layout(track.chords).slice(0, 3), [
70    ["Dm7", 0, 0.5],
71    ["G7", 0.5, 1],
72    ["C", 1, 2],
73  ]);
74});
75
76Deno.test("with nothing right before it, a lead-in takes the target's front half", () => {
77  const track = newChordTrack();
78  placeChord(track, { id: "a", start: 4, dur: 4, symbol: "Am" });
79  assertEquals(insertLeadIn(track, "a", ["E7"], 1), true);
80  assertEquals(layout(track.chords), [
81    ["E7", 4, 6],
82    ["Am", 6, 8],
83  ]);
84  // no room left on a whole-bar grid
85  assertEquals(insertLeadIn(track, "a", ["E7"], 4), false);
86  assertEquals(layout(track.chords), [
87    ["E7", 4, 6],
88    ["Am", 6, 8],
89  ]);
90});
91
92Deno.test("copied chords paste with their spacing, over what's there", () => {
93  const track = newChordTrack();
94  for (const [start, dur, symbol] of [
95    [0, 4, "C"],
96    [4, 2, "F"],
97    [6, 2, "G"],
98    [8, 4, "Am"],
99  ] as const) {
100    placeChord(track, { id: symbol, start, dur, symbol });
101  }
102  // F and Am: not next to each other
103  const clip = copyChords(track, ["F", "Am"])!;
104  assertEquals(clip.span, 8);
105  const ids = pasteChords(track, clip, 1);
106  // the pasted Am (beats 5 to 9) writes over the end of F, all of G, and the start of Am
107  assertEquals(layout(track.chords), [
108    ["C", 0, 1],
109    ["F", 1, 3],
110    ["C", 3, 4],
111    ["F", 4, 5],
112    ["Am", 5, 9],
113    ["Am", 9, 12],
114  ]);
115  assertEquals(ids.length, 2);
116  // a hand voicing comes along, and isn't shared with the original
117  const other = newChordTrack();
118  placeChord(other, { id: "G", start: 0, dur: 2, symbol: "G", notes: [43, 59, 62, 67] });
119  const [g] = pasteChords(track, copyChords(other, ["G"])!, 12);
120  const pasted = track.chords.find(c => c.id === g)!;
121  assertEquals(pasted.notes, [43, 59, 62, 67]);
122  pasted.notes!.push(72);
123  assertEquals(other.chords[0].notes, [43, 59, 62, 67]);
124});
125
126Deno.test("tracks move to before the track they're dropped on, or to the end", () => {
127  const p = newProject();
128  p.tracks = ["a", "b", "c", "d"].map(name => ({ ...newChordTrack(name), id: name }));
129  const order = () => p.tracks.map(t => t.id).join("");
130  moveTrack(p, "a", 2);
131  assertEquals(order(), "bacd");
132  moveTrack(p, "d", 0);
133  assertEquals(order(), "dbac");
134  moveTrack(p, "b", 4);
135  assertEquals(order(), "dacb");
136});