packages feed

musicw-0.3.7: src/Sound/MusicW/Synth.hs

module Sound.MusicW.Synth (
  Synth(..),
  playSynth,
  playSynthNow,
  nodeRefToNode,
  synthDefToSynth,
  synthDefToSynth_,
  startSynth,
  startSynthNow,
  stopSynth,
  stopSynthNow,
  disconnectSynth,
  restartSynth
  ) where

import Data.Foldable (find)
import Control.Monad (void)
import Control.Monad.IO.Class

import Sound.MusicW.AudioContext
import Sound.MusicW.Node
import Sound.MusicW.SynthDef

data Synth m = Synth {
  cachedDestination :: Node,
  spec :: SynthSpec m,
  nodes :: [Node]
  --audioBuffers :: Map Int AudioBuffer, -- TODO restarting a AudioBufferSourceNode needs to replace the buffers
  } deriving (Show)

playSynth :: AudioIO m => Node -> AudioTime -> SynthDef m a -> m (a, Synth m)
playSynth dest t x = do
  (a,spec) <- runSynthDef x
  s <- synthSpecToSynth dest spec
  startSynth t s
  return (a,s)

playSynthNow :: AudioIO m => Node -> SynthDef m a -> m (a, Synth m)
playSynthNow dest x = do
  (a,spec) <- runSynthDef x
  s <- synthSpecToSynth dest spec
  startSynthNow s
  return (a,s)

nodeRefToNode :: AudioIO m => NodeRef -> Synth m -> m Node
nodeRefToNode (NodeRef i (_,_)) s = return $ (nodes s)!!i
nodeRefToNode (ParamRef i pType) s = createParameter ((nodes s)!!i) pType
nodeRefToNode DestinationRef s = return $ cachedDestination s

synthDefToSynth :: AudioIO m => Node -> SynthDef m a -> m (Synth m)
synthDefToSynth dest x = execSynthDef x >>= synthSpecToSynth dest

synthDefToSynth_ :: AudioIO m => SynthDef m a -> m (Synth m)
synthDefToSynth_ x = createDestination >>= (flip synthDefToSynth) x

synthSpecToSynth :: AudioIO m => Node -> SynthSpec m -> m (Synth m)
synthSpecToSynth dest x = do
  ns <- sequence $ nodeBuilders x
  mapM_ (uncurry (makeConnections dest ns)) $ connections x
  disconnectOnStop ns
  return $ Synth { cachedDestination = dest, spec = x, nodes = ns }


makeConnections :: AudioIO m => Node -> [Node] -> NodeRef -> NodeRef -> m ()

-- connections to (deferred) destination
makeConnections dest ns (NodeRef from _) DestinationRef = connectNodes (ns!!from) dest
makeConnections dest ns (NodeOutputRef from fromIndex) DestinationRef = connectNodes'' (ns!!from) fromIndex dest
makeConnections dest ns (ExternalNodeRef from _) DestinationRef = connectNodes from dest
makeConnections dest ns (ExternalNodeOutputRef from fromIndex) DestinationRef = connectNodes'' from fromIndex dest

-- connections to NodeRef
makeConnections _ ns (NodeRef from _) (NodeRef to _) = connectNodes (ns!!from) (ns!!to)
makeConnections _ ns (NodeOutputRef from fromIndex) (NodeRef to _) = connectNodes'' (ns!!from) fromIndex (ns!!to)
makeConnections _ ns (ExternalNodeRef from _) (NodeRef to _) = connectNodes from (ns!!to)
makeConnections _ ns (ExternalNodeOutputRef from fromIndex) (NodeRef to _) = connectNodes'' from fromIndex (ns!!to)

-- connections to ExternalNodeRef
makeConnections _ ns (NodeRef from _) (ExternalNodeRef to _) = connectNodes (ns!!from) to
makeConnections _ ns (NodeOutputRef from fromIndex) (ExternalNodeRef to _) = connectNodes'' (ns!!from) fromIndex to
makeConnections _ ns (ExternalNodeRef from _) (ExternalNodeRef to _) = connectNodes from to
makeConnections _ ns (ExternalNodeOutputRef from fromIndex) (ExternalNodeRef to _) = connectNodes'' from fromIndex to

-- connections to ParamRef
makeConnections _ ns (NodeRef from _) (ParamRef to pType) = createParameter (ns!!to) pType >>= connectNodes (ns!!from)
makeConnections _ ns (NodeOutputRef fromNode fromChannel) (ParamRef to pType) = createParameter (ns!!to) pType >>= connectNodes'' (ns!!fromNode) fromChannel
makeConnections _ ns (ExternalNodeRef from _) (ParamRef to pType) = createParameter (ns!!to) pType >>= connectNodes from
makeConnections _ ns (ExternalNodeOutputRef from fromIndex) (ParamRef to pType) = createParameter (ns!!to) pType >>= connectNodes'' from fromIndex

-- connections to NodeInputRef (note that connections from NodeRef/ExternalNodeRef not supported)
makeConnections _ ns (NodeOutputRef from fromIndex) (NodeInputRef to toIndex) = connectNodes' (ns!!from) fromIndex (ns!!to) toIndex
makeConnections _ ns (ExternalNodeOutputRef from fromIndex) (NodeInputRef to toIndex) = connectNodes' from fromIndex (ns!!to) toIndex

-- connections to ExternalNodeInputRef (note that connections from NodeRef/ExternalNodeRef not supported)
makeConnections _ ns (NodeOutputRef from fromIndex) (ExternalNodeInputRef to toIndex) = connectNodes' (ns!!from) fromIndex to toIndex
makeConnections _ ns (ExternalNodeOutputRef from fromIndex) (ExternalNodeInputRef to toIndex) = connectNodes' from fromIndex to toIndex


-- *** Note: there is probably a bug connected to the definition of disconnectOnStop below:
-- it attaches an onended callback to a single source node from a list of nodes, which doesn't
-- properly cover the case where there are multiple source nodes that end at different times.

disconnectOnStop :: (Foldable t, AudioIO m) => t Node -> m ()
disconnectOnStop ns = maybe (return ()) f $ find isSourceNode ns
  where f x = liftIO $ onended x $ \_ -> mapM_ disconnectAll ns

startSynth :: AudioIO m => AudioTime -> Synth m -> m ()
startSynth t0 s = do
  mapM_ (startNode t0) $ nodes s
  maybe (return ()) (\t -> stopSynth (t0+t) s) $ deletionTime (spec s)
  mapM_ (scheduleChange (nodes s) t0) $ changes (spec s)

scheduleChange :: AudioIO m => [Node] -> AudioTime -> Change -> m ()
scheduleChange ns t0 (SetValue (ParamRef i pType) v t) = void $ setValueAtTime (ns!!i) pType v (t0+t)
scheduleChange ns t0 (LinearRampToValue (ParamRef i pType) v t) = void $ linearRampToValueAtTime (ns!!i) pType v (t0+t)
scheduleChange ns t0 (ExponentialRampToValue (ParamRef i pType) v t) = void $ exponentialRampToValueAtTime (ns!!i) pType v (t0+t)
scheduleChange ns t0 (CurveToValue (ParamRef i pType) curve t dur) = void $ setValueCurveAtTime (ns!!i) pType curve (t0+t) dur
scheduleChange _ _ _ = error "scheduleChange targeted non-ParamRef node"

startSynthNow :: AudioIO m => Synth m -> m ()
startSynthNow s = do
  t <- audioTime
  startSynth (t + 0.050) s

stopSynth :: MonadIO m => AudioTime -> Synth m -> m ()
stopSynth t s = mapM_ (stopNode t) $ nodes s

stopSynthNow :: MonadIO m => Synth m -> IO ()
stopSynthNow s = mapM_ stopNodeNow $ nodes s

disconnectSynth :: MonadIO m => Synth m -> IO ()
disconnectSynth s = mapM_ disconnectAll $ nodes s

restartSynth :: AudioIO m => AudioTime -> Synth m -> m ()
restartSynth t s = do
  stopSynth t s
  s' <- synthSpecToSynth (cachedDestination s) (spec s)
  startSynth t s'