zmidi-core 0.3.0 → 0.4.0
raw patch · 10 files changed
+129/−54 lines, 10 files
Files
- demo/MidiPrint.hs +6/−2
- src/ZMidi/Core.hs +1/−1
- src/ZMidi/Core/Datatypes.hs +45/−11
- src/ZMidi/Core/Internal/ParserMonad.hs +25/−15
- src/ZMidi/Core/Internal/SimpleFormat.hs +4/−4
- src/ZMidi/Core/Pretty.hs +10/−4
- src/ZMidi/Core/ReadFile.hs +18/−11
- src/ZMidi/Core/VersionNumber.hs +2/−2
- src/ZMidi/Core/WriteFile.hs +9/−3
- zmidi-core.cabal +9/−1
demo/MidiPrint.hs view
@@ -2,6 +2,9 @@ -- Dump the contents of a MIDI file +-- Note - GHC (Windows at least) appears to throw an error if+-- the copyright symbol is used a Text meta-event.+ module Main where import ZMidi.Core@@ -20,7 +23,8 @@ process filename = do ans <- readMidi filename case ans of- Left (n,msg) -> putStrLn $ "Parse failure at " ++ show n ++ ": " ++ msg- Right m -> printMidi m+ Left (ParseErr n msg) -> + putStrLn $ "Parse failure at " ++ show n ++ ": " ++ msg+ Right m -> printMidi m
src/ZMidi/Core.hs view
@@ -26,6 +26,7 @@ , module ZMidi.Core.ReadFile , module ZMidi.Core.VersionNumber , module ZMidi.Core.WriteFile+ ) where @@ -34,4 +35,3 @@ import ZMidi.Core.ReadFile import ZMidi.Core.VersionNumber import ZMidi.Core.WriteFile-
src/ZMidi/Core/Datatypes.hs view
@@ -368,15 +368,19 @@ -- data MidiMetaEvent - -- | @ text_type * contents @ + -- | Text / copywright etc. -- + -- > FF * text_type * contents+ -- -- Free text field (e.g. copyright statement). The contents -- can notionally be any length. -- = TextEvent MidiTextType String - -- | @ value @ + -- | Sequence Number -- + -- > FF 00 02 * value+ -- -- Format 1 files - only track 1 should have a sequence -- number. --@@ -388,36 +392,56 @@ -- | SequenceNumber Word16 - -- | @ 1 * channel @ + -- | Channel prefix -- + -- > FF 20 01 * channel+ -- -- Relay all meta and sys-ex events to the given channel. -- -- The first byte should always be 1. -- | ChannelPrefix Word8 Word8 + -- | Midi port+ -- + -- > FF 21 01 * port+ -- + -- Midi port number - used to workaround 16 channel limit...+ -- + | MidiPort Word8+ -- | End-of-track event. --+ -- > FF 2F 00+ -- | EndOfTrack - -- | @ microseconds_per_quarter_note @+ -- | Set tempo --+ -- > FF 51 03 * microseconds_per_quarter_note+ -- | SetTempo Word32 - -- | @ hour * minute * second * frac * subfrac @ + -- | SMPTE offest -- + -- > FF 54 05 * hour * minute * second * frac * subfrac+ -- -- The SMPTE time when a track should start. This event -- should occur at the start of a track, before any non-zero -- time events. -- | SMPTEOffset Word8 Word8 Word8 Word8 Word8 - -- | @ numerator * denominator * metro * num_32nd_notes @ + -- | Time signature + -- + -- > FF 58 04 * numerator * denominator * metro * num_32nd_notes -- | TimeSignature Word8 Word8 Word8 Word8 - -- | @ key_type * scale_type @ + -- | Key signature --+ -- > FF 59 02 * key_type * scale_type+ -- -- @key_type@ is the number of sharps (postive numbers) or -- flats (negative numbers), e.g. (-1) is 1 flat. --@@ -425,17 +449,27 @@ -- | KeySignature Int8 MidiScaleType - -- | @ length * data@ + -- | SSME -- + -- > FF 7F * length * data+ -- -- Sequencer specific meta-event - uninterpreted. -- | SSME Word32 [Word8] + -- | Unrecognized Meta Event+ --+ -- > FF * type * length * data + --+ | MetaOther Word8 Word32 [Word8]+ deriving (Eq,Show,Ord) --- | Scale type - @major@ or @minor@. +-- | Scale type - @major@ or @minor@ or @SCALE_OTHER@. ---data MidiScaleType = MAJOR | MINOR- deriving (Eq,Enum,Ord,Show)+-- @SCALE_OTHER@ represents a parse error.+-- +data MidiScaleType = MAJOR | MINOR | SCALE_OTHER Word8+ deriving (Eq,Ord,Show)
src/ZMidi/Core/Internal/ParserMonad.hs view
@@ -19,7 +19,7 @@ ErrMsg , Pos- , ParseErr+ , ParseErr(..) , ParserM , runParser @@ -33,6 +33,7 @@ , (<??>) , reportError+ , catchError , count , gencount@@ -61,7 +62,8 @@ type ErrMsg = String -type ParseErr = (Pos,ErrMsg)+data ParseErr = ParseErr !Pos !ErrMsg+ deriving (Eq,Show) newtype ParserM a = ParserM { getParserM :: ParserState -> (Either ParseErr a, ParserState) }@@ -95,7 +97,7 @@ word8 :: ParserM Word8 word8 = ParserM $ \s@(ParserState n bs) -> case L.uncons bs of - Nothing -> (Left (n,"word8 - no more data."), s)+ Nothing -> (Left (ParseErr n "word8 - no more data."), s) Just (a,bs') -> (Right a, ParserState (n+1) bs') @@ -107,17 +109,17 @@ word16be :: ParserM Word16 word16be = ParserM $ \s@(ParserState n bs) -> case uncons2 bs of- Nothing -> (Left (n,"word16be - no more data."), s)+ Nothing -> (Left (ParseErr n "word16be - no more data."), s) Just (a,b,bs') -> (Right $ w16be a b, ParserState (n+2) bs') word24be :: ParserM Word32 word24be = ParserM $ \s@(ParserState n bs) -> case uncons3 bs of- Nothing -> (Left (n,"word24be - no more data."), s)+ Nothing -> (Left (ParseErr n "word24be - no more data."), s) Just (a,b,c,bs') -> (Right $ w24be a b c, ParserState (n+3) bs') word32be :: ParserM Word32 word32be = ParserM $ \s@(ParserState n bs) -> case uncons4 bs of- Nothing -> (Left (n, "word32be - no more data."), s)+ 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') @@ -127,15 +129,21 @@ infixr 0 <??> (<??>) :: ErrMsg -> ParserM a -> ParserM a-(<??>) msg p = ParserM $ \s -> case getParserM p s of- (Left (n,_),s') -> (Left (n,msg),s')- (Right a, s') -> (Right a,s')+(<??>) msg p = ParserM $ \s -> + case getParserM p s of+ (Left (ParseErr n _),s') -> (Left (ParseErr n msg),s')+ (Right a, s') -> (Right a,s') reportError :: ErrMsg -> ParserM a-reportError msg = ParserM $ \s -> (Left (pos s, msg), s)--+reportError msg = ParserM $ \s -> (Left (ParseErr (pos s) msg), s) +-- | This uses backtracking...+--+catchError :: ParserM a -> (ErrMsg -> ParserM a) -> ParserM a+catchError f g = ParserM $ \s -> + case getParserM f s of+ (Left (ParseErr _ msg),_) -> getParserM (g msg) s+ (Right a, s') -> (Right a,s') count :: Int -> ParserM a -> ParserM [a] count i p @@ -143,9 +151,11 @@ | otherwise = (:) <$> p <*> count (i-1) p -gencount :: Integral i => ParserM i -> ParserM a -> ParserM (i,[a]) -gencount plen p = plen >>= \i -> - count (fromIntegral i) p >>= \ans -> return (i,ans)+gencount :: Integral i => ParserM i -> ParserM a -> (i -> [a] -> ans) -> ParserM ans+gencount plen p constr = do + i <- plen+ xs <- count (fromIntegral i) p + return $ constr i xs text :: Int -> ParserM String
src/ZMidi/Core/Internal/SimpleFormat.hs view
@@ -21,7 +21,7 @@ , width , output - , sep+ , cat , ssep , char , text@@ -73,10 +73,10 @@ Doc i1 f1 `mappend` Doc i2 f2 = Doc (i1+i2) (f1 . f2) -infixr 6 `sep`+infixr 6 `cat` -sep :: Doc -> Doc -> Doc-sep = mappend+cat :: Doc -> Doc -> Doc+cat = mappend infixr 6 `ssep`
src/ZMidi/Core/Pretty.hs view
@@ -70,7 +70,7 @@ -------------------------------------------------------------------------------- column2 :: String -> Doc -> Doc-column2 s d2 = padr 20 (text s) `sep` char '|' `ssep` d2 +column2 s d2 = padr 20 (text s) `cat` char '|' `ssep` d2 ppFormat :: MidiFormat -> Doc ppFormat = column2 "MIDI Format" . step @@ -177,8 +177,10 @@ ppMetaEvent (ChannelPrefix a b) = event "channel-prefix" (hex2 a `ssep` hex2 b) -ppMetaEvent EndOfTrack = text "end-of-track"+ppMetaEvent (MidiPort w) = event "midi-port" (hex2 w) +ppMetaEvent (EndOfTrack) = text "end-of-track"+ ppMetaEvent (SetTempo w) = event "set-tempo" (integral w) ppMetaEvent (SMPTEOffset h m s f sf) = @@ -193,9 +195,12 @@ ppMetaEvent (SSME n ws) = event "sequencer-specific" (byteList n ws) +ppMetaEvent (MetaOther ty len ws) =+ event "meta-other" (hex2 ty `ssep` byteList len ws)+ byteList :: (Show a, Integral a) => a -> [Word8] -> Doc -byteList n ws | n < 10 = integral n `sep` mconcat (map hex2 ws)- | otherwise = integral n `sep` multiply 10 '.'+byteList n ws | n < 10 = integral n `cat` mconcat (map hex2 ws)+ | otherwise = integral n `cat` multiply 10 '.' textType :: MidiTextType -> String@@ -211,3 +216,4 @@ ppScale :: MidiScaleType -> Doc ppScale MAJOR = text "major" ppScale MINOR = text "minor"+ppScale (SCALE_OTHER i) = text "unrecognized scale" `ssep` hex2 i
src/ZMidi/Core/ReadFile.hs view
@@ -20,7 +20,7 @@ readMidi -- * Auxiallary types- , ParseErr+ , ParseErr(..) -- exported from internal file ParserMonad , Pos , ErrMsg @@ -47,7 +47,7 @@ midiFile :: ParserM MidiFile -midiFile = {- printHexAll >> -} do+midiFile = do hdr <- header let i = trackCount hdr trks <- count i track@@ -76,7 +76,7 @@ message :: ParserM MidiMessage-message = (,) <$> deltaTime <*> event+message = (,) <$> deltaTime <*> event deltaTime :: ParserM DeltaTime deltaTime = "delta time" <??> fmap fromIntegral getVarlen@@ -157,10 +157,12 @@ sysExEvent :: ParserM MidiSysExEvent-sysExEvent = "sys-ex" <??> (uncurry SysEx) <$> getVarlenBytes- +sysExEvent = "sys-ex" <??> getVarlenBytes SysEx +-- FF and the "type" byte already parsed, input to this function +-- is the type byte.+-- metaEvent :: Word8 -> ParserM MidiMetaEvent metaEvent 0x00 = "sequence number" <??> SequenceNumber <$> (assertWord8 2 *> word16be)@@ -176,6 +178,9 @@ metaEvent 0x20 = "channel prefix" <??> ChannelPrefix <$> word8 <*> word8 +metaEvent 0x21 = + "MIDI port" <??> MidiPort <$> (assertWord8 1 *> word8)+ metaEvent 0x2F = "end of track" <??> EndOfTrack <$ assertWord8 0 @@ -197,12 +202,14 @@ <*> scale metaEvent 0x7F = - "system specific meta event" <??> (uncurry SSME) <$> getVarlenBytes + "system specific meta event" <??> getVarlenBytes SSME -metaEvent z = reportError $ "unreconized meta-event " ++ hexStr z+metaEvent ty = + "meta other" <??> getVarlenBytes (MetaOther ty) + format :: ParserM MidiFormat format = word16be >>= fn @@ -224,11 +231,11 @@ where fn 0 = return MAJOR fn 1 = return MINOR- fn z = reportError $ "scale expecting 0 or 1, got " ++ hexStr z+ fn z = return $ SCALE_OTHER z textEvent :: MidiTextType -> ParserM MidiMetaEvent-textEvent ty = (TextEvent ty . snd) <$> getVarlenText+textEvent ty = getVarlenText (\_ ss -> TextEvent ty ss) -------------------------------------------------------------------------------- -- helpers@@ -250,10 +257,10 @@ msg = "assertString - input did not match " ++ s -getVarlenText :: ParserM (Word32,String) +getVarlenText :: (Word32 -> String -> ans) -> ParserM ans getVarlenText = gencount getVarlen char8 -getVarlenBytes :: ParserM (Word32,[Word8]) +getVarlenBytes :: (Word32 -> [Word8] -> ans) -> ParserM ans getVarlenBytes = gencount getVarlen word8
src/ZMidi/Core/VersionNumber.hs view
@@ -22,7 +22,7 @@ -- | Version number ----- > (0,3,0)+-- > (0,4,0) -- zmidi_core_version :: (Int,Int,Int)-zmidi_core_version = (0,3,0)+zmidi_core_version = (0,4,0)
src/ZMidi/Core/WriteFile.hs view
@@ -154,6 +154,9 @@ putMetaEvent (ChannelPrefix i ch) = putWord8 0xFF *> putWord8 0x20 *> prefixLen i (putWord8 ch)++putMetaEvent (MidiPort pn) = + putWord8 0xFF *> putWord8 0x21 *> putWord8 0x01 *> putWord8 pn putMetaEvent (EndOfTrack) = putWord8 0xFF *> putWord8 0x2F *> prefixLen 0 (pure ())@@ -180,6 +183,9 @@ putMetaEvent (SSME i ws) = putWord8 0xFF *> putWord8 0x7F *> putVarlen i *> mapM_ putWord8 ws +putMetaEvent (MetaOther ty len bs) = + putWord8 0xff *> putWord8 ty *> putVarlen (fromIntegral len) + *> mapM_ putWord8 bs @@ -207,9 +213,9 @@ i' = fromIntegral i wscale :: MidiScaleType -> Word8-wscale MAJOR = 0x00-wscale MINOR = 0x01-+wscale (MAJOR) = 0x00+wscale (MINOR) = 0x01+wscale (SCALE_OTHER i) = i putWord24be :: Word32 -> PutM () putWord24be i = putWord8 c *> putWord8 b *> putWord8 a
zmidi-core.cabal view
@@ -1,5 +1,5 @@ name: zmidi-core-version: 0.3.0+version: 0.4.0 license: BSD3 license-file: LICENSE copyright: Stephen Tetley <stephen.tetley@gmail.com>@@ -13,6 +13,14 @@ dependencies only on ByteString and Data.Binary. . Changelog:+ .+ v0.3.0 to v0.4.0:+ .+ * Added new constructors to @MidiMetaEvent@ for MidiPort and + MetaOther. MetaOther recognizes otherwise unrecognized events+ improving the robustness of the parser. Similarly a new + /other/ constructor has been added to @MidiScaleType@ to + avoid parse errors. . v0.2.1 to v0.3.0: .