char-slop/chordpad.vibed.software

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

Charlotte Somallow midi import as chords040fec5

main
2.0 KiB67 linesraw
1import { chordsOf } from "../midi/chords.ts";
2import type { MidiPart } from "../midi/smf.ts";
3
4export type PartKind = "chords" | "notes";
5
6/** ask whether each part of a midi file comes in as chords or as melody: null if cancelled */
7export function choosePartKinds(
8  fileName: string,
9  parts: readonly MidiPart[],
10): Promise<PartKind[] | null> {
11  return new Promise(resolve => {
12    const done = (choice: PartKind[] | null) => {
13      dialog.close();
14      dialog.remove();
15      resolve(choice);
16    };
17
18    const rows = parts.map(part => {
19      const chords = chordsOf(part.notes);
20      const timeWith = (atLeast: number) =>
21        chords.filter(c => c.notes.length >= atLeast).reduce((t, c) => t + c.dur, 0);
22      const most = Math.max(...chords.map(c => c.notes.length));
23      const select = (
24        <select aria-label={`import ${part.name} as`}>
25          <option value="chords">chords</option>
26          <option value="notes">melody</option>
27        </select>
28      ) as HTMLSelectElement;
29      select.value = timeWith(2) > timeWith(1) / 2 ? "chords" : "notes";
30      return {
31        select,
32        row: (
33          <div class="midi-part">
34            <b>{part.name}</b>
35            <span class="midi-detail">
36              {`${part.notes.length} notes · ${most === 1 ? "one" : `up to ${most}`} at once`}
37            </span>
38            {select}
39          </div>
40        ),
41      };
42    });
43
44    const confirm = (
45      <button type="button" _onclick={() => done(rows.map(r => r.select.value as PartKind))}>
46        import
47      </button>
48    ) as HTMLButtonElement;
49
50    const dialog = (
51      <dialog class="dialog midi-dialog" _oncancel={() => done(null)}>
52        <h2>{fileName}</h2>
53        <div class="midi-parts">{rows.map(r => r.row)}</div>
54        <div class="dialog-actions">
55          <button type="button" _onclick={() => done(null)}>
56            cancel
57          </button>
58          {confirm}
59        </div>
60      </dialog>
61    ) as HTMLDialogElement;
62
63    document.body.append(dialog);
64    dialog.showModal();
65    confirm.focus();
66  });
67}