packages feed

zmidi-core 0.5.0 → 0.6.0

raw patch · 10 files changed

+336/−87 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- ZMidi.Core.Datatypes: DataEvent :: MidiDataEvent -> MidiEvent
- ZMidi.Core.Datatypes: MidiDataEvent :: TagByte -> MidiDataEvent
- ZMidi.Core.Datatypes: getTagByte :: MidiDataEvent -> TagByte
- ZMidi.Core.Datatypes: instance Eq MidiDataEvent
- ZMidi.Core.Datatypes: instance Ord MidiDataEvent
- ZMidi.Core.Datatypes: instance Show MidiDataEvent
- ZMidi.Core.Datatypes: newtype MidiDataEvent
+ ZMidi.Core.Canonical: canonical :: MidiFile -> MidiFile
+ ZMidi.Core.Datatypes: MidiDataOther :: TagByte -> MidiDataOther
+ ZMidi.Core.Datatypes: MidiEventOther :: MidiDataOther -> MidiEvent
+ ZMidi.Core.Datatypes: RS_OFF :: MidiRunningStatus
+ ZMidi.Core.Datatypes: RS_ON :: MidiRunningStatus
+ ZMidi.Core.Datatypes: data MidiRunningStatus
+ ZMidi.Core.Datatypes: getMidiDataOther :: MidiDataOther -> TagByte
+ ZMidi.Core.Datatypes: instance Enum MidiRunningStatus
+ ZMidi.Core.Datatypes: instance Eq MidiDataOther
+ ZMidi.Core.Datatypes: instance Eq MidiRunningStatus
+ ZMidi.Core.Datatypes: instance Ord MidiDataOther
+ ZMidi.Core.Datatypes: instance Ord MidiRunningStatus
+ ZMidi.Core.Datatypes: instance Show MidiDataOther
+ ZMidi.Core.Datatypes: instance Show MidiRunningStatus
+ ZMidi.Core.Datatypes: newtype MidiDataOther
- ZMidi.Core.Datatypes: VoiceEvent :: MidiVoiceEvent -> MidiEvent
+ ZMidi.Core.Datatypes: VoiceEvent :: MidiRunningStatus -> MidiVoiceEvent -> MidiEvent

Files

CHANGES view
@@ -1,4 +1,20 @@ +0.5.0 to 0.6.0:+  +  * Extended the parser and changed the syntax tree to interpret +    MIDI Running Status. +   +  * Added a module @ZMidi.Core.Canonical@ to translate MidiFiles+    to a canonical form where any shorthand NoteOff introduced by +    Running Status is expanded to regular NoteOn and NoteOff +    events (with Running Status set, MidiFiles can signal NoteOff +    events as another NoteOn with 0 channel velocity).+  +  * Added a hack to the pretty printer to stop printing ASCII+    chars greater than 164 causing an error when printing to +    stdout.+  + 0.4.0 to 0.5.0:    * Changed order of @MidiVoiceEnvent@ constructors so the Ord 
src/ZMidi/Core.hs view
@@ -21,7 +21,8 @@  module ZMidi.Core   (-    module ZMidi.Core.Datatypes+    module ZMidi.Core.Canonical+  , module ZMidi.Core.Datatypes   , module ZMidi.Core.Pretty   , module ZMidi.Core.ReadFile   , module ZMidi.Core.VersionNumber@@ -30,6 +31,7 @@        ) where +import ZMidi.Core.Canonical import ZMidi.Core.Datatypes import ZMidi.Core.Pretty import ZMidi.Core.ReadFile
+ src/ZMidi/Core/Canonical.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# OPTIONS -Wall #-}++--------------------------------------------------------------------------------+-- |+-- Module      :  ZMidi.Core.Canonical+-- Copyright   :  (c) Stephen Tetley 2012+-- License     :  BSD3+--+-- Maintainer  :  Stephen Tetley <stephen.tetley@gmail.com>+-- Stability   :  unstable+-- Portability :  GHC (at least generalized newtype deriving)+--+-- Convert a MidiFile into \"canonical\" form - i.e. expand +-- any use of Running Status and translate Running Status high, +-- NoteOn channel velocity 0 events to NoteOff events.+--+--------------------------------------------------------------------------------+++module ZMidi.Core.Canonical+  (+++    canonical    ++  ) where++import ZMidi.Core.Datatypes++-- | Convert an MidiFile into \"canonical\" form where any +-- abbreviation introduced by Running Status is expanded.+--+-- Note - even with Running Status on the syntax tree is almost +-- canonical (some expansion takes place in the Parser), so this+-- translation is quite simplistic.+--+canonical :: MidiFile -> MidiFile+canonical (MidiFile hdr trks) = MidiFile hdr (map transTrack trks)++transTrack :: MidiTrack -> MidiTrack+transTrack (MidiTrack msgs) = MidiTrack $ map transMsg msgs++transMsg :: MidiMessage -> MidiMessage+transMsg (dt,e) = (dt, transEvent e)++transEvent :: MidiEvent -> MidiEvent+transEvent (VoiceEvent rs e) = VoiceEvent RS_OFF (transVoiceEvent rs e)+transEvent evt               = evt+++transVoiceEvent :: MidiRunningStatus -> MidiVoiceEvent -> MidiVoiceEvent+transVoiceEvent RS_ON (NoteOn ch pch 0) = NoteOff ch pch 0+transVoiceEvent _     evt               = evt+
src/ZMidi/Core/Datatypes.hs view
@@ -14,8 +14,8 @@ -- Concrete syntax tree for MIDI files. -- -- Values are sometimes not interpreted. This means that the--- the data types do not fully represent the sematics of the --- data, but all the data is either stored within the data type +-- the data types do not fully represent the sematics of MIDI +-- events, but all the data is either stored within the data type  -- or synthesizeable. Hence, @ readFile >>= writeFile @ will  -- produce an identical binary \[1\].  --@@ -34,10 +34,11 @@   , MidiHeader(..)     , MidiTrack(..)   , MidiFormat(..)+  , MidiRunningStatus(..)   , MidiMessage   , MidiEvent(..) -  , MidiDataEvent(..)+  , MidiDataOther(..)   , MidiVoiceEvent(..)   , MidiSysExEvent(..)   , MidiSysCommonEvent(..)@@ -149,27 +150,45 @@ type MidiMessage = (DeltaTime, MidiEvent)  --- Note, the Ord instance for pairs is very useful for rendering.--- When we have have a NoteOn and a NoteOff on the same channel at the --- same time we want the NoteOff played first. An ordinary sort will --- give us this.-      +-- | Running Status.+--+-- MIDI allows a compact representation of voice events where+-- consecutive events (same event, same channel) only need to+-- include the first event-channel byte - subsequent events +-- only send payload until the next event or channel change.+--+-- Including @MidiRunningStatus@ in the data representation is +-- important for ZMidi as an aim is to allow round-tripping+-- of exisiting MIDI files. However it makes MIDI generation+-- more complicated (there is more scope to generate bad +-- output) - if you are only generating MIDI it is wise to always +-- set @MidiRunningStatus@ to @RS_OFF@.+-- +data MidiRunningStatus = RS_ON | RS_OFF+  deriving (Enum,Eq,Ord,Show)  --- | Recognised event types - some types ('DataEvent' and +-- | Recognised event types - some types ('MidiEventOther' and  -- 'SysEx') are not interpreted. -- data MidiEvent -    -- | Data event - just initial tag byte, -    -- uninterpreted+    -- | An unrecognized event. This event is not expected in +    -- well formed MIDI, but the parser may insert it - if it +    -- encounters ill-formed data.     ---    = DataEvent         MidiDataEvent+    = MidiEventOther   MidiDataOther -    -- | Voice event (e.g @note-on@, @note-off@) are relayed to specific-    -- channels.+    -- | Voice event (e.g @note-on@, @note-off@) are relayed to +    -- specific channels. +    -- +    -- Note - they are tagged with Running Status, this is +    -- pertinent to parsing MIDI where a input stream may use +    -- running status to save space. If you are generating MIDI+    -- use RunningStatus with caution and ensure that consecutive+    -- events are all of the same sort.     ---    | VoiceEvent        MidiVoiceEvent+    | VoiceEvent        MidiRunningStatus MidiVoiceEvent       -- | SysEx - system exclusive event. Usually synthesizer @@ -196,14 +215,16 @@    deriving (Eq,Show,Ord) + -- | Data events are events with tags from 0x00 to 0x7F.  --  -- Data events have no payload - they are represented only by the -- tag byte.   ---newtype MidiDataEvent = MidiDataEvent { getTagByte :: TagByte }+newtype MidiDataOther = MidiDataOther { getMidiDataOther :: TagByte }   deriving (Eq,Ord,Show) + -- | Voice events control the output of the synthesizer. -- -- Note - change in v0.5.0 - the constructors have been reordered@@ -281,6 +302,8 @@     -- NOTE - currently value is uninterpreted.     --     | PitchBend           Word8 Word16++   deriving (Eq,Show,Ord)  -- | \SysEx\ - system exclusive event. 
src/ZMidi/Core/Internal/ParserMonad.hs view
@@ -17,12 +17,19 @@ module ZMidi.Core.Internal.ParserMonad   ( -    ErrMsg++    RS_VoiceEvent(..)+  , ErrMsg   , Pos   , ParseErr(..)   , ParserM   , runParser-  ++  , getRunningEvent+  , setRunningEvent++  , peek+  , dropW8   , word8   , int8 @@ -43,6 +50,7 @@    ) where + import Control.Applicative import Data.Bits import qualified Data.ByteString.Lazy as L@@ -50,6 +58,18 @@ import Data.Int import Data.Word +-- | Status is either OFF of the previous VoiceEvent * Channel.+--+data RS_VoiceEvent = RS_STATUS_OFF+                   | RS_NOTE_OFF    !Word8+                   | RS_NOTE_ON     !Word8+                   | RS_NOTE_AFT    !Word8+                   | RS_CONTROL     !Word8+                   | RS_PROG_CHANGE !Word8+                   | RS_CHAN_AFT    !Word8+                   | RS_PCH_BEND    !Word8+  deriving (Eq,Show)                 + -- | Position of the parser in the input stream. --  -- This is exposed by the ReadFile API and may be useful for @@ -58,10 +78,12 @@ type Pos = Int  data ParserState = ParserState -       { pos       :: !Pos-       , input     :: L.ByteString +       { pos                    :: !Pos+       , running_status         :: !RS_VoiceEvent+       , input                  :: !L.ByteString         } + -- | Error message - alias for String. -- type ErrMsg = String@@ -96,24 +118,58 @@                                 Left e -> (Left e, s')                                 Right a -> (getParserM . k) a s' + -- | Run the parser. -- runParser :: L.ByteString -> ParserM a -> Either ParseErr a-runParser bs mf = fst $ getParserM mf (ParserState { pos = 0, input = bs})+runParser bs mf = +    fst $ getParserM mf state_zero +  where+    -- seed the initial state with an unmatchable event+    state_zero = ParserState { pos = 0+                             , running_status = RS_STATUS_OFF+                             , input = bs }  +-- | Get the Running Status flag and channel.+--+getRunningEvent :: ParserM RS_VoiceEvent+getRunningEvent = ParserM $ \s -> (Right $ running_status s, s)++-- | Set the Running Status flag and channel.+--+setRunningEvent :: RS_VoiceEvent -> ParserM ()+setRunningEvent rs = ParserM $ \s -> (Right (), s { running_status = rs })++ -- | Get current Pos. -- getPos :: ParserM Int getPos = ParserM $ \s -> (Right $ pos s, s) ++-- | Peek a Word8.+--+peek :: ParserM Word8+peek = ParserM $ \s@(ParserState n rs bs) -> case L.uncons bs of +    Nothing      -> (Left (ParseErr n "peek - no more data."), s)+    Just (a,_)   -> (Right a, ParserState n rs bs)+++-- | Drop a Word8.+--+dropW8 :: ParserM ()+dropW8 = ParserM $ \s@(ParserState n rs bs) -> case L.uncons bs of +    Nothing      -> (Left (ParseErr n "dropW8 - no more data."), s)+    Just (_,bs') -> (Right (), ParserState (n+1) rs bs')++ -- | Parse a Word8. -- word8 :: ParserM Word8-word8 = ParserM $ \s@(ParserState n bs) -> case L.uncons bs of +word8 = ParserM $ \s@(ParserState n rs bs) -> case L.uncons bs of      Nothing      -> (Left (ParseErr n "word8 - no more data."), s)-    Just (a,bs') -> (Right a, ParserState (n+1) bs')-+    Just (a,bs') -> (Right a, ParserState (n+1) rs bs')   -- | Parse an Int8.@@ -121,26 +177,27 @@ int8 :: ParserM Int8 int8 = fromIntegral <$> word8 + -- | Parse a Word16 (big-endian). -- word16be :: ParserM Word16-word16be = ParserM $ \s@(ParserState n bs) -> case uncons2 bs of+word16be = ParserM $ \s@(ParserState n rs bs) -> case uncons2 bs of     Nothing -> (Left (ParseErr n "word16be - no more data."), s)-    Just (a,b,bs') -> (Right $ w16be a b, ParserState (n+2) bs')+    Just (a,b,bs') -> (Right $ w16be a b, ParserState (n+2) rs bs')  -- | Parse a Word24 (big-endian). -- word24be :: ParserM Word32-word24be = ParserM $ \s@(ParserState n bs) -> case uncons3 bs of+word24be = ParserM $ \s@(ParserState n rs bs) -> case uncons3 bs of     Nothing -> (Left (ParseErr n "word24be - no more data."), s)-    Just (a,b,c,bs') -> (Right $ w24be a b c, ParserState (n+3) bs')+    Just (a,b,c,bs') -> (Right $ w24be a b c, ParserState (n+3) rs bs')  -- | Parse a Word32 (big-endian). -- word32be :: ParserM Word32-word32be = ParserM $ \s@(ParserState n bs) -> case uncons4 bs of+word32be = ParserM $ \s@(ParserState n rs bs) -> case uncons4 bs of     Nothing -> (Left (ParseErr n "word32be - no more data."), s)-    Just (a,b,c,d,bs') -> (Right $ w32be a b c d, ParserState (n+4) bs')+    Just (a,b,c,d,bs') -> (Right $ w32be a b c d, ParserState (n+4) rs bs')  -- | Parse a Char. --
src/ZMidi/Core/Pretty.hs view
@@ -34,6 +34,7 @@ import ZMidi.Core.Datatypes import ZMidi.Core.Internal.SimpleFormat +import Data.Char import Data.List import Data.Monoid import Data.Word@@ -108,8 +109,8 @@     dtime   = padl 6  (integral delta)  ppEvent :: MidiEvent -> Doc-ppEvent (DataEvent e)         = ppDataEvent e-ppEvent (VoiceEvent e)        = ppVoiceEvent e+ppEvent (MidiEventOther e)    = ppMidiDataOther e+ppEvent (VoiceEvent rs e)     = ppVoiceEvent rs e ppEvent (SysExEvent e)        = ppSysExEvent e ppEvent (SysCommonEvent e)    = ppSysCommonEvent e ppEvent (SysRealTimeEvent e)  = ppSysRealTimeEvent e@@ -119,29 +120,35 @@ event :: String -> Doc -> Doc event s d = padr 18 (text s) `dashsep` d -ppDataEvent :: MidiDataEvent -> Doc-ppDataEvent (MidiDataEvent tag)     = event "data" (hex2 tag)+ppMidiDataOther :: MidiDataOther -> Doc+ppMidiDataOther (MidiDataOther n)     = event "midi data other" (hex2 n) -ppVoiceEvent :: MidiVoiceEvent -> Doc-ppVoiceEvent (Controller c n v)     = ++-- | Voice event needs RunningStatus...++ppVoiceEvent :: MidiRunningStatus -> MidiVoiceEvent -> Doc+ppVoiceEvent _  (Controller c n v)     =      event "controller" (hex2 c `sep` hex2 n `sep` hex2 v) -ppVoiceEvent (ProgramChange c n)    = +ppVoiceEvent _  (ProgramChange c n)    =      event "program-change" (hex2 c `sep` hex2 n) -ppVoiceEvent (NoteOff c n v)        =+ppVoiceEvent _ (NoteOff c n v)        =     event "note-off" (hex2 c `sep` hex2 n `sep` hex2 v) -ppVoiceEvent (NoteOn c n v)         = -    event "note-on" (hex2 c `sep` hex2 n `sep` hex2 v)+ppVoiceEvent rs (NoteOn c n v)         +    | rs == RS_ON && v == 0 = event "note-off (RS,V0)" +                                    (hex2 c `sep` hex2 n `sep` hex2 v)+    | otherwise = event "note-on" (hex2 c `sep` hex2 n `sep` hex2 v) -ppVoiceEvent (NoteAftertouch c n v) =++ppVoiceEvent _  (NoteAftertouch c n v) =     event "note-aftertouch" (hex2 c `sep` hex2 n `sep` hex2 v) -ppVoiceEvent (ChanAftertouch c v)   = +ppVoiceEvent _  (ChanAftertouch c v)   =      event "channel-aftertouch" (hex2 c `sep` hex2 v) -ppVoiceEvent (PitchBend c v)        =+ppVoiceEvent _  (PitchBend c v)        =     event "pitch-bend" (hex2 c `sep` hex4 v)  @@ -179,7 +186,7 @@   ppMetaEvent :: MidiMetaEvent -> Doc-ppMetaEvent (TextEvent ty s)          = event (textType ty) (text s)+ppMetaEvent (TextEvent ty s)          = event (textType ty) (text $ safeString s)  ppMetaEvent (SequenceNumber w)        = event "sequence-number" (hex4 w) @@ -226,3 +233,16 @@ ppScale (MAJOR)         = text "major" ppScale (MINOR)         = text "minor" ppScale (SCALE_OTHER i) = text "unrecognized scale" `sep` hex2 i+++-- | This is a temporary hack - characters above ASCII 163+-- cause an (invalid character) error when written to stdout.+--+-- Make the string safe.+--+safeString :: String -> String+safeString = map (f . ord) +  where+    f i | i < 164   = chr i+        | otherwise = '#'+
src/ZMidi/Core/ReadFile.hs view
@@ -16,6 +16,14 @@ -- (i.e all cases of event types are fully enumerated).  -- Malformed input (syntactically bad events, or truncated data)  -- will cause fatal parse errors.+-- +-- Note - the parser returns a /literal/ result if the input +-- uses Running Status, i.e, - the answer matches the input - +-- where running status uses a NoteOn event with velocity 0 to +-- stand for a NoteOff, the parser likewise returns a Note-On. +-- Use the @ZMidi.Core.Canonical@ to translate the input to +-- canonical form where note-offs are encoded directly with +-- NoteOff. -- -------------------------------------------------------------------------------- @@ -96,18 +104,16 @@ -- can cause fatal parse errors. -- event :: ParserM MidiEvent-event = word8 >>= step+event = peek >>= step   where     -- 00..7f  -- /data/-    step n | n == 0xFF  = MetaEvent        <$> (word8 >>= metaEvent)-           | n >= 0xF8  = SysRealTimeEvent <$> sysRealTimeEvent n-           | n >= 0xF1  = SysCommonEvent   <$> sysCommonEvent n-           | n == 0xF0  = SysExEvent       <$> sysExEvent-           | n >= 0x80  = VoiceEvent       <$> voiceEvent (splitByte n)-           | otherwise  = DataEvent        <$> dataEvent n--dataEvent :: Word8 -> ParserM MidiDataEvent-dataEvent tag = pure $ MidiDataEvent tag+    step n +      | n == 0xFF  = MetaEvent         <$> (dropW8 *> (word8 >>= metaEvent))+      | n >= 0xF8  = SysRealTimeEvent  <$> (dropW8 *> sysRealTimeEvent n)+      | n >= 0xF1  = SysCommonEvent    <$> (dropW8 *> sysCommonEvent n)+      | n == 0xF0  = SysExEvent        <$> (dropW8 *> sysExEvent)+      | n >= 0x80  = VoiceEvent RS_OFF <$> (dropW8 *> voiceEvent (splitByte n))+      | otherwise  = getRunningEvent >>= runningStatus      @@ -118,31 +124,74 @@ -- voiceEvent :: SplitByte -> ParserM MidiVoiceEvent voiceEvent (SB 0x80 ch)  = -    "note-off"          <??> (NoteOff ch)        <$> word8 <*> word8+    setRunningEvent (RS_NOTE_OFF ch)    >> noteOff ch  voiceEvent (SB 0x90 ch)  = -    "note-on"           <??> (NoteOn ch)         <$> word8 <*> word8+    setRunningEvent (RS_NOTE_ON ch)     >> noteOn ch  voiceEvent (SB 0xA0 ch)  = -    "note aftertouch"   <??> (NoteAftertouch ch) <$> word8 <*> word8-    +    setRunningEvent (RS_NOTE_AFT ch)    >> noteAftertouch ch+ voiceEvent (SB 0xB0 ch)  = -    "controller"        <??> (Controller ch)     <$> word8 <*> word8+    setRunningEvent (RS_CONTROL ch)     >> controller ch  voiceEvent (SB 0xC0 ch)  = -    "program change"    <??> (ProgramChange ch)  <$> word8 +    setRunningEvent (RS_PROG_CHANGE ch) >> programChange ch  voiceEvent (SB 0xD0 ch)  = -    "chan aftertouch"   <??> (ChanAftertouch ch) <$> word8 +    setRunningEvent (RS_CHAN_AFT ch)    >> chanAftertouch ch  voiceEvent (SB 0xE0 ch)  = -    "pitch bend"        <??> (PitchBend ch)      <$> word16be+    setRunningEvent (RS_PCH_BEND ch)    >> pitchBend ch  -- This is an /impossible/ match - to get here either @splitByte@  -- or the pattern match of @step@ in @event@ would be wrong. voiceEvent (SB z   _ )  = impossibleMatch $ "voiceEvent " ++ hexStr z   ++noteOff :: Word8 -> ParserM MidiVoiceEvent+noteOff ch = +    "note-off"          <??> (NoteOff ch)        <$> word8 <*> word8++noteOn :: Word8 -> ParserM MidiVoiceEvent+noteOn ch = +    "note-on"           <??> (NoteOn ch)         <$> word8 <*> word8++noteAftertouch :: Word8 -> ParserM MidiVoiceEvent+noteAftertouch ch = +    "note aftertouch"   <??> (NoteAftertouch ch) <$> word8 <*> word8++controller :: Word8 -> ParserM MidiVoiceEvent    +controller ch = +    "controller"        <??> (Controller ch)     <$> word8 <*> word8++programChange :: Word8 -> ParserM MidiVoiceEvent+programChange ch = +    "program change"    <??> (ProgramChange ch)  <$> word8 ++chanAftertouch :: Word8 -> ParserM MidiVoiceEvent+chanAftertouch ch = +    "chan aftertouch"   <??> (ChanAftertouch ch) <$> word8 ++pitchBend :: Word8 -> ParserM MidiVoiceEvent+pitchBend ch = +    "pitch bend"        <??> (PitchBend ch)      <$> word16be++++++runningStatus :: RS_VoiceEvent -> ParserM MidiEvent+runningStatus (RS_NOTE_OFF ch)        = VoiceEvent RS_ON <$> noteOff ch+runningStatus (RS_NOTE_ON ch)         = VoiceEvent RS_ON <$> noteOn ch+runningStatus (RS_NOTE_AFT ch)        = VoiceEvent RS_ON <$> noteAftertouch ch+runningStatus (RS_CONTROL ch)         = VoiceEvent RS_ON <$> controller ch+runningStatus (RS_PROG_CHANGE ch)     = VoiceEvent RS_ON <$> programChange ch+runningStatus (RS_CHAN_AFT ch)        = VoiceEvent RS_ON <$> chanAftertouch ch+runningStatus (RS_PCH_BEND ch)        = VoiceEvent RS_ON <$> pitchBend ch+runningStatus (RS_STATUS_OFF)         = +    MidiEventOther . MidiDataOther <$> word8  -- | Input is a contiguous sequence 0xF1 to 0xF7 so any match  -- failure indicates this parser is called with an invalid 
src/ZMidi/Core/VersionNumber.hs view
@@ -22,7 +22,7 @@  -- | Version number ----- > (0,5,0)+-- > (0,6,0) -- zmidi_core_version :: (Int,Int,Int)-zmidi_core_version = (0,5,0)+zmidi_core_version = (0,6,0)
src/ZMidi/Core/WriteFile.hs view
@@ -75,40 +75,46 @@ putMessage (dt,evt) = putVarlen (fromIntegral dt) *> putEvent evt  putEvent :: MidiEvent -> PutM ()-putEvent (DataEvent e)        = putDataEvent  e-putEvent (VoiceEvent e)       = putVoiceEvent e+putEvent (MidiEventOther e)   = putMidiDataOther  e+putEvent (VoiceEvent rs e)    = putVoiceEvent rs e putEvent (SysExEvent e)       = putSysExEvent e putEvent (SysCommonEvent e)   = putSysCommonEvent e putEvent (SysRealTimeEvent e) = putSysRealTimeEvent e putEvent (MetaEvent e)        = putMetaEvent  e    -putDataEvent :: MidiDataEvent -> PutM ()-putDataEvent (MidiDataEvent tag) = putWord8 tag-    -putVoiceEvent :: MidiVoiceEvent -> PutM ()-putVoiceEvent (NoteOff c n v)         = -    putWord8 (0x8 `u4l4` c) *> putWord8 n *> putWord8 v +putMidiDataOther :: MidiDataOther -> PutM ()+putMidiDataOther (MidiDataOther n) = putWord8 n -putVoiceEvent (NoteOn c n v)          = -    putWord8 (0x9 `u4l4` c) *> putWord8 n *> putWord8 v  -putVoiceEvent (NoteAftertouch c n v)  = -    putWord8 (0xA `u4l4` c) *> putWord8 n *> putWord8 v+-- | Note - this assumes the output is properly formed where+-- initial events are labelled with RS_OFF and subsequent events +-- are labelled with RS_ON only when they share the same +-- constructor and channel.+-- +putVoiceEvent :: MidiRunningStatus -> MidiVoiceEvent -> PutM ()+putVoiceEvent rs (NoteOff c n v)         = +    optTagByte rs (0x8 `u4l4` c) *> putWord8 n *> putWord8 v  -putVoiceEvent (Controller c n v)      = -    putWord8 (0xB `u4l4` c) *> putWord8 n *> putWord8 v+putVoiceEvent rs (NoteOn c n v)          = +    optTagByte rs (0x9 `u4l4` c) *> putWord8 n *> putWord8 v  -putVoiceEvent (ProgramChange c n)     = -    putWord8 (0xC `u4l4` c) *> putWord8 n+putVoiceEvent rs (NoteAftertouch c n v)  = +    optTagByte rs (0xA `u4l4` c) *> putWord8 n *> putWord8 v -putVoiceEvent (ChanAftertouch c v)    = -    putWord8 (0xD `u4l4` c) *> putWord8 v  +putVoiceEvent rs (Controller c n v)      = +    optTagByte rs (0xB `u4l4` c) *> putWord8 n *> putWord8 v -putVoiceEvent (PitchBend c v)         = -    putWord8 (0xE `u4l4` c) *> putWord16be v+putVoiceEvent rs (ProgramChange c n)     = +    optTagByte rs (0xC `u4l4` c) *> putWord8 n +putVoiceEvent rs (ChanAftertouch c v)    = +    optTagByte rs (0xD `u4l4` c) *> putWord8 v   +putVoiceEvent rs (PitchBend c v)         = +    optTagByte rs (0xE `u4l4` c) *> putWord16be v++ putSysExEvent :: MidiSysExEvent -> PutM () putSysExEvent (SysEx n ws) =      putWord8 0xF0 *> putVarlen n *> mapM_ putWord8 ws@@ -199,6 +205,11 @@  -------------------------------------------------------------------------------- -- Output helpers++optTagByte :: MidiRunningStatus -> Word8 -> PutM ()+optTagByte RS_OFF n = putWord8 n+optTagByte _      _ = return ()+  prefixLen :: Word8 -> PutM () -> PutM () prefixLen n out = putWord8 n *> out 
zmidi-core.cabal view
@@ -1,5 +1,5 @@ name:             zmidi-core-version:          0.5.0+version:          0.6.0 license:          BSD3 license-file:     LICENSE copyright:        Stephen Tetley <stephen.tetley@gmail.com>@@ -14,9 +14,24 @@   .   Changelog:   .+  v0.5.0 to v0.6.0:+  .+  * Extended the parser and changed the syntax tree to interpret +    MIDI Running Status. +  . +  * Added a module @ZMidi.Core.Canonical@ to translate MidiFiles+    to a canonical form where any shorthand NoteOff introduced by +    Running Status is expanded to regular NoteOn and NoteOff +    events (with Running Status set, MidiFiles can signal NoteOff +    events as another NoteOn with 0 channel velocity).+  .+  * Added a hack to the pretty printer to stop printing ASCII+    chars greater than 164 causing an error when printing to +    stdout.+  .   v0.4.0 to v0.5.0:   .-  * Changed order of @MidiVoiceEnvent@ constructors so the Ord +  * Changed order of @MidiVoiceEvent@ constructors so the Ord      instance follows the order of the /tag/ in the MIDI binary     representation.   . @@ -85,6 +100,7 @@      exposed-modules:     ZMidi.Core,+    ZMidi.Core.Canonical,     ZMidi.Core.Datatypes,     ZMidi.Core.Pretty,     ZMidi.Core.ReadFile,