--
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
--