--
import Music import Ratio import System(system) -- Be sure and maker HUGS look in the directory where the -- Haskore module code is stored using the "path" mechanism import Haskore(outputMidiFile, midiFileToString,MidiFile(..) ,Division(..),MEvent(..),MidiEvent(..),MidiChannel(..) ,ProgNum(..),MetaEvent(..)) cScale1 = 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] --------------------- 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 ----------------------------- ratioToFloat :: Ratio Int -> Float ratioToFloat r = intToFloat (numerator r) / intToFloat (denominator r) intToFloat :: Int -> Float intToFloat = fromInteger . toInteger data Context = Context { cTime :: Time, cInst :: IName, cDur :: DurT, cKey :: Key } deriving Show type Key = AbsPitch metro :: Float -> Dur -> DurT metro setting dur = 60 / (setting * ratioToFloat dur) -------------------------------------------------- performSimple 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 -> performSimple c m1 ++ performSimple (c {cTime = t + ratioToFloat (dur m1) * dt}) m2 m1 :=: m2 -> merge (performSimple c m1) (performSimple c m2) Tempo a m -> performSimple (c {cDur = dt / ratioToFloat a} ) m Trans p m -> performSimple (c {cKey = k + p} ) m Instr nm m -> performSimple (c {cInst = nm} ) m where transpose p k Percussion = absPitch p transpose p k _ = absPitch p + k 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 -------------------------------- 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 ------------------------------------------------------- {- Defined in module Haskore 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 -} ------------------------------- {- Defined in Module Haskore 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 -} -------------------------------------- performToMidi :: Performance -> MidiFile performToMidi pf = MidiFile mfType (Ticks division) (map performToMEvs (splitByInst pf)) mfType = 1 :: Int division = 96 :: Int 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) splitByInst :: Performance ->[(MidiChannel,ProgNum,Performance)] splitByInst p = aux 1 {- used to be 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 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 :: Int tempo = 500000 -------------------------------------- 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 :: 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) ----------------------------------------------------- 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 } {- defCon :: Context defCon = Context { cTime = 0 --, cPlayer = fancyPlayer , cInst = "piano" , cDur = metro 120 qn , cKey = 0 -- , cVol = 127 } -} 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 ()} m1 = cScale1 :=: (revM (Tempo 2 (delay wn cScale1))) main = testNT m1 --