-- | __You probably don't need to use this directly__ -- use "Vivid.SynthDef" instead -- -- This is a representation of how SynthDefs are sent over the wire, as described in the -- < http://doc.sccode.org/Reference/Synth-Definition-File-Format.html Synth Definition File Format > -- helpfile. {-# OPTIONS_HADDOCK show-extensions #-} {-# LANGUAGE NoRebindableSyntax , ScopedTypeVariables , LambdaCase , OverloadedStrings , NoIncoherentInstances , NoMonomorphismRestriction , NoUndecidableInstances #-} -- TODO: remove ScopedTypeVariables? module Vivid.SC.SynthDef.Literally ( LiteralSynthDef(..) , encodeSynthDefFile , decodeSynthDefFile , encodeLiteralSynthDef , decodeLiteralSynthDef , getLiteralSynthDef' , putLiteralSynthDef' , UGenSpec(..) , InputSpec(..) , ParamName(..) , SynthDefFile(..) , OutputSpec(..) , VariantSpec(..) , uOpToSpecialI , biOpToSpecialI , specialIToUOp , specialIToBiOp , sdLitPretty -- For testing: -- todo: maybe 'Internal' module , putPString' , getPString' , encodePString , getPString , getParamName , getParamName' , putParamName' , encodeParamName , getInputSpec' , putInputSpec' , getInputSpec , encodeInputSpec , getOutputSpec' , putOutputSpec' , getOutputSpec , encodeOutputSpec , getCalcRate' , putCalcRate' , getUGenSpec , getUGenSpec' , encodeUGenSpec , putUGenSpec' , getVariantSpec , getVariantSpec' , putVariantSpec' , encodeVariantSpec ) where import Vivid.SC.SynthDef.Types (BinaryOp(..), UnaryOp(..), CalculationRate(..)) -- import Vivid.OSC.Old.Util (floatToWord, wordToFloat) -- For GHC 7.8 and below: -- (Eventually remove) import Control.Applicative (pure, (<$>)) import Control.Arrow (first) import Control.Monad import Data.Binary (decode, encode) -- import Data.Binary.Get -- import Data.Binary.Put import Data.ByteString (ByteString) import qualified Data.ByteString as BS (cons, splitAt, drop, head, length) import qualified Data.ByteString.UTF8 as UTF8 import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Lazy as BSL (fromStrict, toStrict) import Data.Int import Data.List.Split (chunksOf) import Data.Monoid -- cereal. From the sc docs: "all data is stored big endian" import Data.Serialize hiding (decode, encode) -- import Data.Serialize.Get -- import Data.Serialize.Put -- import Data.Serialize.IEEE754 -- TODO: temporary: import Data.Bits ((.&.), complement, Bits) import qualified Foreign as F import System.IO.Unsafe (unsafePerformIO) data LiteralSynthDef = LiteralSynthDef { _synthDefName :: ByteString -- pstring: "a pascal format string: a byte giving the length followed by that many bytes" ,_synthDefConstants :: [Float] ,_synthDefParameters :: [Float] -- Initial values ,_synthDefParamNames :: [ParamName] ,_synthDefUGens :: [UGenSpec] ,_synthDefVariants :: [VariantSpec] } deriving (Show, Eq) data SynthDefFile = SynthDefFile [LiteralSynthDef] deriving (Show) decodeSynthDefFile :: ByteString -> Either String SynthDefFile decodeSynthDefFile blob = let (top, rest) = BS.splitAt 4 blob (fileVersion :: Int32, rest2) = first (decode . BSL.fromStrict) $ BS.splitAt 4 rest in if (top /= "SCgf" || fileVersion /= 2) then Left $ "screwed up synthdef file " <> show top <> show fileVersion else let (numberOfSynthDefs :: Int16, rest3) = first (decode . BSL.fromStrict) $ BS.splitAt 2 rest2 (synthDefs, rest4) = getNWith numberOfSynthDefs decodeLiteralSynthDef rest3 in if rest4 /= "" then Left $ "leftover data: " <> show rest4 else Right $ SynthDefFile synthDefs {- a synth-definition-file is : int32 - four byte file type id containing the ASCII characters: "SCgf" int32 - file version, currently 2. int16 - number of synth definitions in this file (D). [ synth-definition ] * D -} encodeSynthDefFile :: SynthDefFile -> ByteString encodeSynthDefFile (SynthDefFile synthDefs) = mconcat [ "SCgf" , BSL.toStrict $ encode (2 :: Int32) , BSL.toStrict $ encode (toEnum ((length::[a]->Int) synthDefs) :: Int16) , mconcat $ map encodeLiteralSynthDef synthDefs ] -- Yes, the 'restN's are ugly, yes i could have used a state monad. Don't judge! decodeLiteralSynthDef :: ByteString -> (LiteralSynthDef, {- rest: -} ByteString) decodeLiteralSynthDef blob = let (name :: ByteString, rest) = getPString blob (numConstants :: Int32, rest2) = getInt32 rest (constants :: [Float], rest3) = first (map (wordToFloat . decode . BSL.fromStrict)) $ getN4ByteBlocks numConstants rest2 (numParams :: Int32, rest4) = getInt32 rest3 (params :: [Float], rest5) = first (map (wordToFloat . decode . BSL.fromStrict)) $ getN4ByteBlocks numParams rest4 (numParamNames :: Int32, rest6) = getInt32 rest5 (paramNames :: [ParamName], rest7) = getNWith numParamNames getParamName rest6 (numUGens :: Int32, rest8) = getInt32 rest7 (uGens, rest9) = getNWith numUGens getUGenSpec rest8 (numVariants, rest10) = getInt16 rest9 (variantSpecs, rest11) = getNWith numVariants (getVariantSpec numParams) rest10 in (LiteralSynthDef name constants params paramNames uGens variantSpecs, rest11) getLiteralSynthDef' :: Get LiteralSynthDef getLiteralSynthDef' = do name <- getPString' -- Can dedupe these 5 (and others like it): numConstants <- getInt32be constants <- replicateM (fromEnum numConstants) getFloat32be numParams <- getInt32be params <- replicateM (fromEnum numParams) getFloat32be numParamNames <- getInt32be paramNames <- replicateM (fromEnum numParamNames) getParamName' numUGens <- getInt32be ugens <- replicateM (fromEnum numUGens) getUGenSpec' -- Note this is 16, not 32: numVariants <- getInt16be variants <- replicateM (fromEnum numVariants) (getVariantSpec' numParams) pure $ LiteralSynthDef name constants params paramNames ugens variants {- a synth-definition is : pstring - the name of the synth definition int32 - number of constants (K) [float32] * K - constant values int32 - number of parameters (P) [float32] * P - initial parameter values int32 - number of parameter names (N) [ param-name ] * N int32 - number of unit generators (U) [ ugen-spec ] * U int16 - number of variants (V) [ variant-spec ] * V -} encodeLiteralSynthDef :: LiteralSynthDef -> ByteString encodeLiteralSynthDef (LiteralSynthDef name constants params paramNames uGenSpecs variants) = mconcat [ encodePString name , BSL.toStrict $ encode (toEnum ((length::[a]->Int) constants) :: Int32) , mconcat $ map (BSL.toStrict . encode . floatToWord) constants , BSL.toStrict $ encode (toEnum ((length::[a]->Int) params) :: Int32) , mconcat $ map (BSL.toStrict . encode . floatToWord) params , BSL.toStrict $ encode (toEnum ((length::[a]->Int) paramNames) :: Int32) , mconcat $ map encodeParamName paramNames , BSL.toStrict $ encode (toEnum ((length::[a]->Int) uGenSpecs) :: Int32) , mconcat $ map encodeUGenSpec uGenSpecs , BSL.toStrict $ encode (toEnum ((length::[a]->Int) variants) :: Int16) , mconcat $ map encodeVariantSpec variants ] -- TODO: temporary: -- from hosc: align :: (Num i,Bits i) => i -> i {-# INLINE align #-} align n = ((n + 3) .&. complement 3) - n -- from data-binary-ieee754: floatToWord :: Float -> F.Word32 floatToWord = coercionThing wordToFloat :: F.Word32 -> Float wordToFloat = coercionThing doubleToWord :: Double -> F.Word64 doubleToWord = coercionThing wordToDouble :: F.Word64 -> Double wordToDouble = coercionThing coercionThing :: (F.Storable a, F.Storable b) => a -> b coercionThing x = unsafePerformIO $ F.alloca $ \buf -> do F.poke (F.castPtr buf) x F.peek buf putLiteralSynthDef' :: LiteralSynthDef -> Put putLiteralSynthDef' (LiteralSynthDef name constants params paramNames ugenSpecs variants) = do putPString' name putListWLenPrefix32 putFloat32be constants putListWLenPrefix32 putFloat32be params putListWLenPrefix32 putParamName' paramNames putListWLenPrefix32 putUGenSpec' ugenSpecs putListWLenPrefix16 putVariantSpec' variants putListWLenPrefix32 :: (a -> Put) -> [a] -> Put putListWLenPrefix32 putF l = do -- TODO: maybe this should be 'Int32' to be compliant: -- (and maybe others, for the read?) putWord32be $ toEnum $ (length::[a]->Int) l mapM_ putF l putListWLenPrefix16 :: (a -> Put) -> [a] -> Put putListWLenPrefix16 putF l = do -- TODO: maybe this should be 'Int32' to be compliant: -- (and maybe others, for the read?) putWord16be $ toEnum $ (length::[a]->Int) l mapM_ putF l data ParamName = ParamName { _paramName_name :: ByteString ,_paramName_indexInParamArray :: Int32 } deriving (Show, Eq) {- a param-name is : pstring - the name of the parameter int32 - its index in the parameter array -} getParamName :: ByteString -> (ParamName, ByteString) getParamName blob = let (name, rest) = getPString blob (index, rest2) = getInt32 rest in (ParamName name index, rest2) getParamName' :: Get ParamName getParamName' = do name <- getPString' index <- getInt32be pure $ ParamName name index encodeParamName :: ParamName -> ByteString encodeParamName (ParamName name index) = encodePString name <> BSL.toStrict (encode index) putParamName' :: ParamName -> Put putParamName' (ParamName name index) = do putPString' name putInt32be index data UGenSpec = UGenSpec { _uGenSpec_name :: ByteString ,_uGenSpec_calcRate :: CalculationRate ,_uGenSpec_inputs :: [InputSpec] ,_uGenSpec_outputs :: [OutputSpec] ,_uGenSpec_specialIndex :: Int16 } deriving (Show, Eq) {- a ugen-spec is : pstring - the name of the SC unit generator class int8 - calculation rate int32 - number of inputs (I) int32 - number of outputs (O) int16 - special index [ input-spec ] * I [ output-spec ] * O -} getUGenSpec :: ByteString -> (UGenSpec, {- rest: -} ByteString) getUGenSpec blob = let (name, rest) = getPString blob (calcRate :: CalculationRate, rest2) = first ((toEnum) . (fromEnum :: Int8 -> Int) . decode . BSL.fromStrict) $ BS.splitAt 1 rest (numInputs :: Int32, rest3) = getInt32 rest2 (numOutputs :: Int32, rest4) = getInt32 rest3 (specialIndex, rest5) = getInt16 rest4 (inputSpecs, rest6) = getNWith numInputs getInputSpec rest5 (outputSpecs, rest7) = getNWith numOutputs getOutputSpec rest6 in (UGenSpec name calcRate inputSpecs outputSpecs specialIndex, rest7) getUGenSpec' :: Get UGenSpec getUGenSpec' = do name <- getPString' calcRate <- getCalcRate' numInputs <- fromEnum <$> getInt32be numOutputs <- fromEnum <$> getInt32be specialIndex <- getInt16be inputSpecs <- replicateM numInputs getInputSpec' outputSpecs <- replicateM numOutputs getOutputSpec' pure $ UGenSpec name calcRate inputSpecs outputSpecs specialIndex encodeUGenSpec :: UGenSpec -> ByteString encodeUGenSpec (UGenSpec name calcRate inputSpecs outputSpecs specialIndex) = mconcat [ encodePString name ,BSL.toStrict $ encode $ (toEnum (fromEnum calcRate) :: Int8) ,BSL.toStrict $ encode $ (toEnum ((length::[a]->Int) inputSpecs) :: Int32) ,BSL.toStrict $ encode $ (toEnum ((length::[a]->Int) outputSpecs) :: Int32) ,BSL.toStrict $ encode specialIndex ,mconcat $ map encodeInputSpec inputSpecs ,mconcat $ map encodeOutputSpec outputSpecs ] putUGenSpec' :: UGenSpec -> Put putUGenSpec' (UGenSpec name calcRate inputSpecs outputSpecs specialIndex) = do putPString' name putCalcRate' calcRate putInt32be $ toEnum $ (length::[a]->Int) inputSpecs putInt32be $ toEnum $ (length::[a]->Int) outputSpecs putInt16be specialIndex mapM_ putInputSpec' inputSpecs mapM_ putOutputSpec' outputSpecs -- TODO: -- | These record fields are deprecated as it's a variant -- don't use! data InputSpec = InputSpec_UGen { _inputSpec_uGen_index :: Int32 ,_inputSpec_uGen_outputIndex :: Int32 } | InputSpec_Constant { _inputSpec_constant_index :: Int32 } deriving (Show, Read, Eq) {- an input-spec is : int32 - index of unit generator or -1 for a constant if (unit generator index == -1) : int32 - index of constant else : int32 - index of unit generator output -} getInputSpec :: ByteString -> (InputSpec, ByteString) getInputSpec blob = let (stuffForThis, rest) = BS.splitAt 8 blob (one :: Int32, two :: Int32) = (\(a,b) -> ((decode . BSL.fromStrict) a, (decode . BSL.fromStrict) b)) $ BS.splitAt 4 stuffForThis spec = case one of -1 -> InputSpec_Constant two n | n > -1 -> InputSpec_UGen one two _ -> error "bad number" in (spec, rest) getInputSpec' :: Get InputSpec getInputSpec' = do index <- getInt32be outputOrConstantIndex <- getInt32be pure $ case index of -1 -> InputSpec_Constant outputOrConstantIndex n | n > -1 -> InputSpec_UGen index outputOrConstantIndex _ -> error "bad number" -- TODO encodeInputSpec :: InputSpec -> ByteString encodeInputSpec inputSpec = mconcat $ map (BSL.toStrict . encode) $ encodeInputSpec' inputSpec where encodeInputSpec' :: InputSpec -> [Int32] encodeInputSpec' (InputSpec_Constant i) = [ (-1), i ] encodeInputSpec' (InputSpec_UGen i oI) = [ i, oI ] putInputSpec' :: InputSpec -> Put putInputSpec' = \case InputSpec_Constant constant -> do putInt32be (-1) putInt32be constant InputSpec_UGen index constant -> do putInt32be index putInt32be constant data OutputSpec = OutputSpec { _outputSpec_calcRate :: CalculationRate } deriving (Show, Read, Eq) {- an output-spec is : int8 - calculation rate -} getOutputSpec :: ByteString -> (OutputSpec, ByteString) getOutputSpec blob = first (OutputSpec . toEnum . (fromEnum :: Int8 -> Int) . decode . BSL.fromStrict) $ BS.splitAt 1 blob getOutputSpec' :: Get OutputSpec getOutputSpec' = do OutputSpec <$> getCalcRate' encodeOutputSpec :: OutputSpec -> ByteString encodeOutputSpec (OutputSpec calcRate) = BSL.toStrict $ encode $ (toEnum (fromEnum calcRate) :: Int8) putOutputSpec' :: OutputSpec -> Put putOutputSpec' (OutputSpec calcRate) = putCalcRate' calcRate getCalcRate' :: Get CalculationRate getCalcRate' = (toEnum . fromEnum) <$> getInt8 putCalcRate' :: CalculationRate -> Put putCalcRate' calcRate = putWord8 $ toEnum $ fromEnum calcRate data VariantSpec = VariantSpec { _variantSpec_name :: ByteString ,_variantSpec_initialParamVals :: [Float] -- float32 } deriving (Show, Eq) {- a variant-spec is : pstring - the name of the variant [float32] * P - variant initial parameter values -} getVariantSpec :: Int32 -> ByteString -> (VariantSpec, ByteString) getVariantSpec numParams blob = let (name, rest) = getPString blob (initialParamVals :: [Float], rest2) = first (map (decode . BSL.fromStrict)) $ getN4ByteBlocks numParams rest in (VariantSpec name initialParamVals, rest2) getVariantSpec' :: Int32 -> Get VariantSpec getVariantSpec' numParams = do name <- getPString' -- numVariants <- getInt16be -- NOT numVariants floats <- replicateM (fromEnum numParams) $ do -- make sure this is right!: -- wordToFloat <$> getWord32be getFloat32be pure $ VariantSpec name floats encodeVariantSpec :: VariantSpec -> ByteString encodeVariantSpec (VariantSpec name initialParamVals) = encodePString name <> mconcat (map (BSL.toStrict . encode . floatToWord) initialParamVals) putVariantSpec' :: VariantSpec -> Put putVariantSpec' (VariantSpec name initialParamVals) = do putPString' name mapM_ putFloat32be initialParamVals --- helpers: getPString :: ByteString -> (ByteString, {- rest: -} ByteString) getPString blob = first (BS.drop 1) $ BS.splitAt (fromEnum (BS.head blob) + 1) blob -- | A \"pascal format string\" getPString' :: Get ByteString getPString' = do byteLen <- getWord8 -- why's this not 'getWord8be'? - todo getByteString $ fromEnum byteLen encodePString :: ByteString -> ByteString encodePString s = toEnum (BS.length s) `BS.cons` s putPString' :: ByteString -> Put putPString' bs = do putWord8 $ toEnum (BS.length bs) putByteString bs -- Pretty sure this is exactly replicateM: getNWith :: (Integral i) => i -> (ByteString -> (a, ByteString)) -> ByteString -> ([a], ByteString) getNWith 0 _ rest = ([], rest) getNWith n f rest = let (head1, rest2) = f rest (head2, rest3) = getNWith (n - 1) f rest2 in (head1 : head2, rest3) getInt32 :: ByteString -> (Int32, ByteString) getInt32 blob = first (decode . BSL.fromStrict) $ BS.splitAt 4 blob getInt16 :: ByteString -> (Int16, ByteString) getInt16 blob = first (decode . BSL.fromStrict) $ BS.splitAt 2 blob getN4ByteBlocks :: Int32 -> ByteString -> ([ByteString], ByteString) getN4ByteBlocks numBlocks blob = first (map BS8.pack . chunksOf 4 . BS8.unpack) $ BS.splitAt (4 * fromEnum numBlocks) blob uOpToSpecialI :: UnaryOp -> Int16 uOpToSpecialI uop = toEnum . fromEnum $ uop specialIToUOp :: Int16 -> UnaryOp specialIToUOp specialI = toEnum . fromEnum $ specialI biOpToSpecialI :: BinaryOp -> Int16 biOpToSpecialI theBiOp = toEnum . fromEnum $ theBiOp specialIToBiOp :: Int16 -> BinaryOp specialIToBiOp theBiOp = toEnum . fromEnum $ theBiOp sdLitPretty :: LiteralSynthDef -> String sdLitPretty synthDef = mconcat [ "Constants: ", show $ _synthDefConstants synthDef , "\n" -- , show $ zip (_synthDefParameters synthDef) (_synthDefParamNames synthDef) , show $ map (\(ParamName a i)->(a, _synthDefParameters synthDef !! fromIntegral i)) (_synthDefParamNames synthDef) , "\n" , mconcat$ (flip map) (zip [0::Int ..] (_synthDefUGens synthDef)) $ \(i,ug) -> mconcat [ show i <> " " <> show (_uGenSpec_name ug) <> " - " <> show (_uGenSpec_calcRate ug) <> " (" <> show ((length::[a]->Int) $ _uGenSpec_outputs ug) <> " outputs)" ,"\n" ,mconcat $ map ((<>"\n") . (" "<>) . showInputSpec) $ _uGenSpec_inputs ug ,case UTF8.toString (_uGenSpec_name ug) of "UnaryOpUGen" -> mconcat [ " " , show ( specialIToUOp (_uGenSpec_specialIndex ug)) , "\n" ] "BinaryOpUGen" -> " " <> show (specialIToBiOp (_uGenSpec_specialIndex ug)) <> "\n" _ -> "" ] ] where showInputSpec :: InputSpec -> String showInputSpec (InputSpec_Constant constantIndex) = mconcat [ "Constant: " ,show $ (_synthDefConstants synthDef) !! fromEnum constantIndex ," (index ", show constantIndex, ")" ] showInputSpec (InputSpec_UGen ugNum ugOut) = "UGOut: "<>show (ugNum, ugOut) -- showInputSpec x = show x