{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{- |
A wrapper around @(->)@ or @Causal.Process@
that adds amplitude handling to the Arrow paradigm.
This wrapper unifies "Synthesizer.Dimensional.Map"
and "Synthesizer.Dimensional.Causal.Process".
-}
module Synthesizer.Dimensional.Arrow where
import qualified Synthesizer.Dimensional.Signal.Private as SigA
import qualified Synthesizer.Dimensional.Amplitude.Flat as Flat
import qualified Synthesizer.Dimensional.Amplitude as Amp
import qualified Synthesizer.Dimensional.Rate as Rate
import qualified Synthesizer.Causal.Arrow as CausalArrow
import qualified Control.Arrow as Arrow
import qualified Control.Category as Category
import Control.Arrow (Arrow, ArrowLoop, )
import Control.Category (Category, )
import Control.Applicative (Applicative, liftA2, )
import qualified Synthesizer.State.Signal as Sig
import qualified Synthesizer.Generic.Signal2 as SigG2
import qualified Algebra.Module as Module
import qualified Algebra.Field as Field
import qualified Algebra.Ring as Ring
import Algebra.Module ((*>))
import qualified Number.DimensionTerm as DN
import qualified Algebra.DimensionTerm as Dim
import NumericPrelude (one)
import Prelude hiding (map, id, fst, snd, )
{- |
Note that @amp@ can also be a pair of amplitudes
or a more complicated ensemble of amplitudes.
-}
newtype T amp0 amp1 arrow =
Cons (amp0 -> (arrow, amp1))
{-
It is tempting to declare a rate parameter for the process type,
instead of putting the rate phantom into the arrow.
However, Map would then be defined as
> type Map amp0 amp1 yv0 yv1 = T (forall rate. rate) amp0 amp1 (yv0->yv1)@
which is at least ugly. Even more, in module Rate we would need
> class Applicable process signal | signal -> process
> instance Applicable (Phantom s) (Phantom s)
> instance Applicable (forall process. process) (Actual rate)
and this is not possible, at all.
With the current approach we can have
both generic apply functions and generic arrow combinators.
-}
class CausalArrow.C arrow => Applicable arrow rate
instance Applicable (->) rate
infixl 9 `apply`
{-# INLINE apply #-}
apply ::
(SigG2.Transform sig yv0 yv1, Applicable arrow rate) =>
T amp0 amp1 (arrow yv0 yv1) ->
SigA.T rate amp0 (sig yv0) ->
SigA.T rate amp1 (sig yv1)
apply (Cons f) (SigA.Cons rate xAmp samples) =
let (arrow, yAmp) = f xAmp
in SigA.Cons rate yAmp (CausalArrow.apply arrow samples)
{-# INLINE applyFlat #-}
applyFlat ::
(Flat.C yv0 amp0,
SigG2.Transform sig yv0 yv1, Applicable arrow rate) =>
T (Amp.Flat yv0) amp1 (arrow yv0 yv1) ->
SigA.T rate amp0 (sig yv0) ->
SigA.T rate amp1 (sig yv1)
applyFlat f =
apply (canonicalizeFlat >>> f)
{-# INLINE canonicalizeFlat #-}
canonicalizeFlat ::
(Flat.C y flat, Arrow arrow) =>
T flat (Amp.Flat y) (arrow y y)
canonicalizeFlat =
Cons $ \ amp -> (Arrow.arr (Flat.amplifySample amp), Amp.Flat)
{-# INLINE applyConst #-}
applyConst ::
(Amp.C amp1, Ring.C y0, CausalArrow.C arrow) =>
T (Amp.Numeric amp0) amp1 (arrow y0 yv1) ->
amp0 ->
SigA.T (Rate.Phantom s) amp1 (Sig.T yv1)
applyConst (Cons f) x =
let (arrow, yAmp) = f (Amp.Numeric x)
in SigA.Cons Rate.Phantom yAmp
(CausalArrow.apply arrow (Sig.repeat one))
infixl 0 $/:, $/-
{-# INLINE ($/:) #-}
($/:) ::
(Applicative f, SigG2.Transform sig yv0 yv1,
Applicable arrow rate) =>
f (T amp0 amp1 (arrow yv0 yv1)) ->
f (SigA.T rate amp0 (sig yv0)) ->
f (SigA.T rate amp1 (sig yv1))
($/:) = liftA2 apply
{-# INLINE ($/-) #-}
($/-) ::
(Amp.C amp1, Functor f, Ring.C y0, CausalArrow.C arrow) =>
f (T (Amp.Numeric amp0) amp1 (arrow y0 yv1)) ->
amp0 ->
f (SigA.T (Rate.Phantom s) amp1 (Sig.T yv1))
($/-) p x = fmap (flip applyConst x) p
infixr 3 ***
infixr 3 &&&
infixr 1 >>>, <<<
{-# INLINE id #-}
id ::
(Category arrow) =>
T amp amp (arrow yv yv)
id =
Cons (\amp -> (Category.id, amp))
{-# INLINE compose #-}
{-# INLINE (>>>) #-}
compose, (>>>) ::
(Category arrow) =>
T amp0 amp1 (arrow yv0 yv1) ->
T amp1 amp2 (arrow yv1 yv2) ->
T amp0 amp2 (arrow yv0 yv2)
compose (Cons f) (Cons g) =
Cons $ \ xAmp ->
let (causalXY, yAmp) = f xAmp
(causalYZ, zAmp) = g yAmp
in (causalXY Arrow.>>> causalYZ, zAmp)
(>>>) = compose
{-# INLINE (<<<) #-}
(<<<) ::
-- (Category arrow) =>
(Arrow arrow) =>
T amp1 amp2 (arrow yv1 yv2) ->
T amp0 amp1 (arrow yv0 yv1) ->
T amp0 amp2 (arrow yv0 yv2)
(<<<) = flip (>>>)
{-# INLINE first #-}
first ::
(Arrow arrow) =>
T amp0 amp1 (arrow yv0 yv1) ->
T (amp0, amp) (amp1, amp) (arrow (yv0, yv) (yv1, yv))
first (Cons f) =
Cons $ \ (xAmp, amp) ->
let (arrow, yAmp) = f xAmp
in (Arrow.first arrow, (yAmp, amp))
{-# INLINE second #-}
second ::
(Arrow arrow) =>
T amp0 amp1 (arrow yv0 yv1) ->
T (amp, amp0) (amp, amp1) (arrow (yv, yv0) (yv, yv1))
second (Cons f) =
Cons $ \ (amp, xAmp) ->
let (arrow, yAmp) = f xAmp
in (Arrow.second arrow, (amp, yAmp))
{-# INLINE split #-}
{-# INLINE (***) #-}
split, (***) ::
(Arrow arrow) =>
T amp0 amp1 (arrow yv0 yv1) ->
T amp2 amp3 (arrow yv2 yv3) ->
T (amp0, amp2) (amp1, amp3) (arrow (yv0, yv2) (yv1, yv3))
split f g =
compose (first f) (second g)
(***) = split
{-# INLINE fanout #-}
{-# INLINE (&&&) #-}
fanout, (&&&) ::
(Arrow arrow) =>
T amp amp0 (arrow yv yv0) ->
T amp amp1 (arrow yv yv1) ->
T amp (amp0, amp1) (arrow yv (yv0, yv1))
fanout f g =
compose double (split f g)
(&&&) = fanout
-- * map functions
{- |
This function can be abused to bring the amplitudes out of order.
So be careful!
-}
independentMap ::
(Arrow arrow) =>
(amp0 -> amp1) -> (yv0 -> yv1) ->
T amp0 amp1 (arrow yv0 yv1)
independentMap f g =
Cons (\amp -> (Arrow.arr g, f amp))
double ::
(Arrow arrow) =>
T amp (amp, amp) (arrow y (y, y))
double =
let aux = \x -> (x, x)
in independentMap aux aux
{-# INLINE forceDimensionalAmplitude #-}
forceDimensionalAmplitude ::
(Dim.C v, Field.C y, Module.C y yv, Arrow arrow) =>
DN.T v y ->
T (Amp.Dimensional v y) (Amp.Dimensional v y) (arrow yv yv)
forceDimensionalAmplitude ampOut =
Cons $ \(Amp.Numeric ampIn) ->
(Arrow.arr (DN.divToScalar ampIn ampOut *>),
Amp.Numeric ampOut)
{- |
I will call the connection from input to output amplitudes of type @amp@,
the looping channel.
It is essential, that the looping channel decouples output from input amplitude.
You can achieve this by inserting one of the @forceAmplitude@ functions
somewhere in the looping channel.
-}
{-# INLINE loop #-}
loop ::
(ArrowLoop arrow) =>
T (restAmpIn, amp) (restAmpOut, amp)
(arrow (restSampIn, yv) (restSampOut, yv)) ->
T restAmpIn restAmpOut (arrow restSampIn restSampOut)
loop (Cons f) =
Cons $ \restAmpIn ->
let (arrow, (restAmpOut, amp)) = f (restAmpIn, amp)
in (Arrow.loop arrow, restAmpOut)
{-# INLINE loopVolume #-}
loopVolume ::
(Field.C y, Module.C y yv, Dim.C v,
ArrowLoop arrow) =>
DN.T v y ->
T (restAmpIn, Amp.Dimensional v y)
(restAmpOut, Amp.Dimensional v y)
(arrow (restSampIn, yv) (restSampOut, yv)) ->
T restAmpIn restAmpOut
(arrow restSampIn restSampOut)
loopVolume ampIn f =
loop (f >>> second (forceDimensionalAmplitude ampIn))
{-# INLINE loop2Volume #-}
loop2Volume ::
(Field.C y0, Module.C y0 yv0, Dim.C v0,
Field.C y1, Module.C y1 yv1, Dim.C v1,
ArrowLoop arrow) =>
(DN.T v0 y0, DN.T v1 y1) ->
T (restAmpIn, (Amp.Numeric (DN.T v0 y0), Amp.Numeric (DN.T v1 y1)))
(restAmpOut, (Amp.Numeric (DN.T v0 y0), Amp.Numeric (DN.T v1 y1)))
(arrow (restSampIn, (yv0,yv1))
(restSampOut, (yv0,yv1))) ->
T restAmpIn restAmpOut
(arrow restSampIn restSampOut)
loop2Volume (ampIn0,ampIn1) f =
loop (f >>> second
(forceDimensionalAmplitude ampIn0 ***
forceDimensionalAmplitude ampIn1))