\subsubsection{Instrument map} \seclabel{user-patch-map} \begin{haskelllisting}
> module Haskore.Interface.MIDI.InstrumentMap where
> import Haskore.Music.Standard(Instr)
> import qualified Sound.MIDI.Message.Channel as ChannelMsg
> import qualified Sound.MIDI.General         as GeneralMidi
> import qualified Haskore.General.Map as Map
> import qualified Data.List as List
> import Data.Tuple.HT (swap, )
> import Data.Char (toLower, )
> import Data.Maybe (fromMaybe, )
\end{haskelllisting} A \type{InstrumentMap.ChannelProgramTable} is a user-supplied table for mapping instrument names (\type{Instr}s) to Midi channels and General Midi patch names. The patch names are by default General Midi names, although the user can also provide a \type{PatchMap} for mapping Patch Names to unconventional Midi Program Change numbers. \begin{haskelllisting}
> type ChannelTable instr =
>    [(instr,  ChannelMsg.Channel)]
> type ChannelProgramTable instr =
>    [(instr, (ChannelMsg.Channel, ChannelMsg.Program))]
> type ChannelProgramPitchTable instr =
>    [(instr, (ChannelMsg.Channel, ChannelMsg.Program, ChannelMsg.Pitch))]
>
> type ToChannel instr =
>    instr ->  ChannelMsg.Channel
> type ToChannelProgram instr =
>    instr -> (ChannelMsg.Channel, ChannelMsg.Program)
> type ToChannelProgramPitch instr =
>    instr -> (ChannelMsg.Channel, ChannelMsg.Program, ChannelMsg.Pitch)
>
> type FromChannel instr =
>     ChannelMsg.Channel -> Maybe instr
> type FromChannelProgram instr =
>    (ChannelMsg.Channel, ChannelMsg.Program) -> Maybe instr
> type FromChannelProgramPitch instr =
>    (ChannelMsg.Channel, ChannelMsg.Program, ChannelMsg.Pitch) -> Maybe instr
\end{haskelllisting} The \function{allValid} is used to test whether or not every instrument in a list is found in a \type{InstrumentMap.ChannelProgramTable}. \begin{haskelllisting}
> repair :: [Instr] -> ChannelProgramTable Instr -> ChannelProgramTable Instr
> repair insts pMap =
>    if allValid pMap insts
>        then pMap
>        else tableFromInstruments insts
>
> allValid :: ChannelProgramTable Instr -> [Instr] -> Bool
> allValid upm = all (\x -> any (partialMatch x . fst) upm)
\end{haskelllisting} If a Haskore user only uses General Midi instrument names as \type{Instr}s, we can define a function that automatically creates a \type{InstrumentMap.ChannelProgramTable} from these names. Note that, since there are only 15 Midi channels plus percussion, we can handle only 15 instruments. Perhaps in the future a function could be written to test whether or not two tracks can be combined with a Program Change (tracks can be combined if they don't overlap). \begin{haskelllisting}
> tableFromInstruments :: [Instr] -> ChannelProgramTable Instr
> tableFromInstruments instrs =
>    zip instrs (assignChannels GeneralMidi.instrumentChannels instrs)
>         -- 10th channel (#9) is for percussion
> assignChannels :: [ChannelMsg.Channel] -> [Instr] ->
>       [(ChannelMsg.Channel, ChannelMsg.Program)]
> assignChannels _ [] = []
> assignChannels [] _ =
>    error "Too many instruments; not enough MIDI channels."
> assignChannels chans@(c:cs) (i:is) =
>    let percList = ["percussion", "perc", "drum", "drums"]
>    in  if map toLower i `elem` percList
>          then (GeneralMidi.drumChannel, GeneralMidi.drumProgram)
>                      : assignChannels chans is
>          else (c, fromMaybe
>             (error ("unknown instrument <<" ++ i ++ ">>"))
>             (GeneralMidi.instrumentNameToProgram i))
>                      : assignChannels cs is
> fromInstruments :: Ord instr => [instr] -> ToChannel instr
> fromInstruments instrs =
>    let fm = Map.fromList (zip instrs GeneralMidi.instrumentChannels)
>    in  Map.findWithDefault fm (error "More instruments than channels")
\end{haskelllisting} The following functions lookup \type{Instr}s in \type{InstrumentMap.ChannelProgramTable}s to recover channel and program change numbers. Note that the function that does string matching ignores case, and that instrument name and search pattern match if one is a prefix of the other one. For example, \code{"chur"} matches \code{"Church Organ"}. Note also that the {\em first} match succeeds, so using a substring should be done with care to be sure that the correct instrument is selected. \begin{haskelllisting}
> partialMatch :: Instr -> Instr -> Bool
> partialMatch "piano" "Acoustic Grand Piano" = True
> partialMatch s1 s2 =
>   let s1' = map toLower s1
>       s2' = map toLower s2
>   in  all (uncurry (==)) (zip s1' s2')
>
> lookupIName :: [(Instr, a)] -> Instr -> a
> lookupIName  ys x =
>    maybe (error ("InstrumentMap.lookupIName: Instrument " ++ x ++ " unknown"))
>          snd (List.find (partialMatch x . fst) ys)
>
> lookup :: Eq instr => [(instr, a)] -> instr -> a
> lookup ys x =
>    fromMaybe (error ("InstrumentMap.lookup: Instrument unknown"))
>              (List.lookup x ys)
\end{haskelllisting} \begin{haskelllisting}
> reverseLookupMaybe :: Eq a => [(instr, a)] -> a -> Maybe instr
> reverseLookupMaybe ys x =
>    List.lookup x (map swap ys)
> reverseLookup :: Eq a => [(instr, a)] -> a -> instr
> reverseLookup ys x =
>    let instr = reverseLookupMaybe ys x
>        err   = error "InstrumentMap.reverseLookup: channel+program not found"
>    in  fromMaybe err instr
\end{haskelllisting} A default \type{InstrumentMap.ChannelProgramTable}. Note: the PC sound card I'm using is limited to 9 instruments. \begin{haskelllisting}
> defltTable :: [(Instr, ChannelMsg.Channel, GeneralMidi.Instrument)]
> defltTable =
>    map (\(instr,chan,gmInstr) -> (instr, ChannelMsg.toChannel chan, gmInstr))
>    [("piano",   1, GeneralMidi.AcousticGrandPiano),
>     ("vibes",   2, GeneralMidi.Vibraphone),
>     ("bass",    3, GeneralMidi.AcousticBass),
>     ("flute",   4, GeneralMidi.Flute),
>     ("sax",     5, GeneralMidi.TenorSax),
>     ("guitar",  6, GeneralMidi.AcousticGuitarSteel),
>     ("violin",  7, GeneralMidi.Viola),
>     ("violins", 8, GeneralMidi.StringEnsemble1),
>     ("drums",   9, GeneralMidi.AcousticGrandPiano)]
>       -- the GM name for drums is unimportant, only channel 9
> deflt :: ChannelProgramTable Instr
> deflt =
>    map (\(iName, chan, gmName) ->
>          (iName, (chan, GeneralMidi.instrumentToProgram gmName))) defltTable
> defltGM :: ChannelProgramTable GeneralMidi.Instrument
> defltGM =
>    map (\(_, chan, gmName) ->
>          (gmName, (chan, GeneralMidi.instrumentToProgram gmName))) defltTable
> defltCMap :: [(GeneralMidi.Instrument, ChannelMsg.Channel)]
> defltCMap =
>    map (\(_, chan, gmName) -> (gmName, chan)) defltTable
\end{haskelllisting}