-- | The unit-generator graph structure implemented by the
--   SuperCollider synthesis server.
module Sound.SC3.Server.Synthdef where

import qualified Data.ByteString.Lazy as L {- bytestring -}
import Data.Default {- data-default -}
import Data.List {- base -}
import Data.Maybe {- base -}
import System.FilePath {- filepath -}

import qualified Sound.SC3.Server.Graphdef as G
import qualified Sound.SC3.Server.Graphdef.Graph as G
import Sound.SC3.UGen.Graph
import Sound.SC3.UGen.Help.Graph
import Sound.SC3.UGen.Type
import Sound.SC3.UGen.UGen

-- | A named unit generator graph.
data Synthdef = Synthdef {synthdefName :: String
                         ,synthdefUGen :: UGen}
                deriving (Eq,Show)

instance Default Synthdef where def = defaultSynthdef

-- | Lift a 'UGen' graph into a 'Synthdef'.
synthdef :: String -> UGen -> Synthdef
synthdef = Synthdef

-- | The SC3 /default/ instrument 'Synthdef', see
-- 'default_ugen_graph'.
--
-- > import Sound.OSC {- hosc -}
-- > import Sound.SC3 {- hsc3 -}
-- > withSC3 (sendMessage (d_recv defaultSynthdef))
-- > audition defaultSynthdef
defaultSynthdef :: Synthdef
defaultSynthdef = synthdef "default" default_ugen_graph

-- | The SC3 /default/ sample (buffer) playback instrument 'Synthdef',
-- see 'default_sampler_ugen_graph'.
--
-- > withSC3 (sendMessage (d_recv (defaultSampler False)))
-- > audition (defaultSampler False)
defaultSampler :: Bool -> Synthdef
defaultSampler use_gate =
    let nm = "default-sampler-" ++ if use_gate then "gate" else "fixed"
    in synthdef nm (default_sampler_ugen_graph use_gate)

-- | 'ugen_to_graph' of 'synthdefUGen'.
synthdefGraph :: Synthdef -> Graph
synthdefGraph = ugen_to_graph . synthdefUGen

-- | Parameter names at 'Synthdef'.
--
-- > synthdefParam defaultSynthdef == ["amp","pan","gate","freq","out"]
synthdefParam :: Synthdef -> [String]
synthdefParam = map node_k_name . controls . synthdefGraph

-- | Find the indices of the named UGen at 'Graph'.  The index is
-- required when using 'Sound.SC3.Server.Command.u_cmd'.
ugenIndices :: String -> Graph -> [Integer]
ugenIndices nm =
    let f (k,nd) =
            case nd of
              NodeU _ _ nm' _ _ _ _ -> if nm == nm' then Just k else Nothing
              _ -> Nothing
    in mapMaybe f . zip [0..] . ugens

-- | 'graph_to_graphdef' at 'Synthdef'.
synthdef_to_graphdef :: Synthdef -> G.Graphdef
synthdef_to_graphdef (Synthdef nm u) = G.graph_to_graphdef nm (ugen_to_graph u)

-- | Encode 'Synthdef' as a binary data stream.
synthdefData :: Synthdef -> L.ByteString
synthdefData = G.encode_graphdef . synthdef_to_graphdef

-- | Write 'Synthdef' to indicated directory.  The filename is the
-- 'synthdefName' with the appropriate extension (@scsyndef@).
synthdefWrite :: Synthdef -> FilePath -> IO ()
synthdefWrite s dir =
    let nm = dir </> synthdefName s <.> "scsyndef"
    in L.writeFile nm (synthdefData s)

-- | Simple statistical analysis of a unit generator graph.
graph_stat :: Graph -> [String]
graph_stat s =
    let cs = constants s
        ks = controls s
        us = ugens s
        u_nm z = ugen_user_name (node_u_name z) (node_u_special z)
        f g = let h (x:xs) = (x,length (x:xs))
                  h [] = error "graph_stat"
              in show . map h . group . sort . map g
        sq pp_f = intercalate "," (pp_f (map u_nm us))
    in ["number of constants       : " ++ show (length cs)
       ,"number of controls        : " ++ show (length ks)
       ,"control rates             : " ++ f node_k_rate ks
       ,"number of unit generators : " ++ show (length us)
       ,"unit generator rates      : " ++ f node_u_rate us
       ,"unit generator set        : " ++ sq (sort . nub)
       ,"unit generator sequence   : " ++ sq id]

synthstat' :: UGen -> [String]
synthstat' = graph_stat . ugen_to_graph

-- | 'graph_stat' of 'synth'.
synthstat :: UGen -> String
synthstat = unlines . synthstat'