
A MIDI note is an interim data structure
which shall be stored in a \type{Performance.BackEnd.T} list of events.
It stores each note as a single record,
that is it is not split into note-on and note-off.

\begin{haskelllisting}

> module Haskore.Interface.MIDI.Note where

> import qualified Sound.MIDI.General       as GeneralMidi
> import qualified Haskore.Interface.MIDI.InstrumentMap as InstrMap
> import qualified Sound.MIDI.File  as MidiFile
> import qualified Sound.MIDI.Event as MidiEvent
> import qualified Haskore.Music.GeneralMIDI            as MidiMusic
> import qualified Haskore.Music.Rhythmic               as RhyMusic
> import qualified Haskore.Basic.Pitch                  as Pitch
> import           Haskore.General.Utility (limit, toMaybe)
> import qualified Data.List as List

> data T =
>    Cons {
>      velocityOn  :: MidiEvent.Velocity,
>      velocityOff :: MidiEvent.Velocity,
>      channel     :: MidiEvent.Channel,
>      program     :: MidiEvent.Program,
>      pitch       :: MidiEvent.Pitch
>    }

\end{haskelllisting}

You can convert a MidiNote from and to a pair of MIDI events.
This is used in \module{MIDI.Read} and \module{MIDI.Write}, respectively.
\begin{haskelllisting}

> fromMIDIEvents :: (MidiFile.Event, MidiFile.Event) -> Maybe T
> fromMIDIEvents
>    (MidiFile.MIDIEvent c0 (MidiEvent.NoteOn  p0 v0),
>     MidiFile.MIDIEvent c1 (MidiEvent.NoteOff p1 v1)) =
>       let progErr = error ("program depends on channel settings - " ++
>                            "still not determined")
>       in  toMaybe (c0 == c1 && p0 == p1)
>                   (Cons v0 v1 c0 progErr p0)
> fromMIDIEvents _ = Nothing

> toMIDIEvents :: T -> (MidiFile.Event, MidiFile.Event)
> toMIDIEvents note =
>    let chan = channel     note
>        p    = pitch       note
>        vOn  = velocityOn  note
>        vOff = velocityOff note
>        me0 = MidiFile.MIDIEvent chan (MidiEvent.NoteOn  p vOn)
>        me1 = MidiFile.MIDIEvent chan (MidiEvent.NoteOff p vOff)
>    in  (me0, me1)

\end{haskelllisting}

A MidiNote can be constructed from several kinds of notes.
Here are two instances for notes of generic rhythmic music
and General MIDI notes.
These converters are also the functions
where the maps from instrument types to MIDI programs go into.
The first set of functions is need for writing MIDI files.
\begin{haskelllisting}

> fromRhyNote :: RealFrac dyn =>
>    InstrMap.ToChannelProgramPitch drum ->
>    InstrMap.ToChannelProgram instr ->
>       dyn -> Pitch.Relative -> RhyMusic.Note drum instr -> T
> fromRhyNote dMap iMap dyn trans (RhyMusic.Note vel body) =
>    let velMidi = velocityFromStd dyn vel
>    in  case body of
>           RhyMusic.Tone instr p ->
>              let (chan, prog) = iMap instr
>              in  Cons velMidi velMidi
>                       chan prog (pitchFromStd trans p)
>           RhyMusic.Drum drum ->
>              let (chan, prog, key) = dMap drum
>              in  Cons velMidi velMidi chan prog key

> fromGMNote :: RealFrac dyn =>
>    InstrMap.ToChannel MidiMusic.Instr ->
>       dyn -> Pitch.Relative -> MidiMusic.Note -> T
> fromGMNote iMap =
>    fromRhyNote
>       (\drum  -> (GeneralMidi.drumChannel,
>                   GeneralMidi.drumProgram,
>                   GeneralMidi.drumToKey drum))
>       (\instr -> (iMap instr, MidiEvent.toProgram (fromEnum instr)))

> velocityFromStd :: RealFrac dyn =>
>    dyn -> Rational -> MidiEvent.Velocity
> velocityFromStd dyn vel =
>    MidiEvent.toVelocity $
>    round (limit (0, MidiEvent.maximumVelocity)
>                 (dyn * fromRational vel * MidiEvent.normalVelocity))

> pitchFromStd :: Pitch.Relative -> Pitch.T -> MidiEvent.Pitch
> pitchFromStd trans p =
>    -- MIDI pitch is in range because of range checks on Pitch construction
>    MidiEvent.increasePitch (Pitch.toInt p + trans) MidiEvent.zeroKey

\end{haskelllisting}

The second set of functions is need for reading MIDI files.
\begin{haskelllisting}

> toRhyNote ::
>    InstrMap.FromChannelProgramPitch drum ->
>    InstrMap.FromChannelProgram instr ->
>    T -> RhyMusic.Note drum instr
> toRhyNote dMap iMap (Cons v _ ch prog mp) =
>    let drum  = dMap (ch, prog, mp)
>        instr = iMap (ch, prog)
>    in  RhyMusic.Note (velocityToStd v)
>           (case (drum,instr) of
>              (Nothing, Nothing) ->
>                  error "MidiNote.toRhyNote: channel+program not found"
>              (Just _, Just _) ->
>                  error "MidiNote.toRhyNote: note can be drum or instrument"
>              (Just drum', Nothing) ->
>                  RhyMusic.Drum drum'
>              (Nothing, Just instr') ->
>                  RhyMusic.Tone instr' (pitchToStd mp))

> toGMNote :: T -> MidiMusic.Note
> toGMNote =
>    toRhyNote
>       (\(ch, _, mp) ->
>            toMaybe (ch==GeneralMidi.drumChannel)
>                    (GeneralMidi.drumFromKey mp))
>       (\(ch, prog) ->
>            toMaybe (ch/=GeneralMidi.drumChannel)
>                    (GeneralMidi.instrumentFromProgram prog))

\end{haskelllisting}

Load the velocity.
This shouldn't be mixed up with the volume.
The volume which is controlled by the MIDI Volume controller
simply scales the signal
whereas the velocity is an instrument specific value
that corresponds to the intensity with which the instrument is played.

\begin{haskelllisting}

> velocityToStd :: Fractional a => MidiEvent.Velocity -> a
> velocityToStd x =
>    fromIntegral (MidiEvent.fromVelocity x) / MidiEvent.normalVelocity

> pitchToStd :: MidiEvent.Pitch -> Pitch.T
> pitchToStd p = Pitch.fromInt (MidiEvent.subtractPitch MidiEvent.zeroKey p)

\end{haskelllisting}
