hmidi-0.1: System/MIDI/Base.hs
-- |The hardware-independent part of the MIDI binding.
{-# LANGUAGE CPP #-}
module System.MIDI.Base
( TimeStamp
, MidiMessage'(..)
, MidiMessage(..)
, MidiEvent(..)
, ClientCallback
, ShortMessage(..)
, translateShortMessage
, untranslateShortMessage
, shortMessage
) where
import Data.Bits
import Data.Word
type TimeStamp = Word32
-- |A \"regular\" MIDI message.
--
-- Remark: `NoteOff` not having a velocity field is a design decision, and a questionable one. According to the
-- MIDI standard, NoteOff also has a velocity. However, most keyboards do not use this feature (send the default
-- value 64), and there are keyboards which do not send NoteOff messages at all, but send NoteOn messages with
-- zero velocity instead (for example the EMU Xboard series). I don't know what would be a good solution.
-- At the moment, the code auto-translates NoteOn messages with zero velocity to NoteOff messages, and the
-- NoteOff velocity is ignored. This behaviour can be inverted with the Cabal flag NoNoteOff.
data MidiMessage'
= NoteOff !Int -- ^ Note Off (key)
| NoteOn !Int !Int -- ^ Note On (key, velocity)
| PolyAftertouch !Int !Int -- ^ Polyphonic key pressure (key, pressure)
| CC !Int !Int -- ^ Control Change (controller, value)
| ProgramChange !Int -- ^ Program Change (program)
| Aftertouch !Int -- ^ Global aftertouch (pressure)
| PitchWheel !Int -- ^ Pitch wheel (value, from -8192..+8191)
deriving (Show,Eq)
-- |The type representing a MIDI message.
data MidiMessage
= MidiMessage !Int !MidiMessage' -- ^ first argument is the MIDI channel (1..16)
| SysEx [Word8] -- ^ not including the bytes 0xf0, 0xf7
| SongPosition !Int
| SongSelect !Int
| TuneRequest
| SRTClock
| SRTStart
| SRTContinue
| SRTStop
| ActiveSensing
| Reset
| Undefined
deriving (Show,Eq)
-- |The type representing a timestamped MIDI message.
-- Time is measured in milisecs elapsed since the last call to `System.MIDI.start`.
data MidiEvent = MidiEvent !TimeStamp !MidiMessage deriving (Show,Eq)
-- |Type of the user callback function.
type ClientCallback = MidiEvent -> IO ()
translateShortMessage :: ShortMessage -> MidiMessage
translateShortMessage (ShortMessage chn msg bt1 bt2) =
if msg < 15
then MidiMessage (fromIntegral chn + 1) $ translate' msg k v
else translate'' chn k v
where
k = fromIntegral bt1
v = fromIntegral bt2
translate' msg k v = case msg of
#ifdef HMIDI_NO_NOTEOFF
8 -> NoteOn k 0
9 -> NoteOn k v
#else
8 -> NoteOff k
9 -> if v>0 then NoteOn k v else NoteOff k
#endif
10 -> PolyAftertouch k v
11 -> CC k v
12 -> ProgramChange k
13 -> Aftertouch k
14 -> PitchWheel (k + shiftL v 7 - 8192)
translate'' lo a b = case lo of
0 -> Undefined
1 -> Undefined
2 -> SongPosition (a + shiftL b 7)
3 -> SongSelect a
4 -> Undefined
5 -> Undefined
6 -> TuneRequest
7 -> Undefined
8 -> SRTClock
9 -> Undefined
10 -> SRTStart
11 -> SRTContinue
12 -> SRTStop
13 -> Undefined
14 -> ActiveSensing
15 -> Reset
untranslateShortMessage :: MidiMessage -> ShortMessage
untranslateShortMessage (MidiMessage chn msg') =
case msg' of
NoteOff k -> shortMessage chn 8 k 64
NoteOn k v -> shortMessage chn 9 k v
PolyAftertouch k v -> shortMessage chn 10 k v
CC k v -> shortMessage chn 11 k v
ProgramChange k -> shortMessage chn 12 k 0
Aftertouch k -> shortMessage chn 13 k 0
PitchWheel n -> let m = min 16383 $ max 0 $ n + 8192 in shortMessage chn 14 (m.&.127) (shiftR m 7)
untranslateShortMessage (SongPosition p) = shortMessage 15 3 (p.&.7) (shiftR p 7)
untranslateShortMessage (SongSelect s) = shortMessage 15 3 (fromIntegral s) 0
untranslateShortMessage TuneRequest = shortMessage 15 6 0 0
untranslateShortMessage SRTClock = shortMessage 15 8 0 0
untranslateShortMessage SRTStart = shortMessage 15 10 0 0
untranslateShortMessage SRTContinue = shortMessage 15 11 0 0
untranslateShortMessage SRTStop = shortMessage 15 12 0 0
untranslateShortMessage ActiveSensing = shortMessage 15 14 0 0
untranslateShortMessage Reset = shortMessage 15 15 0 0
untranslateShortMessage Undefined = error "cannot untranslate Undefined"
untranslateShortMessage (SysEx _) = error "cannot untranslate SysEx"
shortMessage :: Int -> Int -> Int -> Int -> ShortMessage
shortMessage chn msg bt1 bt2 =
ShortMessage (fromIntegral chn - 1) (fromIntegral msg) (fromIntegral bt1) (fromIntegral bt2)
-- |Low level stuff.
data ShortMessage = ShortMessage
{ sm_channel :: Word8
, sm_msg :: Word8
, sm_byte1 :: Word8
, sm_byte2 :: Word8
} deriving Show