char-slop/chordpad.vibed.software
git clone https://git.t4t.associates/char-slop/chordpad.vibed.software
ee3b50a
main
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 > { 12const key = projectKey ( p ); 13const channels = [ ...Array ( 16 ). keys ()]. filter ( c => c !== DRUM_CHANNEL ); 14 15const tracks = p . tracks . map (( track , i ) :OutTrack => ({ 16name :track . name , 17channel :channels [ i % channels . length ], 18program :ELECTRIC_PIANO , 19notes : 20track . kind === "notes" 21 ?track . notes 22 :voiceTrack ( track , key ). flatMap (({ event, notes}) => 23sounding ( track , notes ). map ( pitch => ({ 24start :event . start , 25dur :event . dur , 26 pitch, 27vel :0.55 , 28})), 29), 30})); 31 32// a key signature only knows major and minor: modes get their parent scale's signature 33const minor = [ "aeolian" , "harmonicMinor" , "melodicMinor" ]. includes ( key . mode ); 34const steps = minor ?MODES . aeolian . steps :MODES [ key . mode ]. steps ; 35const accidentals = steps . reduce (( n , s , d ) => n + spellDegree ( key . tonic , d , s ). acc , 0 ); 36 37return writeMidi ({ 38bpm :p . bpm , 39beatsPerBar :p . beatsPerBar , 40key :{ accidentals, minor}, 41 tracks, 42}); 43}