80 likes | 367 Views
Chapter 22. From Performance to MIDI. Motivation. Abstractly, an MDL program denotes a Performance . But a Performance is just a Haskell data structure – we can analyze it, but we cannot hear it! To remedy this, we will convert a Performance into a MIDI File.
E N D
Chapter 22 From Performance to MIDI
Motivation • Abstractly, an MDL program denotes a Performance. • But a Performance is just a Haskell data structure – we can analyze it, but we cannot hear it! • To remedy this, we will convert a Performance into a MIDI File. • MIDI (Musical Instrument Digital Interface) is a standard protocol for describing electronic music. • The MIDI file format is part of this standard, and all consumer PC’s are capable of playing them through the PC’s sound card. • In addition, General MIDI standardizes instrument and percussion names as used in MDL.
Our Goal • We need to define a function (called “test” in text): musicToMidi :: Music -> IO () musicToMidi = outputMidiFile "test.mid“ . performToMidi . perform defCon • “perform” was defined in the previous chapter, and “defCon” is just the default Context. • So additionally we need: performToMidi :: Performance -> MidiFile outputMidiFile :: String -> MidiFile -> IO () • “MidiFile” and “outputMidiFile” are imported from Haskore. Our main job is to define “performToMidi”.
MidiFile Data Type • The MidiFile data type (imported from Haskore) captures the essence of a Midi file: data MidiFile = MidiFile MFType Division [Track] deriving (Show, Eq) type MFType = Int type Track = [MEvent] data Division = Ticks Int | SMPTE Int Int deriving (Show,Eq) data MEvent = MidiEvent ElapsedTime MidiEvent | MetaEvent ElapsedTime MetaEvent | NoEvent deriving (Show,Eq) type ElapsedTime = Int [ see text for details ]
MIDI Events • MIDI events are like MDL events, except that there are events for both start and end of a note: data MidiEvent = | NoteOff MidiChannel MPitch Velocity | NoteOn MidiChannel MPitch Velocity | ProgChange MidiChannel ProgNum | ... deriving (Show, Eq)type MPitch = Int; type Velocity = Int type ProgNum = Int; type MidiChannel = Int data MetaEvent = SetTempo MTempo | ... deriving (Show, Eq)type MTempo = Int
performToMidi • To convert a Performance into a MidiFile value: performToMidi :: Performance -> MidiFile performToMidi pf = MidiFile mfType (Ticks division) (map performToMEvs (splitByInst pf)) mfType = 1 :: Int division = 96 :: Int • This leaves two functions:splitByInst :: Performance -> [(MidiChannel,ProgNum,Performance)]performToMEvs :: (MidiChannel,ProgNum,Performance) -> [MEvent] • In a Type 1 MIDI file, each instrument is associated with a separate track. splitByInst splits the Performance to achieve this (see text for details).
performToMEvs performToMEvs :: (MidiChannel,ProgNum,Performance) -> [MEvent] performToMEvs (ch,pn,perf)= let setupInst = MidiEvent 0 (ProgChange ch pn) setTempo = MetaEvent 0 (SetTempo tempo) loop [] = [] loop (e:es) = let (mev1,mev2) = mkMEvents ch e in mev1 : insertMEvent mev2 (loop es) in setupInst : setTempo : loop perf tempo = 500000 :: Int -- number of microsecs in one beat insertMEvent :: MEvent -> [MEvent] -> [MEvent]insertMEvent ev1 [] = [ev1]insertMEvent ev1@(MidiEvent t1 _) evs@(ev2@(MidiEvent t2 _) : evs') = if t1 <= t2 then ev1 : evs else ev2 : insertMEvent ev1 evs'
mkMEvents • Finally, we convert individual notes:mkMEvents :: MidiChannel -> Event -> (MEvent,MEvent)mkMEvents mChan (Event { eTime = t, ePitch = p, eDur = d }) = ( MidiEvent (toDelta t) (NoteOn mChan p 127), MidiEvent (toDelta (t+d)) (NoteOff mChan p 127) )toDelta t = round (t * 4.0 * intToFloat division)