-- | Basic user interaction with the scsynth server.
module Sound.SC3.Server.Play (stop,reset,send,async
                             ,withSC3
                             ,Audible(..)
                             ,perform) where

import Sound.OpenSoundControl
import Sound.SC3.Server.Command
import Sound.SC3.Server.Synthdef
import Sound.SC3.UGen.UGen

-- | Free all nodes ('g_freeAll') at group @1@.
stop :: Transport t => t -> IO ()
stop fd = send fd (g_freeAll [1])

-- | Send an 'OSC' message and wait for a @\/done@ reply.
async :: Transport t => t -> OSC -> IO OSC
async fd m = send fd m >> wait fd "/done"

-- | Free all nodes ('g_freeAll') at group @0@ and re-create groups
-- @1@ and @2@.
reset :: Transport t => t -> IO ()
reset fd = do
  send fd (g_freeAll [0])
  send fd (g_new [(1,AddToTail,0),(2,AddToTail,0)])

-- | Bracket @SC3@ communication.
withSC3 :: (UDP -> IO a) -> IO a
withSC3 = withTransport (openUDP "127.0.0.1" 57110)

-- | Send 'd_recv' and 's_new' messages to scsynth.
playSynthdef :: Transport t => t -> Synthdef -> IO ()
playSynthdef fd s = do
  _ <- async fd (d_recv s)
  send fd (s_new (synthdefName s) (-1) AddToTail 1 [])

-- | Send an /anonymous/ instrument definition using 'playSynthdef'.
playUGen :: Transport t => t -> UGen -> IO ()
playUGen fd = playSynthdef fd . synthdef "Anonymous"

-- | Class for values that can be encoded and send to @scsynth@ for
-- audition.
class Audible e where
    play :: Transport t => t -> e -> IO ()
    audition :: e -> IO ()
    audition e = withSC3 (`play` e)

instance Audible Synthdef where
    play = playSynthdef

instance Audible UGen where
    play = playUGen

-- | Wait ('pauseThreadUntil') until bundle is due to be sent relative
-- to initial 'UTCr' time, then send each message, asynchronously if
-- required.
run_bundle :: Transport t => t -> Double -> OSC -> IO ()
run_bundle fd i o =
    let wr m = if isAsync m
               then async fd m >> return ()
               else send fd m
    in case o of
         Bundle (NTPr t) x' -> do
             pauseThreadUntil (i + t)
             mapM_ wr x'
         _ -> error "run_bundle: non bundle or non-NTPr bundle"

-- | Perform an 'OSC' score (as would be rendered by 'writeNRT').  In
-- particular note that: (1) all 'OSC' must be 'Bundle's and (2)
-- timestamps /must/ be in 'NTPr' form.
perform :: [OSC] -> IO ()
perform s = do
  let f i fd = run_bundle fd i
  withSC3 (\fd -> utcr >>= \i -> mapM_ (f i fd) s)