module Sound.MIDI.File.Load (fromFile, fromStream, maybeFromStream, showFile)
where
import Sound.MIDI.File
import qualified Sound.MIDI.File as MIDIFile
import qualified Sound.MIDI.Event as MIDIEvent
import qualified Data.EventList.Relative.TimeBody as EventList
import qualified Numeric.NonNegative.Wrapper as NonNeg
import Sound.MIDI.IO (ByteString, readBinaryFile, stringCharFromByte)
import qualified Sound.MIDI.Bit as Bit
import Data.Bits (testBit, clearBit)
import Data.Word (Word8)
import Data.Maybe (mapMaybe, fromMaybe)
import Sound.MIDI.String (unlinesS)
import qualified Sound.MIDI.Parser as Parser
import qualified Sound.MIDI.ParserState as ParserState
import qualified Control.Monad.State as State
import Control.Monad (replicateM, liftM, liftM2)
import Control.Monad.State (StateT(StateT, runStateT), evalStateT, lift)
fromFile :: FilePath -> IO MIDIFile.T
fromFile = liftM fromStream . readBinaryFile
fromStream :: ByteString -> MIDIFile.T
fromStream contents =
case maybeFromStream contents of
Right (mf,[]) -> mf
Right _ -> error "Garbage left over."
Left msg -> error ("MIDI.Load.fromStream: " ++ msg)
maybeFromStream :: ByteString -> Either String (MIDIFile.T, ByteString)
maybeFromStream = evalParser parse
evalParser :: ByteParser a -> ByteString -> Either String (a, ByteString)
evalParser = runStateT
parse :: ByteParser MIDIFile.T
parse =
getChunk >>= \ chunk ->
case chunk of
Header (format, nTracks, division) ->
liftM
(MIDIFile.Cons format division .
map removeEndOfTrack .
mapMaybe trackFromChunk)
(replicateM (NonNeg.toNumber nTracks) getChunk)
_ -> parse
trackFromChunk :: Chunk -> Maybe Track
trackFromChunk (Track t) = Just t
trackFromChunk _ = Nothing
removeEndOfTrack :: Track -> Track
removeEndOfTrack xs =
fromMaybe
(error "Empty track, missing EndOfTrack")
(do (initEvents, lastEvent) <- EventList.viewR xs
let (eots, track) =
EventList.partition isEndOfTrack initEvents
if EventList.null eots
then return ()
else error "EndOfTrack inside a track"
if isEndOfTrack (snd lastEvent)
then return ()
else error "Track does not end with EndOfTrack"
return track)
isEndOfTrack :: Event -> Bool
isEndOfTrack ev =
case ev of
MetaEvent EndOfTrack -> True
_ -> False
getChunk :: ByteParser Chunk
getChunk =
do
(ty, body) <- getPlainChunk
case ty of
"MThd" ->
return $ Header $
case evalParser getHeader body of
Right (hd,[]) -> hd
Right (_,_) -> error "header chunk too large"
Left msg -> error ("getChunk header: " ++ msg)
"MTrk" ->
return $ Track $
either (\msg -> error ("getChunk track: " ++ msg)) id $
evalStateT (evalStateT getTrack initReadEvent) body
_ -> return (AlienChunk ty body)
data Chunk =
Header (MIDIFile.Type, NonNeg.Int, Division)
| Track Track
| AlienChunk String ByteString
deriving Eq
getHeader :: ByteParser (MIDIFile.Type, NonNeg.Int, Division)
getHeader =
do
format <- liftM toEnum get2
nTracks <- liftM (NonNeg.fromNumberMsg "MIDI.Load.getHeader") get2
division <- getDivision
return (format, nTracks, division)
getDivision :: ByteParser Division
getDivision =
do
x <- get1
y <- get1
return (if x < 128
then Ticks (NonNeg.fromNumberMsg "MIDI.Load.getDivision" (x*256+y))
else SMPTE (256x) y)
getTrack :: TrackParser MIDIFile.Track
getTrack =
liftM
EventList.fromPairList
(ParserState.zeroOrMore getSchedEvent)
getSchedEvent :: TrackParser MIDIFile.SchedEvent
getSchedEvent = liftM2 (,) (lift getVar) getEvent
getEvent :: TrackParser MIDIFile.Event
getEvent =
do
tag <- lift get1
case tag of
240 -> liftM SysExStart $ lift (getBigN =<< getVar)
247 -> liftM SysExCont $ lift (getBigN =<< getVar)
255 -> lift $
do
code <- get1
size <- getVar
liftM MetaEvent (getMetaEvent code size)
x -> if x>127
then let parseEv = decodeStatus tag
in putEventParser parseEv >> lift (get1 >>= parseEvent parseEv)
else
lift . flip parseEvent tag =<< getEventParser
getPlainTrack :: TrackParser MIDIFile.Track
getPlainTrack =
liftM
EventList.fromPairList
(ParserState.oneOrMore getSchedEvent)
newtype EventParser =
EventParser {parseEvent :: Int -> ByteParser MIDIFile.Event}
decodeStatus :: Int -> EventParser
decodeStatus tag =
let (code, channel) = Bit.splitAt 4 tag
in EventParser (getMIDIEvent code (MIDIEvent.toChannel channel))
getMIDIEvent :: Int -> MIDIEvent.Channel -> Int -> ByteParser MIDIFile.Event
getMIDIEvent code channel firstData =
let pitch = MIDIEvent.toPitch firstData
getVel = liftM MIDIEvent.toVelocity get1
getME =
case code of
08 -> liftM (MIDIEvent.NoteOff pitch) getVel
09 -> liftM (MIDIEvent.NoteOn pitch) getVel
10 -> liftM (MIDIEvent.PolyAfter pitch) get1
11 -> liftM (MIDIEvent.Control (toEnum firstData)) get1
12 -> return (MIDIEvent.ProgramChange (MIDIEvent.toProgram firstData))
13 -> return (MIDIEvent.MonoAfter firstData)
14 -> liftM (\msb -> MIDIEvent.PitchBend (firstData+128*msb)) get1
_ -> fail ("invalid MIDIEvent code:" ++ show code)
in liftM (MIDIEvent channel) getME
getMetaEvent :: Int -> NonNeg.Integer -> ByteParser MetaEvent
getMetaEvent code size =
case code of
000 -> liftM SequenceNum get2
001 -> getText size TextEvent
002 -> getText size Copyright
003 -> getText size TrackName
004 -> getText size InstrName
005 -> getText size Lyric
006 -> getText size Marker
007 -> getText size CuePoint
032 -> liftM (MIDIPrefix . MIDIEvent.toChannel) get1
047 -> return EndOfTrack
081 -> liftM (SetTempo . NonNeg.fromNumberMsg "MIDI.Load.getMetaEvent") get3
084 -> do {hrs <- get1 ; mins <- get1 ; secs <- get1;
frames <- get1 ; bits <- get1 ;
return (SMPTEOffset hrs mins secs frames bits)}
088 -> do
n <- get1
d <- get1
c <- get1
b <- get1
return (TimeSig n d c b)
089 -> do
sf <- get1
mi <- get1
return (KeySig (toKeyName sf) (toEnum mi))
127 -> liftM SequencerSpecific (getBigN size)
_ -> liftM (Unknown code) (getBigN size)
getText :: NonNeg.Integer -> (String -> MetaEvent) -> ByteParser MetaEvent
getText size c = liftM c (getString size)
toKeyName :: Int -> Key
toKeyName sf = toEnum (mod (sf+7) 15)
getByte :: ByteParser Word8
getByte = StateT $ maybe (Left "reached end of file") Right . viewL
viewL :: [a] -> Maybe (a, [a])
viewL xs =
case xs of
[] -> Nothing
(c:cs) -> Just (c,cs)
getN :: NonNeg.Int -> ByteParser ByteString
getN n = replicateM (NonNeg.toNumber n) getByte
getString :: NonNeg.Integer -> ByteParser String
getString n = liftM stringCharFromByte (getBigN n)
getBigN :: NonNeg.Integer -> ByteParser ByteString
getBigN n =
sequence $
Bit.replicateBig
(succ (fromIntegral (maxBound :: NonNeg.Int)))
(NonNeg.toNumber n)
getByte
get1 :: ByteParser Int
get1 = liftM fromIntegral getByte
getNByteInt :: NonNeg.Int -> ByteParser Int
getNByteInt n =
liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) get1)
get2, get3, get4 :: ByteParser Int
get2 = getNByteInt 2
get3 = getNByteInt 3
get4 = getNByteInt 4
getVar :: ByteParser NonNeg.Integer
getVar =
liftM (Bit.fromBase (2^(7::Int)) . map fromIntegral) getVarBytes
getVarBytes :: ByteParser [Word8]
getVarBytes =
do
digit <- getByte
if flip testBit 7 digit
then liftM (flip clearBit 7 digit :) getVarBytes
else return [digit]
showFile :: FilePath -> IO ()
showFile fileName = putStr . showChunks =<< readBinaryFile fileName
showChunks :: ByteString -> String
showChunks mf = showMR getChunks (unlinesS . map pp) mf ""
where
pp :: (String, ByteString) -> ShowS
pp ("MThd",contents) =
showString "Header: " .
showMR getHeader shows contents
pp ("MTrk",contents) =
showString "Track:\n" .
showMR (evalStateT getPlainTrack initReadEvent)
(\track str ->
EventList.foldr
MIDIFile.showTime
(\e -> MIDIFile.showEvent e . showString "\n")
str track)
contents
pp (ty,contents) =
showString "Chunk: " .
showString ty .
showString " " .
shows contents .
showString "\n"
showMR :: ByteParser a -> (a->ShowS) -> ByteString -> ShowS
showMR m pp contents =
case evalParser m contents of
Left msg ->
showString "Parse failed: " . showString (msg++"\n") . shows contents
Right (a,[] ) -> pp a
Right (a,junk) -> pp a . showString "Junk: " . shows junk
getChunks :: ByteParser [(String, ByteString)]
getChunks = Parser.zeroOrMore getPlainChunk
getPlainChunk :: ByteParser (String, ByteString)
getPlainChunk =
liftM2 (,)
(getString 4)
(getN . NonNeg.fromNumberMsg "getPlainChunk" =<< get4)
type ByteParser a = Parser.T ByteString a
type TrackParser a = ParserState.T EventParser ByteString a
putEventParser :: EventParser -> TrackParser ()
putEventParser = State.put
getEventParser :: TrackParser EventParser
getEventParser = State.get
initReadEvent :: EventParser
initReadEvent =
EventParser (const (return (error "At beginning, no event type set so far")))