hsc3-0.18: Sound/SC3/Server/Graphdef.hs
-- | Binary 'Graph Definition' as understood by @scsynth@.
-- There are both encoders and decoders.
module Sound.SC3.Server.Graphdef where
import Control.Monad {- base -}
import qualified Data.ByteString.Lazy as L {- bytestring -}
import Data.List {- base -}
import qualified Safe {- safe -}
import System.FilePath {- filepath -}
import System.IO {- base -}
import qualified Sound.OSC.Coding.Byte as Byte {- hosc -}
import qualified Sound.OSC.Coding.Cast as Cast {- hosc -}
import qualified Sound.OSC.Datum as Datum {- hosc -}
-- * Type
-- | Names are ASCII strings.
type Name = Datum.ASCII
-- | Controls are a name and a ugen-index.
type Control = (Name,Int)
-- | Constants are floating point.
type Sample = Double
-- | Inputs are a ugen-index and a port-index.
-- If the ugen-index is -1 it indicates a constant.
data Input = Input Int Int deriving (Eq,Show)
-- | Read ugen-index of input, else Nothing.
input_ugen_ix :: Input -> Maybe Int
input_ugen_ix (Input u p) = if p == -1 then Nothing else Just u
-- | Rates are encoded as integers (IR = 0, KR = 1, AR = 2, DR = 3).
type Rate = Int
-- | Outputs each indicate a Rate.
type Output = Rate
-- | Secondary (special) index, used by operator UGens to select operation.
type Special = Int
-- | Unit generator type.
type UGen = (Name,Rate,[Input],[Output],Special)
-- | 'UGen' 'Rate'.
ugen_rate :: UGen -> Rate
ugen_rate (_,r,_,_,_) = r
-- | 'UGen' 'Input's.
ugen_inputs :: UGen -> [Input]
ugen_inputs (_,_,i,_,_) = i
-- | 'UGen' 'Output's.
ugen_outputs :: UGen -> [Output]
ugen_outputs (_,_,_,o,_) = o
-- | Predicate to examine Ugen name and decide if it is a control.
ugen_is_control :: UGen -> Bool
ugen_is_control (nm,_,_,_,_) =
Datum.ascii_to_string nm `elem` ["Control","LagControl","TrigControl"]
-- | Input is a UGen and the UGen is a control.
input_is_control :: Graphdef -> Input -> Bool
input_is_control g (Input u _) =
if u == -1
then False
else ugen_is_control (graphdef_ugen g u)
-- | Graph definition type.
data Graphdef = Graphdef {graphdef_name :: Name
,graphdef_constants :: [Sample]
,graphdef_controls :: [(Control,Sample)]
,graphdef_ugens :: [UGen]}
deriving (Eq,Show)
-- | Lookup UGen by index.
graphdef_ugen :: Graphdef -> Int -> UGen
graphdef_ugen g = Safe.atNote "graphdef_ugen" (graphdef_ugens g)
-- | Lookup Control and default value by index.
graphdef_control :: Graphdef -> Int -> (Control,Sample)
graphdef_control g = Safe.atNote "graphdef_controls" (graphdef_controls g)
-- | nid of constant.
graphdef_constant_nid :: Graphdef -> Int -> Int
graphdef_constant_nid _ = id
-- | nid of control.
graphdef_control_nid :: Graphdef -> Int -> Int
graphdef_control_nid g = (+) (length (graphdef_constants g))
-- | nid of UGen.
graphdef_ugen_nid :: Graphdef -> Int -> Int
graphdef_ugen_nid g n = graphdef_control_nid g 0 + length (graphdef_controls g) + n
-- * Read (version 0 or 2).
-- | Read a 'Sample'.
read_sample :: Handle -> IO Sample
read_sample = fmap realToFrac . Byte.read_f32
-- | Read a 'Control'.
read_control :: (Handle -> IO Int) -> Handle -> IO Control
read_control read_i h = do
nm <- Byte.read_pstr h
ix <- read_i h
return (nm,ix)
-- | Read an 'Input'.
read_input :: (Handle -> IO Int) -> Handle -> IO Input
read_input read_i h = do
u <- read_i h
p <- read_i h
return (Input u p)
-- | Read an 'output'.
read_output :: Handle -> IO Output
read_output = Byte.read_i8
-- | Read a 'UGen'.
read_ugen :: (Handle -> IO Int) -> Handle -> IO UGen
read_ugen read_i h = do
name <- Byte.read_pstr h
rate <- Byte.read_i8 h
number_of_inputs <- read_i h
number_of_outputs <- read_i h
special <- Byte.read_i16 h
inputs <- replicateM number_of_inputs (read_input read_i h)
outputs <- replicateM number_of_outputs (read_output h)
return (name
,rate
,inputs
,outputs
,special)
-- | Read a 'Graphdef'. Ignores variants.
read_graphdef :: Handle -> IO Graphdef
read_graphdef h = do
magic <- fmap Byte.decode_ascii (L.hGet h 4)
version <- Byte.read_i32 h
let read_i =
case version of
0 -> Byte.read_i16
2 -> Byte.read_i32
_ -> error ("read_graphdef: version not at {zero | two}: " ++ show version)
number_of_definitions <- Byte.read_i16 h
when (magic /= Datum.ascii "SCgf")
(error "read_graphdef: illegal magic string")
when (number_of_definitions /= 1)
(error "read_graphdef: non unary graphdef file")
name <- Byte.read_pstr h
number_of_constants <- read_i h
constants <- replicateM number_of_constants (read_sample h)
number_of_control_defaults <- read_i h
control_defaults <- replicateM number_of_control_defaults (read_sample h)
number_of_controls <- read_i h
controls <- replicateM number_of_controls (read_control read_i h)
number_of_ugens <- read_i h
ugens <- replicateM number_of_ugens (read_ugen read_i h)
return (Graphdef name
constants
(zip controls control_defaults)
ugens)
{- | Read Graphdef from .scsyndef file.
> dir = "/home/rohan/sw/rsc3-disassembler/scsyndef/"
> pp nm = read_graphdef_file (dir ++ nm) >>= putStrLn . graphdef_stat
> pp "simple.scsyndef"
> pp "with-ctl.scsyndef"
> pp "mce.scsyndef"
> pp "mrg.scsyndef"
-}
read_graphdef_file :: FilePath -> IO Graphdef
read_graphdef_file nm = do
h <- openFile nm ReadMode
g <- read_graphdef h
hClose h
return g
-- | Read scsyndef file and run 'graphdef_stat'.
scsyndef_stat :: FilePath -> IO String
scsyndef_stat fn = do
g <- read_graphdef_file fn
return (graphdef_stat g)
-- * Encode (version zero)
-- | Pascal (length prefixed) encoding of string.
encode_pstr :: Name -> L.ByteString
encode_pstr = L.pack . Cast.str_pstr . Datum.ascii_to_string
-- | Byte-encode 'Input'.
encode_input :: Input -> L.ByteString
encode_input (Input u p) = L.append (Byte.encode_i16 u) (Byte.encode_i16 p)
-- | Byte-encode 'Control'.
encode_control :: Control -> L.ByteString
encode_control (nm,k) = L.concat [encode_pstr nm,Byte.encode_i16 k]
-- | Byte-encode 'UGen'.
encode_ugen :: UGen -> L.ByteString
encode_ugen (nm,r,i,o,s) =
L.concat [encode_pstr nm
,Byte.encode_i8 r
,Byte.encode_i16 (length i)
,Byte.encode_i16 (length o)
,Byte.encode_i16 s
,L.concat (map encode_input i)
,L.concat (map Byte.encode_i8 o)]
-- | Encode 'Sample' as 32-bit IEEE float.
encode_sample :: Sample -> L.ByteString
encode_sample = Byte.encode_f32 . realToFrac
-- | Encode 'Graphdef'.
encode_graphdef :: Graphdef -> L.ByteString
encode_graphdef (Graphdef nm cs ks us) =
let (ks_ctl,ks_def) = unzip ks
in L.concat [Byte.encode_ascii (Datum.ascii "SCgf")
,Byte.encode_i32 0 -- version
,Byte.encode_i16 1 -- number of graphs
,encode_pstr nm
,Byte.encode_i16 (length cs)
,L.concat (map encode_sample cs)
,Byte.encode_i16 (length ks_def)
,L.concat (map encode_sample ks_def)
,Byte.encode_i16 (length ks_ctl)
,L.concat (map encode_control ks_ctl)
,Byte.encode_i16 (length us)
,L.concat (map encode_ugen us)]
-- * IO
-- | Write 'Graphdef' to indicated file.
graphdefWrite :: FilePath -> Graphdef -> IO ()
graphdefWrite fn = L.writeFile fn . encode_graphdef
-- | Write 'Graphdef' to indicated directory. The filename is the
-- 'graphdef_name' with the appropriate extension (@scsyndef@).
graphdefWrite_dir :: FilePath -> Graphdef -> IO ()
graphdefWrite_dir dir s =
let fn = dir </> Datum.ascii_to_string (graphdef_name s) <.> "scsyndef"
in graphdefWrite fn s
-- * Stat
-- | Simple statistics printer for 'Graphdef'.
graphdef_stat :: Graphdef -> String
graphdef_stat (Graphdef nm cs ks us) =
let u_nm (sc3_nm,_,_,_,_) = Datum.ascii_to_string sc3_nm
f g = let h (x:xs) = (x,length (x:xs))
h [] = error "graphdef_stat"
in show . map h . group . sort . map g
sq pp_f = intercalate "," (pp_f (map u_nm us))
in unlines ["name : " ++ show nm
,"number of constants : " ++ show (length cs)
,"number of controls : " ++ show (length ks)
,"number of unit generators : " ++ show (length us)
,"unit generator rates : " ++ f ugen_rate us
,"unit generator set : " ++ sq (nub . sort)
,"unit generator sequence : " ++ sq id]