import { sounding, voiceTrack } from "../project/harmony.ts"; import { type Project, projectKey } from "../project/model.ts"; import { MODES } from "../theory/key.ts"; import { spellDegree } from "../theory/pitch.ts"; import { type OutTrack, writeMidi } from "./write.ts"; const ELECTRIC_PIANO = 4; const DRUM_CHANNEL = 9; /** a project as a midi file: chords as they're voiced, every track on its own channel */ export function projectMidi(p: Project): Uint8Array { const key = projectKey(p); const channels = [...Array(16).keys()].filter(c => c !== DRUM_CHANNEL); const tracks = p.tracks.map((track, i): OutTrack => ({ name: track.name, channel: channels[i % channels.length], program: ELECTRIC_PIANO, notes: track.kind === "notes" ? track.notes : voiceTrack(track, key).flatMap(({ event, notes }) => sounding(track, notes).map(pitch => ({ start: event.start, dur: event.dur, pitch, vel: 0.55, })), ), })); // a key signature only knows major and minor: modes get their parent scale's signature const minor = ["aeolian", "harmonicMinor", "melodicMinor"].includes(key.mode); const steps = minor ? MODES.aeolian.steps : MODES[key.mode].steps; const accidentals = steps.reduce((n, s, d) => n + spellDegree(key.tonic, d, s).acc, 0); return writeMidi({ bpm: p.bpm, beatsPerBar: p.beatsPerBar, key: { accidentals, minor }, tracks, }); }