Title: Lecture
1Lecture 19, Dec. 6, 2004
- Todays Topics
- Interpreting Music
- Performance
- MidiFile
- Read
- Chapters 21 - Interpreting Functional Music
- Chapter 22 From Performance to Midi
2Haskore
- Haskore is a Haskell library for constructing
digital music - The end result is a MIDI-file
- Todays lecture
- translating the Music datatype into a MIDI file
Haskell
Haskore
Haskore Abstract High Level Implementation indepe
ndent
MIDI low level bit based implementation standard
presentation
The point of todays lecture
3Musical notation
cScale line c 4 qn, d 4 qn, e 4 qn,
f 4 qn, g 4 qn, a 4 qn, b 4 qn, c 5 qn
Algorithm
Note (C,4) (1 4) (Note (D,4) (1 4)
(Note (E,4) (1 4) (Note
(F,4) (1 4) (Note (G,4) (1 4)
(Note (A,4) (1 4)
(Note (B,4) (1 4)
(Note (C,5) (1 4)
(Rest (0 1)))))))))
Music algebraic datatype
4Need a Shape transformation
- Algebraic datatype is tree-like
- MIDI-file is linear in shape
- Need a flattening transformation
53 step process
- Translate Music data type to Performance data
type - this step begins the flattening process
- It uses simpler notion of time than the
midi-standard - Its purely functional (no actions)
- Translate Performance data type to MidiFile data
type - Introduces the MIDI notion of events.
- Each note, rest etc. translates to two
midi-events, - a start event
- a stop event
- Write MidiFile data type to a real MIDI format
file - Lots of messy details. But luckily this is
handled by the Haskell MIDI library. - First place that non-pure actions are introduced.
6Performance data type
- type Performance Event
- data Event
- Event eTime Time, -- start time
- eInst IName, -- instrument
- ePitch AbsPitch, -- pitch or note
- eDur DurT -- duration
- deriving (Eq,Ord,Show)
- type Time Float
- type DurT Float
7Haskell record syntax
- data Event Event eTime Time,
- eInst IName,
- ePitch AbsPitch,
- eDur DurT
- Normal constructor notation
- (Event start-time instrument pitch duration)
- Also introduces selector functions
- eTime Event -gt Time
- eInst Event -gt Iname
- ePitch Event -gt AbsPitch
- eDur Event -gt DurT
- And an update notation
- x eTime y Event y (eInst x) (ePitch x)
(eDur x) - where x has shape (Event a b c d)
8perform Context -gt Music -gt Performance
- The function perform translates a Music value
into a Performance in some Context - A Context contains
- time to begin the performance
- the proper musical key to play the performance
- the tempo (or speed) to play the performance
- the instrument to use (unless one is explicitly
given) - data Context Context cTime Time, cInst
IName, - cDur DurT, cKey
Key - deriving Show
- type Key AbsPitch
- metro computes the time for one whole note, given
a beats per minute setting and a duration for one
beat (quarter note, half note etc). - metro Float -gt Dur -gt DurT
- metro setting dur 60 / (setting ratioToFloat
dur)
9Simple Perform
- perform c_at_(Context t i dt k) m
- case m of
- Note p d -gt let dur ratioToFloat d dt
- in Event t i (transpose p k i)
dur - Rest d -gt
- m1 m2 -gt
- perform c m1
- perform (c cTime t ratioToFloat (dur
m1) dt) m2 - m1 m2 -gt merge (perform c m1) (perform c
m2) - Tempo a m -gt
- perform (c cDur dt / ratioToFloat a ) m
- Trans p m -gt perform (c cKey k p ) m
- Instr nm m -gt perform (c cInst nm ) m
- where transpose p k Percussion absPitch p
- transpose p k _ absPitch p k
Quadratic running time
10Consider a Music Tree like this
- A tree, skewed to the left, will be very
expensive to translate - m1 m2 -gt
- perform c m1
- perform
- (c cTime ...(dur m1)...) m2
- Solution compute the translation and the
duration of the Music-tree simultaneously. - Have perform return a pair
- perform Context -gt Music -gt (Performance,DurT)
11Efficient perform
- perform Context -gt Music -gt Performance
- perform c m fst (perf c m)
- perf Context -gt Music -gt (Performance, DurT)
- perf c_at_(Context t i dt k) m
- case m of
- Note p d -gt let dur ratioToFloat d dt
- in (Event t i (transpose p k
i) dur, dur) - Rest d -gt (, ratioToFloat d dt)
- m1 m2 -gt let (pf1,d1) perf c m1
- (pf2,d2) perf (c cTime
td1 ) m2 - in (pf1pf2, d1d2)
- m1 m2 -gt let (pf1,d1) perf c m1
- (pf2,d2) perf c m2
- in (merge pf1 pf2, max d1 d2)
- Tempo a m -gt perf (c cDur dt /
ratioToFloat a ) m - Trans p m -gt perf (c cKey k p ) m
- Instr nm m -gt perf (c cInst nm ) m
- where transpose p k Percussion absPitch p
Note how the context changes in recursive calls
12 merge
- Consider the case for parallel composition
(chords etc.) - m1 m2 -gt let (pf1,d1) perf c m1
- (pf2,d2) perf c m2
- in (merge pf1 pf2, max d1 d2)
- merge - synchronizes two time stamped ordered
lists - merge Performance -gt Performance -gt
Performance - merge a_at_(e1es1) b_at_(e2es2)
- if eTime e1 lt eTime e2 then e1 merge es1 b
- else e2 merge a es2
- merge es2 es2
- merge es1 es1
13Notes on step 1
- Perform has flattened the Music structure into a
list of events. - Events are time stamped, and the final list is in
time-stamp order. - Each event carries information about instrument,
pitch, and duration. - Perform has not dealt with the issue of each note
etc. must be translated into two midi-events,
one with a start, and the other with a stop.
14The Haskell MIDI Library
- 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
-
15Lots of details were ignoring
- MidiFile MFType Division Track
- MFType - Int in the range 1,2,3. Were
interested in MFType 2. This means the midi
file contains information about multiple tracks
(up to 15), each playing a different instrument.
All tracks are played simultaneously. - Division - Int representing the time strategy of
the midi file. We will always use Division 96.
This means 96 ticks per quarter note. - Track - Mevent . This represents the music
that is played. Note there is a list of Tracks
each which is a list of Mevents (midi-event).
16MIDI Events
- MIDI events come in two flavors.
- Normal event. NoteOn, NoteOff, or ProgChange
(switch instrument) - Meta event. Change how things are played.
- Of interest to us SetTempo - change the speed
of music played.
17MIDI Library - MIDI Events
- -- Midi Events
- 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
- -- Meta Events
- data MetaEvent SetTempo MTempo
- ...
- deriving (Show, Eq)
- type MTempo Int
18Translating
- performToMidi Performance -gt MidiFile
- performToMidi pf
- MidiFile mfType (Ticks division)
- (map performToMEvs (splitByInst
pf)) - mfType 1
- division 96
- First, take the performance (a list of events,
each of which carries information about
instrument, pitch, and duration), and split it
into a list of performances, each of which deals
with only one instrument. - splitByInst Performance -gt Performance
- For each of these single-instrument performances
turn it into a list of Mevents. - performToMEvs Performance -gt Mevent
- Last, make a MidiFile data type out of it using
the default MFType and Division
19Side Trip - Partition
- partition even 1,2,3,4,6,2,45
- --gt
- (2,4,6,2,1,3,45)
- Partition takes a predicate and a list, and
returns a pair of lists. The first element of the
pair is all the elements of the list that meet
the predicate. The second element all those that
dont. - partition (a -gt Bool) -gt a -gt (a,a)
- partition p xs
- foldr select (,) xs
- where select x (ts,fs)
- p x (xts,fs)
- otherwise (ts, xfs)
20SplitByInst
Instrument
Track
- splitByInst Performance -gt(MidiChannel,ProgNum
,Performance) - splitByInst p
- aux 0 p where
- aux n
- aux n pf
- let i eInst (head pf)
- (pf1,pf2) partition (\e -gt eInst e
i) pf - n' if n8 then 10 else n1
- in if iPercussion
- then (9, 0, pf1) aux n pf2
- else
- if ngt15
- then error
- "No more than 16
instruments allowed" - else (n, fromEnum i, pf1)
aux n' pf2
21PerformToMEvs
- performToMEvs (MidiChannel,ProgNum,Performance)
-gt MEvent - performToMEvs (ch,pn,perf)
- let setupInst MidiEvent 0 (ProgChange ch
pn) - setTempo MetaEvent 0 (SetTempo
tempo) - loop
- loop (ees)
- let (mev1,mev2) mkMEvents ch e
- in mev1 insertMEvent mev2 (loop es)
- in setupInst setTempo loop perf
- For each event, set up the instrument and the
tempo, and generate a start and stop event. - The start event goes at the beginning of the
list. But where does the stop event go?
22First insertMEvent
- Since the stop event can possibly go any where in
the list generated we need an function that
inserts a time-stamped event in the correct
location in a time-stamped ordered list. - insertMEvent MEvent -gt MEvent -gt MEvent
- insertMEvent ev1 ev1
- insertMEvent ev1_at_(MidiEvent t1 _)
evs_at_(ev2_at_(MidiEvent t2 _)evs') - if t1 lt t2 then ev1 evs
- else ev2 insertMEvent ev1 evs'
23Second mkMEvents
- mkMEvents MidiChannel -gt Event -gt
(MEvent,MEvent) - mkMEvents mChan (Event eTime t,
- ePitch p,
- eDur d )
- (MidiEvent (toDelta t) (NoteOn mChan p
127), - MidiEvent (toDelta (td))(NoteOff mChan p
127)) - toDelta t round (t 4.0 intToFloat division)
- Generate a NoteOn and a NoteOff for each note at
the appropriate time.
24Step 3 Writing a MIDI file
- -- outputMidiFile String -gt MidiFile -gt IO ()
- test Music -gt IO ()
- test m outputMidiFile "test.mid"
- (performToMidi (perform defCon m))
- defCon Context -- Defauult Initial Context
- defCon Context cTime 0,
- cInst AcousticGrandPiano,
- cDur metro 120 qn,
- cKey 0
- Note it is not until we write the midi-file to
disk that we move from the pure functional world,
to the world of actions.
25Playing Music
- testWin95 m
- do test m
- system "mplayer test.mid
- return ()
- testNT m
- do test m
- system "mplay32 test.mid
- return ()
- testLinux m
- do test m
- system "playmidi -rf test.mid
- return ()
26Lets Play Some Music!
- cScale
- line c 4 qn, d 4 qn, e 4 qn,
- f 4 qn, g 4 qn, a 4 qn,
- b 4 qn, c 5 qn
- testNT cScale
27(No Transcript)