\subsubsection{Reading Midi files}
\seclabel{Haskore.Interface.MIDI.Read}

Now that we have translated a raw Midi file into a \code{MidiFile.T} data type,
we can translate that \code{MidiFile.T} into a \code{MidiMusic.T} object.

\begin{haskelllisting}

> module Haskore.Interface.MIDI.Read (toRhyMusic, toGMMusic,
>  {- debugging -} retrieveTracks)
>   where
>
> import qualified Haskore.Interface.MIDI.Note         as MidiNote
> import qualified Haskore.Interface.MIDI.InstrumentMap as InstrMap
> import           Sound.MIDI.File  as MidiFile
> import           Sound.MIDI.Event as MidiEvent
> import qualified Sound.MIDI.General       as GeneralMidi
>
> import Haskore.Basic.Duration ((%+))
> import qualified Haskore.Basic.TimeOrderedList as TimeList
> import qualified Haskore.Music             as Music
> import qualified Haskore.Music.GeneralMIDI as MidiMusic
> import qualified Haskore.Music.Rhythmic    as RhyMusic
> import qualified Haskore.Performance.Context  as Context
> import qualified Haskore.Performance.BackEnd  as PfBE
> import qualified Haskore.Performance.Default  as DefltPf
> import qualified Haskore.Process.Optimization as Optimization

> import qualified Numeric.NonNegative.Class as NonNeg

> import Haskore.Music
>              (line, chord, changeTempo, Dur, DurRatio)
> import Haskore.General.Utility (mapPair, mapSnd, )
>
> import Haskore.General.Map (Map)
> import qualified Haskore.General.Map as Map
> import Data.Maybe (mapMaybe, fromMaybe)

\end{haskelllisting}

The main function.
Note that we need drum and instrument maps
in order to restore a \code{Context.T}
as well as a \code{RhyMusic.T} object.
\begin{haskelllisting}

> toRhyMusic ::
>    (NonNeg.C time, Fractional time, Real time, Fractional dyn) =>
>    InstrMap.ChannelProgramPitchTable drum ->
>    InstrMap.ChannelProgramTable instr ->
>    MidiFile.T ->
>       (Context.T time dyn (RhyMusic.Note drum instr), RhyMusic.T drum instr)
> toRhyMusic dMap iMap mf@(MidiFile.Cons _ d trks) =
>   let cpm = makeCPM trks
>       m   = Music.mapNote
>                (MidiNote.toRhyNote
>                   (InstrMap.reverseLookupMaybe dMap)
>                   (InstrMap.reverseLookupMaybe iMap))
>                (format (readFullTrack d cpm) mf)
>   in (context, m)

> toGMMusic ::
>    (NonNeg.C time, Fractional time, Real time, Fractional dyn) =>
>    MidiFile.T -> (InstrMap.ChannelTable MidiMusic.Instr,
>                   Context.T time dyn MidiMusic.Note, MidiMusic.T)
> toGMMusic mf@(MidiFile.Cons _ d trks) =
>   let cpm     = makeCPM trks
>       upm     = map (\(ch, progNum) ->
>                       (GeneralMidi.instrumentFromProgram progNum, ch))
>                     (Map.toList cpm)
>       m       = Music.mapNote MidiNote.toGMNote
>                               (format (readFullTrack d cpm) mf)
>   in (upm, context, m)

> context ::
>    (NonNeg.C time, Fractional time, Real time, Fractional dyn) =>
>    Context.T time dyn note
> context =
>    Context.setPlayer DefltPf.player $
>    Context.setDur 1 $
>    DefltPf.context

> retrieveTracks :: MidiFile.T -> [[MidiMusic.T]]
> retrieveTracks (MidiFile.Cons _ d trks) =
>   let cpm = makeCPM trks
>   in  map (map (Music.mapNote MidiNote.toGMNote
>                  . readTrack (tDiv d) cpm . fst)
>              . prepareTrack) trks

> type ChannelProgramMap = Map MidiEvent.Channel MidiEvent.Program

> readFullTrack ::
>    Division -> ChannelProgramMap -> Track -> Music.T MidiNote.T
> readFullTrack dv cpm =
>   let readTempoTrack (t,r) =
>           changeTempo r (readTrack (tDiv dv) cpm t)
>   in  Optimization.all . line . map readTempoTrack . prepareTrack

> prepareTrack :: Track -> [(RichTrack, DurRatio)]
> prepareTrack =
>    map (extractTempo defltST) . segmentBeforeSetTempo .
>    mergeNotes defltST . moveTempoToHead

\end{haskelllisting}

Make one big music out of the individual tracks of a MidiFile,
using different composition types depending on the format of the MidiFile.
\begin{haskelllisting}

> format :: (Track -> Music.T note) -> MidiFile.T -> Music.T note
> format tm (MidiFile.Cons typ _ trks) =
>    let trks' = map tm trks
>    in  case typ of
>           MidiFile.Mixed ->
>              case trks' of
>                 [trk] -> trk
>                 _ -> error ("toRhyMusic: Only one track allowed for MIDI file type 0.")
>           MidiFile.Parallel -> chord trks'
>           MidiFile.Serial   -> line  trks'

\end{haskelllisting}


Look for Program Changes in the given tracks,
in order to make a \code{ChannelProgramMap}.
\begin{haskelllisting}

> makeCPM :: [Track] -> ChannelProgramMap
> makeCPM =
>    Map.fromList . concatMap (mapMaybe getPC . TimeList.getBodies)
>
> getPC :: MidiFile.Event -> Maybe (Channel, Program)
> getPC (MIDIEvent ch (ProgramChange num)) = Just (ch, num)
> getPC _ = Nothing

\end{haskelllisting}

Translate \code{Divisions} into the number of ticks per quarter note.
\begin{haskelllisting}

> tDiv :: Division -> Tempo
> tDiv (Ticks x) = x
> tDiv (SMPTE _ _) = error "Sorry, SMPTE not yet implemented."

\end{haskelllisting}

\code{moveTempoToHead} gets the information that occurs at the beginning of
the piece: the default tempo and the default key signature.
A \code{SetTempo} in the middle of the piece
should translate to a tempo change (\code{Tempo r m}),
but a \code{SetTempo} at time 0 should set the default
tempo for the entire piece, by translating to \code{Context.T} tempo.
It remains a matter of taste which tempo of several parallel tracks
to use for the whole music.
\code{moveTempoToHead} takes care of all events that occur at time 0
so that if any \code{SetTempo} appears at time 0,
it is moved to the front of the list,
so that it can be easily retrieved from the result of
\code{segmentBeforeSetTempo}.
\begin{haskelllisting}

> moveTempoToHead :: Track -> Track
> moveTempoToHead es =
>    let (tempo, track) = getHeadTempo es
>    in  TimeList.cons 0 (MetaEvent (SetTempo tempo)) track

> getHeadTempo :: Track -> (Tempo, Track)
> getHeadTempo es =
>    maybe
>       (defltST, es)
>       (\ ~(me,rest) ->
>           case me of
>              MetaEvent (SetTempo tempo) -> (tempo, rest)
>              _ -> mapSnd (TimeList.cons 0 me) (getHeadTempo rest))
>       (do ((0,me),rest) <- TimeList.viewL es
>           return (me,rest))

\end{haskelllisting}

Manages the tempo changes in the piece.
It translates each MidiFile \code{SetTempo}
into a ratio between the new tempo and the tempo at the beginning.
\begin{haskelllisting}

> extractTempo :: Tempo -> RichTrack -> (RichTrack, DurRatio)
> extractTempo d trk =
>    fromMaybe
>       (trk, 1)
>       (do ((_, Event (MetaEvent (SetTempo tempo))), rest) <- TimeList.viewL trk
>           return (rest, toInteger d %+ toInteger tempo))

\end{haskelllisting}

\code{segmentBefore} is used to split a track into sub-tracks by tempo.
\begin{haskelllisting}

> isSetTempo :: RichEvent -> Bool
> isSetTempo (Event (MetaEvent (SetTempo _))) = True
> isSetTempo _                                = False

> segmentBeforeSetTempo :: RichTrack -> [RichTrack]
> segmentBeforeSetTempo = TimeList.segmentBefore isSetTempo

\end{haskelllisting}

\code{readTrack} is the heart of the \code{toRhyMusic} operation.
It reads a track that has been processed by \code{mergeNotes},
and returns the track as \code{StdMusic.T}.
A \code{SchedRichEvent} consists either of a normal \code{MIDIEvent}
or of a note, which in contrast to normal \code{MIDIEvent}s
contains the information of corresponding \code{NoteOn} and \code{NoteOff} events.

\begin{haskelllisting}

> type RichTrack = TimeList.T ElapsedTime RichEvent
> data RichEvent =
>      Event MidiFile.Event
>    | Note  ElapsedTime MidiNote.T

> readTrack :: Tempo -> ChannelProgramMap ->
>    RichTrack -> Music.T MidiNote.T
> readTrack ticks cpm =
>    PfBE.toMusic . trackTimeToStd ticks
>     . richTrackToBE . applyProgChanges cpm

\end{haskelllisting}

Take the division in ticks and a duration value and
converts that to a common note duration
(such as quarter note, eighth note, etc.).
\begin{haskelllisting}

> fromTicks :: Tempo -> ElapsedTime -> Dur
> fromTicks ticks d =
>    toInteger d %+ (toInteger ticks * toInteger defltDurT)

     d %+ (fromIntegral ticks * defltDurT))

> trackTimeToStd :: Tempo ->
>    PfBE.T ElapsedTime note -> PfBE.T Dur note
> trackTimeToStd ticks =
>    TimeList.mapBody (\(PfBE.Event d n) -> PfBE.Event (fromTicks ticks d) n)
>       . TimeList.mapTime (fromTicks ticks)

\end{haskelllisting}

Look up an instrument name from a \code{ChannelProgramMap} given its channel number.
\begin{haskelllisting}

> lookupChannelProg :: ChannelProgramMap -> Channel -> Program
> lookupChannelProg cpm =
>    Map.findWithDefault cpm
>       (error "Invalid channel in user patch map")

\end{haskelllisting}

Implement a \keyword{Program Change}: a change in the \code{ChannelProgramMap} in
which a channel changes from one instrument to another.
\begin{haskelllisting}

> progChange :: Channel -> Program -> ChannelProgramMap -> ChannelProgramMap
> progChange = Map.insert
> -- progChange ch num cpm = Map.insert ch num cpm

\end{haskelllisting}

Process all \code{ProgramChange} events in a track.
That is, manage a patch map and
insert in the appropriate program numbers into the \type{MidiNote.T}s.

The function works the following way:
Split the track into pieces, each beginning with a program change.
Compute the patch maps that are active after each program change.
Apply these patch maps to the track parts.
\begin{haskelllisting}

> isProgChange :: RichEvent -> Bool
> isProgChange (Event (MIDIEvent _ (ProgramChange _))) = True
> isProgChange _                                    = False

> applyProgChanges :: ChannelProgramMap -> RichTrack -> RichTrack
> applyProgChanges cpm track =
>    let parts@(_:pcParts) = TimeList.segmentBefore isProgChange track
> {-
>        updateCPM (Event (MIDIEvent ch (ProgramChange prog))) =
>           progChange ch prog
>        updateCPM _  =  error "TimeList.collectCoincident is buggy"
> -}
>        updateCPM =
>           maybe
>              (error "TimeList.collectCoincident is buggy")
>              (\ ((_, Event (MIDIEvent ch (ProgramChange prog))), _) ->
>                     progChange ch prog)
>                . TimeList.viewL
>        cpms =
>           scanl (flip id) cpm (map updateCPM pcParts)
>        setProg localCPM (Note d n) =
>           Note d (n{MidiNote.program =
>                        lookupChannelProg localCPM (MidiNote.channel n)})
>        setProg _ e = e
>    in  TimeList.concat (zipWith (TimeList.mapBody . setProg) cpms parts)

\end{haskelllisting}

Remove meta events from \type{RichTrack},
thus converting to a back-end performance.
\begin{haskelllisting}

> richNoteToBE :: RichEvent -> PfBE.Event ElapsedTime MidiNote.T
> richNoteToBE (Note d n) = PfBE.Event d n
> richNoteToBE _ = error "richNoteToBE: only Note constructor allowed"

> isRichNote :: RichEvent -> Bool
> isRichNote (Note _ _) = True
> isRichNote _          = False

> richTrackToBE :: RichTrack -> PfBE.T ElapsedTime MidiNote.T
> richTrackToBE =
>    TimeList.mapBody richNoteToBE . fst
>       . TimeList.partition isRichNote

\end{haskelllisting}

The \code{mergeNotes} function changes the order of the events in a track
so that they can be handled by readTrack: each \code{NoteOff}
is put directly after its corresponding \code{NoteOn}. Its first and second
arguments are the elapsed time and value (in microseconds per quarter
note) of the \code{SetTempo} currently in effect.
\begin{haskelllisting}

> mergeNotes :: Tempo -> Track -> RichTrack
> mergeNotes stv =
>    TimeList.mapTimeTail
>       ((\(e, rest) ->
>            uncurry TimeList.consBody $
>            case e of
>               MetaEvent (SetTempo newStv) ->
>                  (Event e, mergeNotes newStv rest)
>               MIDIEvent _ (NoteOn _ _) ->
>                  mapPair
>                     (uncurry Note, mergeNotes stv)
>                     (searchNoteOff 0 stv 1 e rest)
>               MIDIEvent _ (NoteOff _ _) ->
>                  error "NoteOff before NoteOn"
>               _ ->
>                  (Event e, mergeNotes stv rest))
>         . TimeList.viewBodyL)

\end{haskelllisting}

The function \code{searchNoteOff} takes a track and
looks through the list of events to find the \code{NoteOff}
corresponding to the given \code{NoteOn}.
A \code{NoteOff} corresponds to an earlier \code{NoteOn}
if it is the first in the track to have the same channel and pitch.
If between \code{NoteOn} and \code{NoteOff} are \code{SetTempo} events,
it calculates what the elapsed-time is,
expressed in the current tempo.
This function takes a ridiculous number of arguments,
I know, but I don't think it can do without any of the information.
Maybe there is a simpler way.
\begin{haskelllisting}

> searchNoteOff ::
>       Double          {- ^ time interval between NoteOn and now,
>                            in terms of the tempo at the NoteOn -}
>    -> Tempo -> Double {- ^ SetTempo values: the one at the NoteOn and
>                            the ratio between the current tempo and the first one. -}
>    -> MidiFile.Event  -- ^ channel and pitch of NoteOn (NoteOff must match)
>    -> Track           -- ^ the track to be searched
>    -> ((ElapsedTime, MidiNote.T), Track)
>                       -- ^ the needed event and the remainder of the track
>
> searchNoteOff int ost str mev0 =
>    maybe
>       (error "ReadMidi.searchNoteOff: no corresponding NoteOff")
>       (\((t1, mev1), es) ->
>           maybe
>              -- if MIDI events don't match, then recurse
>              (mapSnd (TimeList.cons t1 mev1) $
>               searchNoteOff (addInterval str t1 int) ost
>                  (case mev1 of
>                     -- respect tempo changes
>                     MetaEvent (SetTempo nst) ->
>                          fromIntegral ost / fromIntegral nst
>                     _ -> str)
>                  mev0 es)
>              -- if MIDI events match, construct a MidiNote.T
>              (\note ->
>                 let d = round (addInterval str t1 int)
>                 in  ((d, note), TimeList.delay t1 es))
>              -- check whether NoteOn and NoteOff matches
>              (MidiNote.fromMIDIEvents (mev0, mev1)))
>     . TimeList.viewL

> addInterval :: Double -> ElapsedTime -> Double -> Double
> addInterval str t int = int + fromIntegral t * str

\end{haskelllisting}
