> {-# LINE 8 "ToMidi.lhs" #-}
> module Euterpea.IO.MIDI.ToMidi where
> import Euterpea.Music
> import Euterpea.IO.MIDI.MEvent
> import Euterpea.IO.MIDI.GeneralMidi
> import Euterpea.IO.MIDI.MidiIO
> import Euterpea.IO.MIDI.ExportMidiFile
> import Sound.PortMidi
> import Data.List(partition)
> import Data.Char(toLower,toUpper)
> import Codec.Midi
> type ProgNum     = Int
> type UserPatchMap = [(InstrumentName, Channel)]
> makeGMMap :: [InstrumentName] -> UserPatchMap
> makeGMMap ins = mkGMMap 0 ins
>   where mkGMMap _ []        = []
>         mkGMMap n _ | n>=15 = 
>                   error "makeGMMap: too many instruments."
>         mkGMMap n (Percussion : ins)    = 
>                   (Percussion, 9) : mkGMMap n ins
>         mkGMMap n (i : ins) = 
>                   (i, chanList !! n) : mkGMMap (n+1) ins
>         chanList = [0..8] ++ [10..15]  --  channel 9 is for percussion
> upmLookup :: UserPatchMap  -> InstrumentName 
>                            -> (Channel, ProgNum)
> upmLookup upm iName = (chan, toGM iName)
>   where chan = maybe  (error (  "instrument " ++ show iName ++ 
>                                 " not in patch map")  )
>                       id (lookup iName upm)
> toMidi :: [MEvent] -> Midi
> toMidi = toMidiUPM defUpm
> toMidiUPM :: UserPatchMap -> [MEvent] -> Midi
> toMidiUPM upm pf =
>    let split     = splitByInst pf
>        insts     = map fst split
>        rightMap  =  if (allValid upm insts) then upm
>                     else (makeGMMap insts)
>    in Midi  (if length split == 1  then SingleTrack 
>                                    else MultiTrack)
>             (TicksPerBeat division)
>             (map (fromAbsTime . mevsToMessages rightMap) split)
> division = 96 :: Int
> allValid :: UserPatchMap -> [InstrumentName] -> Bool
> allValid upm = and . map (lookupB upm)
> lookupB :: UserPatchMap -> InstrumentName -> Bool
> lookupB upm x = or (map ((== x) . fst) upm)
> splitByInst :: [MEvent] ->  [(InstrumentName, [MEvent])]
> splitByInst [] = []
> splitByInst pf = (i, pf1) : splitByInst pf2
>        where i          = eInst (head pf)
>              (pf1, pf2) = partition (\e -> eInst e == i) pf
> type MidiEvent = (Ticks, Message)
> defST = 500000
> mevsToMessages ::  UserPatchMap
>                   -> (InstrumentName, [MEvent]) 
>                   -> [MidiEvent]
> mevsToMessages upm (inm, pf) =
>   let  (chan,progNum)   = upmLookup upm inm
>        setupInst        = (0, ProgramChange chan progNum)
>        setTempo         = (0, TempoChange defST)
>        loop []      =  []
>        loop (e:es)  =  let (mev1,mev2) = mkMEvents chan e
>                        in mev1 : insertMEvent mev2 (loop es)
>   in setupInst : setTempo : loop pf
> mkMEvents :: Channel -> MEvent -> (MidiEvent,MidiEvent)
> mkMEvents  mChan (MEvent {  eTime = t, ePitch = p, 
>                            eDur = d, eVol = v})
>                   = (  (toDelta t, NoteOn  mChan p v'),
>                        (toDelta (t+d), NoteOff mChan p v') )
>            where v' = max 0 (min 127 (fromIntegral v))
> toDelta t = round (t * 2.0 * fromIntegral division)
> insertMEvent :: MidiEvent -> [MidiEvent] -> [MidiEvent]
> insertMEvent mev1  []         = [mev1]
> insertMEvent mev1@(t1,_) mevs@(mev2@(t2,_):mevs') = 
>       if t1 <= t2 then mev1 : mevs
>                   else mev2 : insertMEvent mev1 mevs'
> defUpm :: UserPatchMap
> defUpm = [(AcousticGrandPiano,0),
>           (Marimba,1),
>           (Vibraphone,2),
>           (AcousticBass,3),
>           (Flute,4),
>           (TenorSax,5),
>           (AcousticGuitarSteel,6),
>           (Viola,7),
>           (StringEnsemble1,8),
>           (AcousticGrandPiano,9)]
>            --  the GM name for drums is unimportant, only channel 9
> writeMidi :: ToMusic1 a => FilePath -> Music a -> IO ()
> writeMidi fn m = exportMidiFile fn $ toMidi $ perform m
play :: ToMusic1 a => Music a -> IO () play = playM . toMidi . perform playM :: Midi -> IO () playM midi = do initialize (defaultOutput playMidi) midi terminate return ()