-- | Encoding and decoding of OSC types as JSON values. module Sound.OSC.Type.JSON where import qualified Data.Aeson as A {- aeson -} import qualified Data.ByteString.Char8 as Char8 {- bytestring -} import qualified Data.ByteString.Lazy as Lazy {- bytestring -} import qualified Data.ByteString.Lazy.UTF8 as UTF8 {- utf8-string -} import qualified Sound.OSC as O {- hosc -} import qualified Sound.OSC.Datum.Datem as D {- hosc -} import qualified Sound.OSC.Type.JSON.Aeson as J {- hosc-json -} -- * Library variant -- | The JSON value type. type Value = A.Value -- * String translation -- | 'String' variant of 'encode_json'. encode_json_str :: Value -> String encode_json_str = UTF8.toString . J.encode_json -- | 'String' variant of 'decode_json'. -- -- > import Sound.OSChar8.Type.JSON -- > let j = decode_json_str "[\"/n_set\",-1,\"c1\",66]" -- > fmap decode_message j decode_json_str :: String -> Maybe Value decode_json_str = J.decode_json . UTF8.fromString -- * Encoding -- | JSON numbers are 'Either' 'Integer' or 'Double'. type Number = Either Integer Double -- | Encode 'Number'. encode_number :: Number -> Value encode_number = either J.encode_integer J.encode_double -- | Encode 'O.TimeStamp' data ('O.Time'), ie. the @hosc@ real-valued -- @NRT@ representation. encode_timestamp :: O.Time -> Value encode_timestamp n = J.encode_assoc ("timestamp",J.encode_double n) encode_integral :: Integral n => n -> Value encode_integral = J.encode_integer . fromIntegral encode_floating :: Real n => n -> Value encode_floating = J.encode_double . realToFrac -- | Encode 'O.Blob' data ('Lazy.ByteString'). encode_blob :: Lazy.ByteString -> Value encode_blob b = let a = J.encode_list (map encode_integral (Lazy.unpack b)) in J.encode_assoc ("blob",a) -- | Encode 'O.Midi' data ('Word8' tuple). encode_midi :: O.MIDI -> A.Value encode_midi (O.MIDI p q r s) = let a = J.encode_list (map encode_integral [p,q,r,s]) in J.encode_assoc ("midi",a) -- | 'Datum' encoder. The encoding is shallow, 'O.Int', 'O.Float' and -- 'O.Double' are all sent to 'A.Number'. 'O.Blob', 'O.TimeStamp' and -- 'O.Midi' are tagged. -- -- > let {t = [(int32 0,"0") -- > ,(int64 0,"0") -- > ,(float 0.0,"0.0") -- > ,(double 0.1,"0.1") -- > ,(string "s","\"s\"") -- > ,(Blob (Data.ByteString.Lazy.pack [0,1]),"{\"blob\":[0,1]}") -- > ,(TimeStamp 0.0,"{\"timestamp\":0.0}") -- > ,(midi (0,1,2,3),"{\"midi\":[0,1,2,3]}")] -- > ;r = map (\(d,s) -> encode_json_str (encode_datum d) == s) t} -- > in all id r == True encode_datum :: O.Datum -> Value encode_datum d = case d of O.Int32 n -> encode_integral n O.Int64 n -> encode_integral n O.Float n -> encode_floating n O.Double n -> encode_floating n O.ASCII_String s -> J.encode_string (Char8.unpack s) O.Blob b -> encode_blob b O.TimeStamp n -> encode_timestamp n O.Midi m -> encode_midi m -- | 'Message' encoder, the representation is a flat array of -- @address@ and then arguments. -- -- > let m = message "/m" [Int32 0,Float 1,string "s"] -- > in encode_json_str (encode_message m) -- -- > import Sound.SC3 -- > encode_json_str (encode_message (n_free [0])) == "[\"/n_free\",0]" encode_message :: O.Message -> Value encode_message (O.Message a d) = let a' = J.encode_string a d' = map encode_datum d in J.encode_list (a' : d') -- | 'O.Bundle' encoder, the representation is a flat array of @#bundle@ -- tag, 'O.TimeStamp' and then message arrays. -- -- > let b = bundle 0 [message "/m" []] -- > in encode_json_str (encode_bundle b) -- -- > let {b = bundle 0 [c_set1 3 4,n_free [0]] -- > ;r = "[\"#bundle\",{\"timestamp\":0.0}" ++ -- > ",[\"/c_set\",3,4.0],[\"/n_free\",0]]"} -- > in encode_json_str (encode_bundle b) == r encode_bundle :: O.Bundle -> Value encode_bundle (O.Bundle t m) = let b = J.encode_string "#bundle" t' = encode_timestamp t m' = map encode_message m in J.encode_list (b : t' : m') -- | 'Packet' encoder. encode_packet :: O.Packet -> Value encode_packet p = case p of O.Packet_Message m -> encode_message m O.Packet_Bundle b -> encode_bundle b -- * Decoder -- | Decode 'Message'. -- -- > let m = message "/m" [Int32 1,Float 1] -- > in decode_message (encode_message m) == Just m decode_message :: Value -> Maybe O.Message decode_message j = case J.decode_list j of Just (m : d) -> case J.decode_datum m of Just (O.ASCII_String m') -> mapM J.decode_datum d >>= Just . O.message (Char8.unpack m') _ -> Nothing _ -> Nothing -- | Decode 'Bundle'. -- -- > let b = bundle 0.0 [message "/m" [Int32 1,Float 1]] -- > in decode_bundle (encode_bundle b) == Just b -- -- > let {b = bundle 0 [c_set1 3 4,n_free [0]] -- > ;j = encode_bundle b} -- > in (b,decode_bundle j) decode_bundle :: Value -> Maybe O.Bundle decode_bundle j = case J.decode_list j of Just (b : t : m) -> case (D.datum_string =<< J.decode_datum b,J.decode_datum t) of (Just "#bundle",Just (O.TimeStamp t')) -> mapM decode_message m >>= Just . O.Bundle t' _ -> Nothing _ -> Nothing -- | Decode 'Packet'. decode_packet :: Value -> Maybe O.Packet decode_packet v = case decode_bundle v of Just b -> Just (O.Packet_Bundle b) Nothing -> case decode_message v of Just m -> Just (O.Packet_Message m) Nothing -> Nothing