char-slop/chordpad.vibed.software

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

Charlotte Somoops8aa6d5f

main
3.2 KiB81 linesraw
1import type { JSX } from "@char/aftercare/jsx-runtime";
2import { projectKey } from "../project/model.ts";
3import { project } from "../project/store.ts";
4import { palette, prettyRoman, type Relation } from "../theory/analysis.ts";
5import { formatNote } from "../theory/pitch.ts";
6import { chordName } from "../theory/chord.ts";
7import { MODES } from "../theory/key.ts";
8import { previewChord, writeChordFromPalette } from "./actions.ts";
9import { Circle } from "./circle.tsx";
10import { disclosure, help, watch } from "./dom.tsx";
11import { mixtureOpen, paletteSevenths } from "./state.ts";
12
13const LABELS: Partial<Record<Relation, string>> = {
14  home: "key",
15  sharp: "sharp vertex",
16  flat: "flat vertex",
17  inverse: "inverse vertex",
18};
19
20/** the diatonic chords of the key, of the other keys on its axis, and of its parallel modes */
21export function Palette(): JSX.Element {
22  const keyId = project.derive(p => `${p.key.tonic} ${p.key.mode}`);
23  const rows = <div class="palette-rows" />;
24
25  watch(rows, [keyId, paletteSevenths], () => {
26    const key = projectKey(project.get());
27    rows.replaceChildren(
28      ...palette(key, paletteSevenths.get()).map(
29        ({ key: rowKey, relation, entries, vertex }) => (
30          <div
31            class={`palette-row ${relation === "parallel" ? `from-${rowKey.mode}` : `on-${relation}`}`}
32          >
33            <div class="palette-mode">
34              {LABELS[vertex ?? relation] ?? MODES[rowKey.mode].label}
35              {relation !== "parallel" || vertex ? (
36                <small>
37                  {formatNote(rowKey.tonic)} {MODES[rowKey.mode].label}
38                </small>
39              ) : undefined}
40            </div>
41            {entries.map(({ chord, symbol, diatonic }) => (
42              <button
43                type="button"
44                class={`chip${relation !== "home" && diatonic ? " same" : ""}`}
45                // the second click of a double-click would only preview on top of the write
46                _onclick={e => e.detail < 2 && previewChord(symbol)}
47                _ondblclick={() => writeChordFromPalette(symbol)}
48              >
49                <b>{prettyRoman(symbol)}</b>
50                <span>{chordName(chord)}</span>
51              </button>
52            ))}
53          </div>
54        ),
55      ),
56    );
57  });
58
59  return (
60    <section class="palette" dataset={{ open: mixtureOpen.derive(String) }}>
61      <header>
62        {disclosure(mixtureOpen, "modal mixture")}
63        {help(
64          "chords to borrow: from the key, from the three keys a minor third and a tritone " +
65            "away, and from the other modes on the same tonic. faded ones are already in the " +
66            "key.\n\nthe circle of fifths turns to put the current chord at the top, with " +
67            "what's a fifth either way of it beside it, and the relative minors inside; " +
68            "it writes after the current chord, if there's room.\n\n" +
69            "click a chord to hear it; double-click to write it at the cursor",
70        )}
71        <label>
72          <input type="checkbox" checked={paletteSevenths} /> sevenths
73        </label>
74      </header>
75      <div class="palette-body">
76        {rows}
77        <Circle />
78      </div>
79    </section>
80  );
81}