{- | Copyright : (c) Henning Thielemann 2008 License : GPL Maintainer : synthesizer@henning-thielemann.de Stability : provisional Portability : requires multi-parameter type classes -} module Synthesizer.Dimensional.Causal.Displacement ( mix, mixVolume, fanoutAndMixMulti, fanoutAndMixMultiVolume, raise, distort, ) where import qualified Synthesizer.Dimensional.Process as Proc import qualified Synthesizer.Dimensional.Causal.Process as CausalD import qualified Synthesizer.Causal.Process as Causal import Control.Arrow ((^<<), (&&&), ) import qualified Number.DimensionTerm as DN import qualified Algebra.DimensionTerm as Dim import qualified Algebra.Module as Module import qualified Algebra.Field as Field import qualified Algebra.Real as Real -- import qualified Algebra.Ring as Ring -- import qualified Algebra.Additive as Additive -- import Algebra.Module ((*>)) import Control.Monad.Trans.Reader (Reader, runReader, ask, ) import PreludeBase import NumericPrelude import Prelude () {- * Mixing -} {- | Mix two signals. In contrast to 'zipWith' the result has the length of the longer signal. -} {-# INLINE mix #-} mix :: (Real.C y, Field.C y, Module.C y yv, Dim.C v) => Proc.T s u t (CausalD.T s (DN.T v y, DN.T v y) (DN.T v y) (yv,yv) yv) mix = Proc.pure $ fromAmplitudeReader $ \(amp0,amp1) -> (DN.abs amp0 + DN.abs amp1, mixCore amp0 amp1) {-# INLINE mixVolume #-} mixVolume :: (Field.C y, Module.C y yv, Dim.C v) => DN.T v y -> Proc.T s u t (CausalD.T s (DN.T v y, DN.T v y) (DN.T v y) (yv,yv) yv) mixVolume amp = Proc.pure $ fromAmplitudeReader $ \(amp0,amp1) -> (amp, mixCore amp0 amp1) {-# INLINE mixCore #-} mixCore :: (Field.C y, Module.C y yv, Dim.C v) => DN.T v y -> DN.T v y -> Reader (DN.T v y) (Causal.T (yv,yv) yv) mixCore amp0 amp1 = do toSamp0 <- toAmplitudeVector amp0 toSamp1 <- toAmplitudeVector amp1 return $ Causal.map (\(y0,y1) -> toSamp0 y0 + toSamp1 y1) {- | Mix one or more signals. -} {-# INLINE fanoutAndMixMulti #-} fanoutAndMixMulti :: (Real.C y, Field.C y, Module.C y yv, Dim.C v) => [Proc.T s u t (CausalD.T s ampIn (DN.T v y) yvIn yv)] -> Proc.T s u t (CausalD.T s ampIn (DN.T v y) yvIn yv) fanoutAndMixMulti = fmap fanoutAndMixMultiPlain . sequence {-# INLINE fanoutAndMixMultiPlain #-} fanoutAndMixMultiPlain :: (Real.C y, Field.C y, Module.C y yv, Dim.C v) => [CausalD.T s ampIn (DN.T v y) yvIn yv] -> CausalD.T s ampIn (DN.T v y) yvIn yv fanoutAndMixMultiPlain cs = fromAmplitudeReader $ \ampIn -> let ampCs = map (\(CausalD.Cons f) -> f ampIn) cs in (maximum (map fst ampCs), fanoutAndMixMultiVolumeCore ampCs) {-# INLINE fanoutAndMixMultiVolume #-} fanoutAndMixMultiVolume :: (Field.C y, Module.C y yv, Dim.C v) => DN.T v y -> [Proc.T s u t (CausalD.T s ampIn (DN.T v y) yvIn yv)] -> Proc.T s u t (CausalD.T s ampIn (DN.T v y) yvIn yv) fanoutAndMixMultiVolume amp = fmap (fanoutAndMixMultiVolumePlain amp) . sequence {-# INLINE fanoutAndMixMultiVolumePlain #-} fanoutAndMixMultiVolumePlain :: (Field.C y, Module.C y yv, Dim.C v) => DN.T v y -> [CausalD.T s ampIn (DN.T v y) yvIn yv] -> CausalD.T s ampIn (DN.T v y) yvIn yv fanoutAndMixMultiVolumePlain amp cs = fromAmplitudeReader $ \ampIn -> (amp, fanoutAndMixMultiVolumeCore $ map (\(CausalD.Cons f) -> f ampIn) cs) {-# INLINE fanoutAndMixMultiVolumeCore #-} fanoutAndMixMultiVolumeCore :: (Field.C y, Module.C y yv, Dim.C v) => [(DN.T v y, Causal.T yvIn yv)] -> Reader (DN.T v y) (Causal.T yvIn yv) fanoutAndMixMultiVolumeCore cs = foldr (\(ampX,c) acc -> do toSamp <- toAmplitudeVector ampX rest <- acc return $ uncurry (+) ^<< (toSamp ^<< c) &&& rest) (return $ Causal.map (const zero)) cs {- | 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) => DN.T v y -> yv -> Proc.T s u t (CausalD.T s (DN.T v y) (DN.T v y) yv yv) raise y' yv = Proc.pure $ fromAmplitudeReader $ \amp -> (amp, do toSamp <- toAmplitudeVector y' return $ Causal.map (toSamp yv +)) {- | 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 output. 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) => (yv -> yv) -> Proc.T s u t (CausalD.T s (DN.T v y, DN.T v y) (DN.T v y) (y,yv) yv) distort f = Proc.pure $ fromAmplitudeReader $ \(ampCtrl,ampIn) -> (ampIn, do toSamp <- toAmplitudeScalar ampCtrl return $ Causal.map (\(c,y) -> let c' = toSamp c in c' *> f (recip c' *> y))) {-# INLINE toAmplitudeScalar #-} toAmplitudeScalar :: (Field.C y, Dim.C u) => DN.T u y -> Reader (DN.T u y) (y -> y) toAmplitudeScalar ampIn = do ampOut <- ask return (DN.divToScalar ampIn ampOut *) {-# INLINE toAmplitudeVector #-} toAmplitudeVector :: (Module.C y yv, Field.C y, Dim.C u) => DN.T u y -> Reader (DN.T u y) (yv -> yv) toAmplitudeVector ampIn = do ampOut <- ask return (DN.divToScalar ampIn ampOut *> ) {-# INLINE fromAmplitudeReader #-} fromAmplitudeReader :: (ampIn -> (ampOut, Reader ampOut (Causal.T yv0 yv1))) -> CausalD.T s ampIn ampOut yv0 yv1 fromAmplitudeReader f = CausalD.Cons $ \ampIn -> let (ampOut, rd) = f ampIn in (ampOut, runReader rd ampOut)