{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-}
{- |
Copyright   :  (c) Henning Thielemann 2008-2009
License     :  GPL

Maintainer  :  synthesizer@henning-thielemann.de
Stability   :  provisional
Portability :  requires multi-parameter type classes (Flat)


Basic definitions for causal signal processors
which are controlled by another signal.
Additionally to "Synthesizer.Dimensional.ControlledProcess"
you can convert those processes to plain causal processes
in the case of equal audio and control rates (synchronous control).

It is sensible to bundle the functions
"computation of internal parameters" and
"running the main process",
since computation of the internal parameters
depends on the sample rate of the main process
in case of frequency control values
even though the computation of internal parameters happens
at a different sample rate.

ToDo:
 - Is it better to provide the conversion method not by a record
   but by a type class?
   The difficulty with this is,
   how to handle global parameters like the filter order?
 - Note, that parameters might be computed by different ways.
   Thus a type class with functional dependencies
   for automatic selection of input types and conversion
   will not always be flexible enough.
 - Is it possible and reasonable to hide the type parameter
   for the internal control parameter
   since the user does not need to know it?
 - The internal parameters that the converter generates
   usually depend on the sample rate of the (target) audio signal.
   However, it does not depend on the sample rate of control signal
   where it is applied to.
   How can we ensure that it is not used somewhere else?
   We could discourage access to it at all.
   But it might be sensible to define new external parameters
   in terms of existing ones.
   We could add a phantom 's' type parameter
   to internal control parameters.
   Would this do the trick? Is this convenient?
   See 'RateDep'.
-}
module Synthesizer.Dimensional.Causal.ControlledProcess where

import qualified Synthesizer.Dimensional.Sample as Sample
import Synthesizer.Dimensional.Sample (Amplitude, Displacement, )
import Synthesizer.Dimensional.Causal.Process ((<<<), )

import qualified Synthesizer.Dimensional.Process as Proc
import qualified Synthesizer.Dimensional.Rate as Rate
import qualified Synthesizer.Dimensional.Signal.Private as SigA
import qualified Synthesizer.Dimensional.Causal.Process as CausalD
import qualified Synthesizer.Dimensional.Arrow as ArrowD
import qualified Synthesizer.Dimensional.Map as MapD
import qualified Synthesizer.Dimensional.Amplitude as Amp
import qualified Synthesizer.Causal.Process       as Causal
import qualified Synthesizer.Causal.Interpolation as Interpolation
import qualified Synthesizer.Interpolation.Class as Interpol
import qualified Synthesizer.State.Signal as Sig
import qualified Number.DimensionTerm        as DN
import qualified Algebra.DimensionTerm       as Dim

-- import Synthesizer.Dimensional.Process (($:), ($#), )
-- import Synthesizer.Dimensional.RateAmplitude.Signal (($-))

-- import Number.DimensionTerm ((*&), ) -- ((&*&), (&/&))

import qualified Algebra.RealField      as RealField
-- import qualified Algebra.Field          as Field
-- import qualified Algebra.Ring           as Ring
import qualified Algebra.Additive       as Additive

import Data.Tuple.HT (swap, )
import Control.Applicative (liftA2, )

import Foreign.Storable.Newtype as Store
import Foreign.Storable (Storable(..))

import NumericPrelude.Numeric
import NumericPrelude.Base as P


{- |
This is quite analogous to Dimensional.Causal.Process
but adds the @conv@ parameter for conversion
from intuitive external parameters to internal parameters.
-}
data T conv proc = Cons {
      converter :: conv,
      processor :: proc
   }


{- |
The Functor instance allows
to define an allpass phaser as ControlledProcess,
reusing the allpass cascade provided as ControlledProcess.
It is also possible to define a lowpass filter
with resonance as ControlledProcess
based on the universal filter ControlledProcess.
-}
instance Functor (T conv) where
   fmap f proc =
      Cons (converter proc) (f $ processor proc)

{- |
@ecAmp@ is a set of physical units for the external control parameters,
@ec@ is the type for the external control parameters,
@ic@ for internal control parameters.
-}
type Converter s ec ic =
   MapD.T ec (SampleRateDep s ic)

type SampleRateDep s ic = Sample.Abstract (RateDep s ic)

newtype RateDep s ic = RateDep {unRateDep :: ic}


instance Interpol.C a ic => Interpol.C a (RateDep s ic) where
   scaleAndAccumulate =
      Interpol.makeMac RateDep unRateDep

instance Storable ic => Storable (RateDep s ic) where
   sizeOf = Store.sizeOf unRateDep
   alignment = Store.alignment unRateDep
   peek = Store.peek RateDep
   poke = Store.poke unRateDep

type Signal s ecAmp ec =
   SigA.T (Rate.Phantom s) ecAmp (Sig.T ec)

{- |
This function is intended for implementing high-level dimensional processors
from low-level processors.
It introduces the sample rate tag @s@.
-}
{-# INLINE makeConverter #-}
makeConverter ::
   (Sample.Amplitude ec -> Sample.Displacement ec -> ic) ->
   Converter s ec ic
makeConverter f =
   ArrowD.Cons $ swap . (,) Amp.Abstract . (RateDep.) . f

{-# INLINE causalFromConverter #-}
causalFromConverter ::
   Converter s ec ic ->
   CausalD.T s ec (SampleRateDep s ic)
causalFromConverter = CausalD.map


{-# INLINE joinSynchronousPlain #-}
joinSynchronousPlain ::
   T (Converter s ec ic)
     (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut) ->
   CausalD.T s (ec, sampleIn) sampleOut
joinSynchronousPlain p =
   processor p <<<
   MapD.swap <<<
   CausalD.first (causalFromConverter (converter p))

{-# INLINE joinSynchronous #-}
joinSynchronous ::
   Proc.T s u t
      (T (Converter s ec ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   Proc.T s u t (CausalD.T s (ec, sampleIn) sampleOut)
joinSynchronous cp =
   fmap joinSynchronousPlain cp


{-# INLINE joinFirstSynchronousPlain #-}
joinFirstSynchronousPlain ::
   T (Converter s ec ic, a)
     (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut) ->
   T a
     (CausalD.T s (ec, sampleIn) sampleOut)
joinFirstSynchronousPlain p =
   Cons {
      converter = snd (converter p),
      processor = joinSynchronousPlain (Cons (fst (converter p)) (processor p))
   }

{-
With this signature we deconstruct a right biased pair tree in the ampIn parameter of T
and build a left biased pair tree in the corresponding output parameter.
We could also use a pair of heterogeneous lists.
But the effect is always, that the list is reversed.
-}
{-# INLINE joinFirstSynchronous #-}
joinFirstSynchronous ::
   Proc.T s u t
      (T (Converter s ec ic, a)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   Proc.T s u t
      (T a
         (CausalD.T s (ec, sampleIn) sampleOut))
joinFirstSynchronous cp =
   fmap joinFirstSynchronousPlain cp

{-
{-# INLINE runSynchronous #-}
runSynchronous ::
   Proc.T s u t (T s (Convert ecAmp ec ic) (Amp.Abstract, ampIn) ampOut (RateDep s ic, sampIn) sampOut) ->
   Proc.T s u t (CausalD.T s (ecAmp, ampIn) ampOut (ec, sampIn) sampOut)
runSynchronous cp =
   cp >>= \p ->
      return (processor p . converter p)
-}

{-# INLINE runSynchronous1 #-}
runSynchronous1 :: (Amp.C ecAmp) =>
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp ec) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   Proc.T s u t
      (Signal s ecAmp ec -> CausalD.T s sampleIn sampleOut)
runSynchronous1 =
   fmap CausalD.applyFst . joinSynchronous


{-# INLINE runSynchronousPlain2 #-}
runSynchronousPlain2 :: (Amp.C ecAmp0, Amp.C ecAmp1) =>
   (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
      (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   (Signal s ecAmp0 ec0 ->
    Signal s ecAmp1 ec1 ->
    CausalD.T s sampleIn sampleOut)
runSynchronousPlain2 causal =
   let causalPairs =
          joinSynchronousPlain causal <<< MapD.balanceLeft
   in  \x y ->
          (causalPairs `CausalD.applyFst` x) `CausalD.applyFst` y

{-# INLINE runSynchronous2 #-}
runSynchronous2 :: (Amp.C ecAmp0, Amp.C ecAmp1) =>
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   Proc.T s u t
      (Signal s ecAmp0 ec0 ->
       Signal s ecAmp1 ec1 ->
       CausalD.T s sampleIn sampleOut)
runSynchronous2 cp =
   fmap runSynchronousPlain2 cp



{-# INLINE runAsynchronous #-}
runAsynchronous ::
   (Dim.C u, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s ec ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   SigA.T (Rate.Dimensional u t) Amp.Abstract (Sig.T (RateDep s ic)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
runAsynchronous ip cp sig =
   liftA2 (\p k ->
         CausalD.applyFst (processor p <<< MapD.swap) $
         SigA.abstractFromBody $
         Causal.applyConst
            (Interpolation.relativeConstantPad ip zero (SigA.body sig))
            k)
      cp (Proc.toFrequencyScalar (SigA.actualSampleRate sig))

{-# INLINE runAsynchronousBuffered #-}
runAsynchronousBuffered ::
   (Dim.C u, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s ec ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   SigA.T (Rate.Dimensional u t) Amp.Abstract (Sig.T (RateDep s ic)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
runAsynchronousBuffered ip cp =
   runAsynchronous ip cp .
   SigA.processBody (Sig.fromList . Sig.toList)


{-# INLINE applyConverter1 #-}
applyConverter1 :: (Amp.C ecAmp) =>
   Converter s (Sample.T ecAmp ec) ic ->
   SigA.T (Rate.Dimensional u t) ecAmp (Sig.T ec) ->
   SigA.T (Rate.Dimensional u t) Amp.Abstract (Sig.T (RateDep s ic))
applyConverter1 = MapD.apply

{-# INLINE runAsynchronous1 #-}
runAsynchronous1 ::
   (Dim.C u, Amp.C ecAmp, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp ec) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   SigA.T (Rate.Dimensional u t) ecAmp (Sig.T ec) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
runAsynchronous1 ip cp x =
   cp >>= \p ->
   runAsynchronous ip cp
      (applyConverter1 (converter p) x)

{-# INLINE processAsynchronous1 #-}
processAsynchronous1 ::
   (Dim.C u, Amp.C ecAmp, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp ec) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   DN.T (Dim.Recip u) t ->
   (forall r. Proc.T r u t (Signal r ecAmp ec)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
processAsynchronous1 ip cp rate x =
   runAsynchronous1 ip cp (SigA.render rate x)


{-# INLINE applyConverter2 #-}
applyConverter2 :: (Amp.C ecAmp0, Amp.C ecAmp1) =>
   (DN.T (Dim.Recip u) t ->
    DN.T (Dim.Recip u) t ->
    DN.T (Dim.Recip u) t) ->
   Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic ->
   SigA.T (Rate.Dimensional u t) ecAmp0 (Sig.T ec0) ->
   SigA.T (Rate.Dimensional u t) ecAmp1 (Sig.T ec1) ->
   SigA.T (Rate.Dimensional u t) Amp.Abstract (Sig.T (RateDep s ic))
applyConverter2 mergeRate f x y =
   ArrowD.apply f $
   SigA.Cons
      (Rate.Actual $ mergeRate (SigA.actualSampleRate x) (SigA.actualSampleRate y))
      (SigA.amplitude x, SigA.amplitude y)
      (Sig.zip (SigA.body x) (SigA.body y))

{- |
Using two SigP.T's as input has the disadvantage
that their rates must be compared dynamically.
It is not possible with our data structures
to use one rate for multiple signals.
We could also allow the input of a Rate.T and two Proc.T's,
since this is the form we get from the computation routines.
But this way we lose sharing.
-}
{-# INLINE runAsynchronous2 #-}
runAsynchronous2 ::
   (Dim.C u, Amp.C ecAmp0, Amp.C ecAmp1, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   SigA.T (Rate.Dimensional u t) (ecAmp0) (Sig.T ec0) ->
   SigA.T (Rate.Dimensional u t) (ecAmp1) (Sig.T ec1) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
runAsynchronous2 ip cp x y =
   cp >>= \p ->
   runAsynchronous ip cp
      (applyConverter2
          (Rate.common "ControlledProcess.runAsynchronous2")
          (converter p)
          x y)


{- |
This function will be more commonly used than 'runAsynchronous2',
but it disallows sharing of control signals.
It can be easily defined in terms of 'runAsynchronous2' and 'SigA.render',
but the implementation here does not need the check for equal sample rates.
-}
{-# INLINE processAsynchronous2 #-}
processAsynchronous2 ::
   (Dim.C u, Amp.C ecAmp0, Amp.C ecAmp1, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   DN.T (Dim.Recip u) t ->
   (forall r. Proc.T r u t (Signal r ecAmp0 ec0)) ->
   (forall r. Proc.T r u t (Signal r ecAmp1 ec1)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
processAsynchronous2 ip cp rate x y =
   let sigX = SigA.render rate x
       sigY = SigA.render rate y
   in  cp >>= \p ->
          runAsynchronous ip cp
             (applyConverter2 const (converter p) sigX sigY)


{-# INLINE processAsynchronousNaive2 #-}
processAsynchronousNaive2 ::
   (Dim.C u, Amp.C ecAmp0, Amp.C ecAmp1, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   DN.T (Dim.Recip u) t ->
   (forall r. Proc.T r u t (Signal r ecAmp0 ec0)) ->
   (forall r. Proc.T r u t (Signal r ecAmp1 ec1)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
processAsynchronousNaive2 ip cp rate x y =
   runAsynchronous2 ip cp
      (SigA.render rate x) (SigA.render rate y)


{-
This uses lazy StorableVector for buffering
of the internal control parameters.
This increases laziness granularity,
but it should be faster, since interpolation needs frequent look-ahead,
and this is faster on a Storable signal than on a plain stateful signal generator.
Since the look-ahead is constant,
it is interesting whether interpolation can be made more efficient
without Storable.

{-# INLINE processAsynchronousStorable2 #-}
processAsynchronousStorable2 ::
   (Dim.C u, Amp.C ecAmp0, Amp.C ecAmp1, Storable ic, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   DN.T (Dim.Recip u) t ->
   (forall r. Proc.T r u t (Signal r ecAmp0 ec0)) ->
   (forall r. Proc.T r u t (Signal r ecAmp1 ec1)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
processAsynchronousStorable2 ip cp rate x y =
   let sigX = SigA.render rate x
       sigY = SigA.render rate y
   in  cp >>= \p ->
          runAsynchronous ip cp
             (applyConverter2 const (converter p) sigX sigY)
-}

{- |
This buffers internal control parameters before interpolation.
This should be faster, since interpolation needs frequent look-ahead,
and this is faster on a buffered signal than on a plain stateful signal generator.

Since the look-ahead is constant,
it is interesting whether interpolation can be made more efficient
without the inefficient intermediate list structure.
-}
{-# INLINE processAsynchronousBuffered2 #-}
processAsynchronousBuffered2 ::
   (Dim.C u, Amp.C ecAmp0, Amp.C ecAmp1, RealField.C t) =>
   Interpolation.T t (RateDep s ic) ->
   Proc.T s u t
      (T (Converter s (Sample.T ecAmp0 ec0, Sample.T ecAmp1 ec1) ic)
         (CausalD.T s (sampleIn, SampleRateDep s ic) sampleOut)) ->
   DN.T (Dim.Recip u) t ->
   (forall r. Proc.T r u t (Signal r ecAmp0 ec0)) ->
   (forall r. Proc.T r u t (Signal r ecAmp1 ec1)) ->
   Proc.T s u t
      (CausalD.T s sampleIn sampleOut)
processAsynchronousBuffered2 ip cp rate x y =
   let sigX = SigA.render rate x
       sigY = SigA.render rate y
   in  cp >>= \p ->
          runAsynchronousBuffered ip cp
             (applyConverter2 const (converter p) sigX sigY)