module Synthesizer.Dimensional.Map.Displacement ( mix, mixVolume, fanoutAndMixMulti, fanoutAndMixMultiVolume, raise, distort, mapLinear, mapExponential, mapLinearDimension, ) where import qualified Synthesizer.Dimensional.Amplitude.Flat as Flat import qualified Synthesizer.Dimensional.Amplitude as Amp import qualified Synthesizer.Dimensional.Sample as Sample import qualified Synthesizer.Dimensional.Arrow as ArrowD import Control.Arrow (Arrow, arr, (<<<), (^<<), (&&&), ) import qualified Number.DimensionTerm as DN import qualified Algebra.DimensionTerm as Dim import Number.DimensionTerm ((&*&)) import qualified Algebra.Transcendental as Trans import qualified Algebra.Module as Module import qualified Algebra.RealField as RealField import qualified Algebra.Field as Field import qualified Algebra.Absolute as Absolute import qualified Algebra.Ring as Ring -- import qualified Algebra.Additive as Additive -- import Algebra.Module ((*>)) import Control.Monad.Trans.Reader (Reader, runReader, asks, ) import Control.Applicative (liftA2, ) import NumericPrelude.Base import NumericPrelude.Numeric import Prelude () type DNS v y yv = Sample.Dimensional v y yv type Context v y = Reader (DN.T v y) -- * Mixing {- | Mix two signals. In contrast to 'zipWith' the result has the length of the longer signal. -} {-# INLINE mix #-} mix :: (Absolute.C y, Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => ArrowD.T arrow (DNS v y yv, DNS v y yv) (DNS v y yv) mix = fromAmplitudeReader $ \(Amp.Numeric amp0, Amp.Numeric amp1) -> (DN.abs amp0 + DN.abs amp1, mixCore amp0 amp1) {-# INLINE mixVolume #-} mixVolume :: (Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => DN.T v y -> ArrowD.T arrow (DNS v y yv, DNS v y yv) (DNS v y yv) mixVolume amp = fromAmplitudeReader $ \(Amp.Numeric amp0, Amp.Numeric amp1) -> (amp, mixCore amp0 amp1) {-# INLINE mixCore #-} mixCore :: (Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => DN.T v y -> DN.T v y -> Context v y (arrow (yv,yv) yv) mixCore amp0 amp1 = liftA2 (\toSamp0 toSamp1 -> arr (\(y0,y1) -> toSamp0 y0 + toSamp1 y1)) (toAmplitudeVector amp0) (toAmplitudeVector amp1) {- | Mix one or more signals. -} {-# INLINE fanoutAndMixMulti #-} fanoutAndMixMulti :: (RealField.C y, Module.C y yv, Dim.C v, Arrow arrow) => [ArrowD.T arrow sample (DNS v y yv)] -> ArrowD.T arrow sample (DNS v y yv) fanoutAndMixMulti cs = fromAmplitudeReader $ \ampIn -> let ampCs = map (\(ArrowD.Cons f) -> f ampIn) cs in (maximum (map (\(_, Amp.Numeric amp) -> amp) ampCs), fanoutAndMixMultiCore ampCs) {- | Mix zero or more signals. -} {-# INLINE fanoutAndMixMultiVolume #-} fanoutAndMixMultiVolume :: (Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => DN.T v y -> [ArrowD.T arrow sample (DNS v y yv)] -> ArrowD.T arrow sample (DNS v y yv) fanoutAndMixMultiVolume amp cs = fromAmplitudeReader $ \ampIn -> (amp, fanoutAndMixMultiCore $ map (\(ArrowD.Cons f) -> f ampIn) cs) {-# INLINE fanoutAndMixMultiCore #-} fanoutAndMixMultiCore :: (Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => [(arrow yvIn yv, Amp.Dimensional v y)] -> Context v y (arrow yvIn yv) fanoutAndMixMultiCore cs = foldr (\(c, Amp.Numeric ampX) -> liftA2 (\toSamp rest -> uncurry (+) ^<< (toSamp ^<< c) &&& rest) (toAmplitudeVector ampX)) (return $ arr (const zero)) cs -- * Miscellaneous {- | Add a number to all of the signal values. This is useful for adjusting the center of a modulation. -} {-# INLINE raise #-} raise :: (Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => DN.T v y -> yv -> ArrowD.T arrow (DNS v y yv) (DNS v y yv) raise y' yv = fromAmplitudeReader $ \(Amp.Numeric amp) -> (amp, fmap (\toSamp -> arr (toSamp yv +)) (toAmplitudeVector y')) {- | Distort the signal using a flat function. The first signal gives the scaling of the function. If the scaling is @c@ and the input sample is @y@, then @c * f(y/c)@ is emitted. This way we can use an (efficient) flat function and have a simple, yet dimension conform, way of controlling the distortion. E.g. if the distortion function is @tanh@ then the value @c@ controls the saturation level. -} {-# INLINE distort #-} distort :: (Field.C y, Module.C y yv, Dim.C v, Arrow arrow) => (yv -> yv) -> ArrowD.T arrow (DNS v y y, DNS v y yv) (DNS v y yv) distort f = fromAmplitudeReader $ \(Amp.Numeric ampCtrl, Amp.Numeric ampIn) -> (ampIn, fmap (\toSamp -> arr (\(c,y) -> let c' = toSamp c in c' *> f (recip c' *> y))) (toAmplitudeScalar ampCtrl)) {- | Map a control curve without amplitude unit by a linear (affine) function with a unit. This is a combination of 'raise' and 'amplify'. It is not quite correct in the sense, that it does not produce low-level sample values in the range (-1,1). Instead it generates values around 1. -} {-# INLINE mapLinear #-} mapLinear :: (Flat.C y flat, Ring.C y, Dim.C u, Arrow arrow) => y -> DN.T u y -> ArrowD.T arrow (Sample.T flat y) (DNS u y y) mapLinear depth center = ArrowD.Cons (\Amp.Flat -> (arr (\x -> one+x*depth), Amp.Numeric center)) <<< ArrowD.canonicalizeFlat {-# INLINE mapExponential #-} mapExponential :: (Flat.C y flat, Trans.C y, Dim.C u, Arrow arrow) => y -> DN.T u q -> ArrowD.T arrow (Sample.T flat y) (DNS u q y) mapExponential depth center = {- X86 processors only have (logBase 2) and (2**). Thus on those machines computing with respect to base 2 can be more efficient and more precise. -} let logDepth = log depth in ArrowD.Cons (\Amp.Flat -> (arr (exp . (logDepth*)), Amp.Numeric center)) <<< ArrowD.canonicalizeFlat {-# INLINE mapLinearDimension #-} mapLinearDimension :: (Field.C y, Absolute.C y, Dim.C u, Dim.C v, Arrow arrow) => DN.T v y {- ^ range: one is mapped to @center + range * ampX@ -} -> DN.T (Dim.Mul v u) y {- ^ center: zero is mapped to @center@ -} -> ArrowD.T arrow (DNS u y y) (DNS (Dim.Mul v u) y y) mapLinearDimension range center = ArrowD.Cons $ \(Amp.Numeric ampIn) -> let absRange = DN.abs range &*& ampIn absCenter = DN.abs center ampOut = absRange + absCenter rng = DN.divToScalar absRange ampOut cnt = DN.divToScalar absCenter ampOut in (arr (\y -> cnt + rng*y), Amp.Numeric ampOut) -- auxiliary functions {-# INLINE toAmplitudeScalar #-} toAmplitudeScalar :: (Field.C y, Dim.C u) => DN.T u y -> Context u y (y -> y) toAmplitudeScalar ampIn = asks (\ampOut -> (DN.divToScalar ampIn ampOut *)) {-# INLINE toAmplitudeVector #-} toAmplitudeVector :: (Module.C y yv, Field.C y, Dim.C u) => DN.T u y -> Context u y (yv -> yv) toAmplitudeVector ampIn = asks (\ampOut -> (DN.divToScalar ampIn ampOut *> )) {-# INLINE fromAmplitudeReader #-} fromAmplitudeReader :: (Sample.Amplitude sampleIn -> (ampOut, Reader ampOut (arrow (Sample.Displacement sampleIn) yvOut))) -> ArrowD.T arrow sampleIn (Sample.Numeric ampOut yvOut) fromAmplitudeReader f = ArrowD.Cons $ \ampIn -> let (ampOut, rd) = f ampIn in (runReader rd ampOut, Amp.Numeric ampOut)