{- | Parse primitive types contained in MIDI files. -} module Sound.MIDI.Parser.Primitive (getByte, getN, getString, getBigN, getNByteInt, get1, get2, get3, get4, getNByteCardinal, getVar, getVarBytes, getEnum, makeEnum, ) where import qualified Sound.MIDI.Parser.Class as Parser import Control.Monad (replicateM, liftM, ) import Sound.MIDI.IO (ByteList, listCharFromByte, ) import qualified Sound.MIDI.Bit as Bit import Data.Bits (testBit, clearBit) import Data.Word (Word8) import qualified Numeric.NonNegative.Wrapper as NonNeg {- | 'getByte' gets a single byte from the input. -} getByte :: Parser.C parser => Parser.Fallible parser Word8 getByte = Parser.getByte {- | @getN n@ returns n characters (bytes) from the input. -} getN :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser ByteList getN n = replicateM (NonNeg.toNumber n) getByte getString :: Parser.C parser => NonNeg.Integer -> Parser.Fallible parser String getString n = liftM listCharFromByte (getBigN n) getBigN :: Parser.C parser => NonNeg.Integer -> Parser.Fallible parser ByteList getBigN n = sequence $ Bit.replicateBig (1 + fromIntegral (maxBound :: NonNeg.Int)) (NonNeg.toNumber n) getByte {- | 'get1', 'get2', 'get3', and 'get4' take 1-, 2-, 3-, or 4-byte numbers from the input (respectively), convert the base-256 data into a single number, and return. -} get1 :: Parser.C parser => Parser.Fallible parser Int get1 = liftM fromIntegral getByte getNByteInt :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser Int getNByteInt n = liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) get1) get2, get3, get4 :: Parser.C parser => Parser.Fallible parser Int get2 = getNByteInt 2 get3 = getNByteInt 3 get4 = getNByteInt 4 getByteAsCardinal :: Parser.C parser => Parser.Fallible parser NonNeg.Integer getByteAsCardinal = liftM fromIntegral getByte getNByteCardinal :: Parser.C parser => NonNeg.Int -> Parser.Fallible parser NonNeg.Integer getNByteCardinal n = liftM Bit.fromBytes (replicateM (NonNeg.toNumber n) getByteAsCardinal) {- | /Variable-length quantities/ are used often in MIDI notation. They are represented in the following way: Each byte (containing 8 bits) uses the 7 least significant bits to store information. The most significant bit is used to signal whether or not more information is coming. If it's @1@, another byte is coming. If it's @0@, that byte is the last one. 'getVar' gets a variable-length quantity from the input. -} getVar :: Parser.C parser => Parser.Fallible parser NonNeg.Integer getVar = liftM (Bit.fromBase (2^(7::Int)) . map fromIntegral) getVarBytes {- | The returned list contains only bytes with the most significant bit cleared. These are digits of a 128-ary number. -} getVarBytes :: Parser.C parser => Parser.Fallible parser [Word8] getVarBytes = do digit <- getByte if flip testBit 7 digit -- if it's the last byte then liftM (flip clearBit 7 digit :) getVarBytes else return [digit] getEnum :: (Parser.C parser, Enum enum, Bounded enum) => Parser.Fallible parser enum getEnum = makeEnum =<< get1 makeEnum :: (Parser.C parser, Enum enum, Bounded enum) => Int -> Parser.Fallible parser enum makeEnum n = let go :: (Parser.C parser, Enum a) => a -> a -> Parser.Fallible parser a go lower upper = if fromEnum lower <= n && n <= fromEnum upper then return (toEnum n) else Parser.giveUp ("value " ++ show n ++ " is out of range for enumeration") in go minBound maxBound