-- | Optimised decode function for Osc packets. module Sound.Osc.Coding.Decode.Binary ( get_packet, decodeMessage, decodeBundle, decodePacket, decodePacket_strict, decodeMessageOr, decodeBundleOr, decodePacketOr, ) where import Control.Applicative {- base -} import Control.Monad {- base -} import Data.Word {- base -} import qualified Data.Binary.Get as Binary {- binary -} import qualified Data.ByteString.Char8 as ByteString.Char8 {- bytestring -} import qualified Data.ByteString.Lazy as ByteString.Lazy {- bytestring -} import qualified Data.ByteString.Lazy.Char8 as ByteString.Lazy.Char8 {- bytestring -} import qualified Sound.Osc.Coding.Byte as Byte {- hosc -} import qualified Sound.Osc.Coding.Convert as Convert {- hosc -} import Sound.Osc.Datum {- hosc -} import Sound.Osc.Packet {- hosc -} import qualified Sound.Osc.Time as Time {- hosc -} -- | Get an aligned Osc string. get_string :: Binary.Get String get_string = do s <- Binary.getLazyByteStringNul Binary.skip (Convert.int64_to_int (Byte.align (ByteString.Lazy.length s + 1))) return (ByteString.Lazy.Char8.unpack s) -- | Get an aligned Osc string. get_ascii :: Binary.Get Ascii get_ascii = do s <- Binary.getLazyByteStringNul Binary.skip (Convert.int64_to_int (Byte.align (ByteString.Lazy.length s + 1))) return (ByteString.Char8.pack (ByteString.Lazy.Char8.unpack s)) -- | Get binary data prefixed by byte count. get_bytes :: Word32 -> Binary.Get ByteString.Lazy.ByteString get_bytes n = do b <- Binary.getLazyByteString (Convert.word32_to_int64 n) if n /= Convert.int64_to_word32 (ByteString.Lazy.length b) then fail "get_bytes: end of stream" else Binary.skip (Convert.word32_to_int (Byte.align n)) return b -- | Get an Osc datum. get_datum :: DatumType -> Binary.Get Datum get_datum ty = case ty of 'i' -> fmap Int32 Binary.getInt32be 'h' -> fmap Int64 Binary.getInt64be 'f' -> fmap Float Binary.getFloatbe 'd' -> fmap Double Binary.getDoublebe 's' -> fmap AsciiString get_ascii 'b' -> fmap Blob (get_bytes =<< Binary.getWord32be) 't' -> fmap (TimeStamp . Time.ntpi_to_ntpr) Binary.getWord64be 'm' -> fmap Midi (liftM4 MidiData Binary.getWord8 Binary.getWord8 Binary.getWord8 Binary.getWord8) _ -> fail ("get_datum: illegal type " ++ show ty) -- | Get an Osc 'Message', fail if type descriptor is invalid. get_message :: Binary.Get Message get_message = do cmd <- get_string dsc <- get_ascii case ByteString.Char8.unpack dsc of ',' : tags -> do arg <- mapM get_datum tags return (Message cmd arg) e -> fail ("get_message: invalid type descriptor string: " ++ e) -- | Get a sequence of Osc 'Message's, each one headed by its length. get_message_seq :: Binary.Get [Message] get_message_seq = do b <- Binary.isEmpty if b then return [] else do p <- flip Binary.isolate get_message . Convert.word32_to_int =<< Binary.getWord32be ps <- get_message_seq return (p : ps) -- | Get a bundle. Fail if bundle header is not found in packet. get_bundle :: Binary.Get (BundleOf Message) get_bundle = do h <- Binary.getByteString (ByteString.Char8.length Byte.bundleHeader_strict) when (h /= Byte.bundleHeader_strict) (fail "get_bundle: not a bundle") t <- fmap Time.ntpi_to_ntpr Binary.getWord64be fmap (Bundle t) get_message_seq -- | Get an Osc 'Packet'. get_packet :: Binary.Get (PacketOf Message) get_packet = fmap Packet_Bundle get_bundle <|> fmap Packet_Message get_message {-# INLINE decodeMessage #-} {-# INLINE decodeBundle #-} {-# INLINE decodePacket #-} {-# INLINE decodePacket_strict #-} {- | Decode an Osc 'Message' from a lazy ByteString. >>> let b = ByteString.Lazy.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0] >>> decodeMessage b == Message "/g_free" [Int32 0] True -} decodeMessage :: ByteString.Lazy.ByteString -> Message decodeMessage = Binary.runGet get_message -- | Decode an Osc 'Bundle' from a lazy ByteString. decodeBundle :: ByteString.Lazy.ByteString -> BundleOf Message decodeBundle = Binary.runGet get_bundle -- | Run decoder and report any error. runDecoder :: Binary.Get t -> ByteString.Lazy.Char8.ByteString -> Either String t runDecoder f p = case Binary.runGetOrFail f p of Left (_, _, err) -> Left err Right (_, _, decoded) -> Right decoded {- | Decode an Osc packet from a lazy ByteString. >>> let b = ByteString.Lazy.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0] >>> decodePacket b == Packet_Message (Message "/g_free" [Int32 0]) True -} decodePacket :: ByteString.Lazy.ByteString -> PacketOf Message decodePacket = Binary.runGet get_packet -- | Decode an Osc packet from a strict Char8 ByteString. decodePacket_strict :: ByteString.Char8.ByteString -> PacketOf Message decodePacket_strict = Binary.runGet get_packet . ByteString.Lazy.fromChunks . (: []) {- | Either decode Osc message or return an error message. Prevents application halt for non-valid message/bundle/packet arrives. >>> let b = ByteString.Lazy.pack [1,2,3,2,1] >>> decodePacketOr b Left "not enough bytes" -} decodeMessageOr :: ByteString.Lazy.ByteString -> Either String Message decodeMessageOr = runDecoder get_message decodeBundleOr :: ByteString.Lazy.ByteString -> Either String Bundle decodeBundleOr = runDecoder get_bundle decodePacketOr :: ByteString.Lazy.ByteString -> Either String Packet decodePacketOr = runDecoder get_packet