char-slop/chordpad.vibed.software

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

Charlotte Somtrack reordering + chord bass togglingee3b50a

main
1.5 KiB43 linesraw
1import { sounding, voiceTrack } from "../project/harmony.ts";
2import { type Project, projectKey } from "../project/model.ts";
3import { MODES } from "../theory/key.ts";
4import { spellDegree } from "../theory/pitch.ts";
5import { type OutTrack, writeMidi } from "./write.ts";
6
7const ELECTRIC_PIANO = 4;
8const DRUM_CHANNEL = 9;
9
10/** a project as a midi file: chords as they're voiced, every track on its own channel */
11export function projectMidi(p: Project): Uint8Array<ArrayBuffer> {
12  const key = projectKey(p);
13  const channels = [...Array(16).keys()].filter(c => c !== DRUM_CHANNEL);
14
15  const tracks = p.tracks.map((track, i): OutTrack => ({
16    name: track.name,
17    channel: channels[i % channels.length],
18    program: ELECTRIC_PIANO,
19    notes:
20      track.kind === "notes"
21        ? track.notes
22        : voiceTrack(track, key).flatMap(({ event, notes }) =>
23            sounding(track, notes).map(pitch => ({
24              start: event.start,
25              dur: event.dur,
26              pitch,
27              vel: 0.55,
28            })),
29          ),
30  }));
31
32  // a key signature only knows major and minor: modes get their parent scale's signature
33  const minor = ["aeolian", "harmonicMinor", "melodicMinor"].includes(key.mode);
34  const steps = minor ? MODES.aeolian.steps : MODES[key.mode].steps;
35  const accidentals = steps.reduce((n, s, d) => n + spellDegree(key.tonic, d, s).acc, 0);
36
37  return writeMidi({
38    bpm: p.bpm,
39    beatsPerBar: p.beatsPerBar,
40    key: { accidentals, minor },
41    tracks,
42  });
43}