Download presentation
Presentation is loading. Please wait.
2
Cse536 Functional Programming 1 6/26/2015 Lecture #19, Dec. 6, 2004 Todays Topics –Interpreting Music –Performance –MidiFile Read – Chapters 21 - Interpreting Functional Music – Chapter 22 – From Performance to Midi
3
Cse536 Functional Programming 2 6/26/2015 Haskore Haskore is a Haskell library for constructing digital music The end result is a MIDI-file Today’s lecture: –translating the Music datatype into a MIDI file Haskore Haskell Haskore Abstract High Level Implementation independent MIDI low level bit based implementation standard presentation The point of today’s lecture
4
Cse536 Functional Programming 3 6/26/2015 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] 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))))))))) Musical notation Algorithm Music algebraic datatype
5
Cse536 Functional Programming 4 6/26/2015 Need a Shape transformation Algebraic datatype is “tree-like” MIDI-file is linear in shape Need a “flattening” transformation :+: C D E
6
Cse536 Functional Programming 5 6/26/2015 3 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 –It’s 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 –Lot’s of messy details. But luckily this is handled by the Haskell MIDI library. –First place that non-pure actions are introduced.
7
Cse536 Functional Programming 6 6/26/2015 Performance 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
8
Cse536 Functional Programming 7 6/26/2015 Haskell 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 -> Time –eInst :: Event -> Iname –ePitch :: Event -> AbsPitch –eDur :: Event -> 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)
9
Cse536 Functional Programming 8 6/26/2015 perform :: Context -> Music -> 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 -> Dur -> DurT metro setting dur = 60 / (setting * ratioToFloat dur)
10
Cse536 Functional Programming 9 6/26/2015 Simple Perform perform c@(Context t i dt k) m = case m of Note p d -> let dur = ratioToFloat d * dt in [Event t i (transpose p k i) dur] Rest d -> [] m1 :+: m2 -> perform c m1 ++ perform (c {cTime = t + ratioToFloat (dur m1) * dt}) m2 m1 :=: m2 -> merge (perform c m1) (perform c m2) Tempo a m -> perform (c {cDur = dt / ratioToFloat a} ) m Trans p m -> perform (c {cKey = k + p} ) m Instr nm m -> perform (c {cInst = nm} ) m where transpose p k Percussion = absPitch p transpose p k _ = absPitch p + k Quadratic running time
11
Cse536 Functional Programming 10 6/26/2015 Consider a Music Tree like this: A tree, skewed to the left, will be very expensive to translate: m1 :+: m2 -> 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 -> Music -> (Performance,DurT) :+: D E E D DB
12
Cse536 Functional Programming 11 6/26/2015 Efficient perform perform :: Context -> Music -> Performance perform c m = fst (perf c m) perf :: Context -> Music -> (Performance, DurT) perf c@(Context t i dt k) m = case m of Note p d -> let dur = ratioToFloat d * dt in ([Event t i (transpose p k i) dur], dur) Rest d -> ([], ratioToFloat d * dt) m1 :+: m2 -> let (pf1,d1) = perf c m1 (pf2,d2) = perf (c {cTime = t+d1} ) m2 in (pf1++pf2, d1+d2) m1 :=: m2 -> let (pf1,d1) = perf c m1 (pf2,d2) = perf c m2 in (merge pf1 pf2, max d1 d2) Tempo a m -> perf (c {cDur = dt / ratioToFloat a} ) m Trans p m -> perf (c {cKey = k + p} ) m Instr nm m -> perf (c {cInst = nm} ) m where transpose p k Percussion = absPitch p transpose p k _ = absPitch p + k Note how the context changes in recursive calls
13
Cse536 Functional Programming 12 6/26/2015 merge Consider the case for parallel composition (chords etc.) m1 :=: m2 -> 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 -> Performance -> Performance merge a@(e1:es1) b@(e2:es2) = if eTime e1 < eTime e2 then e1 : merge es1 b else e2 : merge a es2 merge [] es2 = es2 merge es1 [] = es1
14
Cse536 Functional Programming 13 6/26/2015 Notes 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.
15
Cse536 Functional Programming 14 6/26/2015 The 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
16
Cse536 Functional Programming 15 6/26/2015 Lots of details we’re ignoring MidiFile MFType Division [Track] MFType - Int in the range {1,2,3}. We’re 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 Track’s each which is a list of Mevent’ s (midi-event).
17
Cse536 Functional Programming 16 6/26/2015 MIDI 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.
18
Cse536 Functional Programming 17 6/26/2015 MIDI 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
19
Cse536 Functional Programming 18 6/26/2015 Translating performToMidi :: Performance -> 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 -> [Performance] For each of these single-instrument performances turn it into a list of Mevent ’s. –performToMEvs :: Performance -> [ Mevent] Last, make a MidiFile data type out of it using the default MFType and Division
20
Cse536 Functional Programming 19 6/26/2015 Side Trip - Partition partition even [1,2,3,4,6,2,45] --> ([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 don’t. partition :: (a -> Bool) -> [a] -> ([a],[a]) partition p xs = foldr select ([],[]) xs where select x (ts,fs) | p x = (x:ts,fs) | otherwise = (ts, x:fs)
21
Cse536 Functional Programming 20 6/26/2015 SplitByInst splitByInst :: Performance ->[(MidiChannel,ProgNum,Performance)] splitByInst p = aux 0 p where aux n [] = [] aux n pf = let i = eInst (head pf) (pf1,pf2) = partition (\e -> eInst e == i) pf n' = if n==8 then 10 else n+1 in if i==Percussion then (9, 0, pf1) : aux n pf2 else if n>15 then error "No more than 16 instruments allowed" else (n, fromEnum i, pf1) : aux n' pf2 Track Instrument
22
Cse536 Functional Programming 21 6/26/2015 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 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?
23
Cse536 Functional Programming 22 6/26/2015 First: 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 -> [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'
24
Cse536 Functional Programming 23 6/26/2015 Second: mkMEvents 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) Generate a NoteOn and a NoteOff for each note at the appropriate time.
25
Cse536 Functional Programming 24 6/26/2015 Step 3: Writing a MIDI file -- outputMidiFile :: String -> MidiFile -> IO () test :: Music -> 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.
26
Cse536 Functional Programming 25 6/26/2015 Playing 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 ()}
27
Cse536 Functional Programming 26 6/26/2015 Let’s Play Some Music!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
28
Cse536 Functional Programming 27 6/26/2015 :+: D E E D DB
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.