content: {-# LANGUAGE Arrows #-}
  {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE DeriveDataTypeable #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE StandaloneDeriving #-}
  
  {- | Support for [@vivid@](https://hackage.haskell.org/package/vivid),
  a Haskell library for [SuperCollider](https://supercollider.github.io/).
  
  With this module, you can create cells corresponding to synthesizers.
  
  The synthesizers automatically start and stop on reload.
  -}
  module LiveCoding.Vivid where
  
  -- base
  import Data.Foldable (traverse_)
  import GHC.TypeLits (KnownSymbol)
  
  -- vivid
  import Vivid
  
  -- essence-of-live-coding
  
  import LiveCoding
  import LiveCoding.Handle
  
  {- | Whether a synthesizer should currently be running or not.
  
  Typically, you will either statically supply the value and change it in the code to start and stop the synth,
  or you can connect another cell to it.
  -}
  data SynthState
    = Started
    | Stopped
    deriving (Eq)
  
  {- | A 'ParametrisedHandle' corresponding to one @vivid@/@SuperCollider@ synthesizer.
  
  Usually, you will want to use 'liveSynth' instead, it is easier to handle.
  -}
  vividHandleParametrised ::
    (VividAction m, Eq params, VarList params, Subset (InnerVars params) args, Elem "gate" args) =>
    ParametrisedHandle (params, SynthDef args, SynthState) m (Maybe (Synth args))
  vividHandleParametrised = ParametrisedHandle {..}
    where
      createParametrised (params, synthDef, Started) = Just <$> synth synthDef params
      createParametrised (params, synthDef, Stopped) = defineSD synthDef >> pure Nothing
  
      destroyParametrised _ synthMaybe = traverse_ release synthMaybe
  
      -- Only the synth parameters changed and it's still running.
      -- So simply set new parameters without stopping it.
      changeParametrised (paramsOld, synthDefOld, Started) (paramsNew, synthDefNew, Started) (Just synth)
        | paramsOld /= paramsNew && synthDefOld == synthDefNew = do
            set synth paramsNew
            return $ Just synth
      -- Synthdef or start/stop state changed, need to release and reinitialise
      changeParametrised old new synth = defaultChange createParametrised destroyParametrised old new synth
  
  deriving instance Data SynthState
  deriving instance (KnownSymbol a) => Data (I a)
  
  {- | Create a synthesizer.
  
  When you add 'liveSynth' to your live program,
  it will be started upon reload immediately.
  
  Feed the definition of the synthesizer and its current intended state to this cell.
  The input has the form @(params, sdbody, synthState)@.
  
  * A change in @params@ will reload the synthesizer quickly,
    unless the types of the parameters change.
  * A change in the @sdbody :: 'SDBody' ...@ or the _types_ of the @params@ will 'release' the synthesizer and start a new one.
  * The input @synthState :: 'SynthState'@ represent whether the synthesizer should currently be running or not.
    Changes in it quickly start or stop it.
    * When it is started, @'Just' synth@ is returned, where @synth@ represents the running synthesizer.
    * When it is stopped, 'Nothing' is returned.
  
  You have to use 'envGate' in your @sdbody@,
  or another way of gating your output signals
  in order to ensure release of the synths without clipping.
  
  For an example, have a look at the source code of 'sine'.
  -}
  liveSynth ::
    ( VividAction m
    , Eq params
    , Typeable params
    , VarList params
    , Typeable (InnerVars params)
    , Subset (InnerVars params) (InnerVars params)
    , Elem "gate" (InnerVars params)
    , Data params
    ) =>
    Cell
      (HandlingStateT m)
      (params, SDBody' (InnerVars params) [Signal], SynthState)
      (Maybe (Synth (InnerVars params)))
  liveSynth = proc (params, sdbody, synthstate) -> do
    paramsFirstValue <- holdFirst -< params
    handlingParametrised vividHandleParametrised -< (params, sd paramsFirstValue sdbody, synthstate)
  
  -- | Example sine synthesizer that creates a sine wave at the given input frequency.
  sine :: (VividAction m) => Cell (HandlingStateT m) Float ()
  sine = proc frequency -> do
    liveSynth
      -<
        (
          ( 1 :: I "gate"
          , 2 :: I "fadeSecs"
          , I frequency :: I "freq"
          )
        , out (0 :: Int) [envGate ~* sinOsc (freq_ (V :: V "freq"))]
        , Started
        )
    returnA -< ()
  