synthesizer-0.2: src/Synthesizer/Basic/WaveSmoothed.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{- |
Copyright : (c) Henning Thielemann 2006
License : GPL
Maintainer : synthesizer@henning-thielemann.de
Stability : provisional
Portability : requires multi-parameter type classes
Waveforms which are smoothed according to the oscillator frequency
in order to suppress aliasing effects.
-}
module Synthesizer.Basic.WaveSmoothed (
T,
fromFunction,
fromWave,
fromControlledWave,
raise,
amplify,
distort,
apply,
sine,
cosine,
saw,
square,
triangle,
Wave.Harmonic,
Wave.harmonic,
composedHarmonics,
) where
import qualified Synthesizer.Basic.Wave as Wave
import qualified Synthesizer.Basic.Phase as Phase
-- import qualified Algebra.RealTranscendental as RealTrans
import qualified Algebra.Transcendental as Trans
-- import qualified Algebra.RealField as RealField
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 qualified MathObj.Polynomial as Poly
import qualified Number.Complex as Complex
import NumericPrelude
-- import qualified Prelude as P
import PreludeBase
{- * Definition and construction -}
newtype T t y = Cons {decons :: t -> Phase.T t -> y}
{-# INLINE fromFunction #-}
fromFunction :: (t -> t -> y) -> (T t y)
fromFunction wave =
Cons (\f p -> wave f (Phase.toRepresentative p))
{- |
Use this function for waves which are sufficiently smooth.
If the Nyquist frequency is exceeded the wave is simply replaced
by a constant zero wave.
-}
{-# INLINE fromWave #-}
fromWave ::
(Field.C t, Real.C t, Additive.C y) =>
Wave.T t y -> (T t y)
fromWave wave =
fromControlledWaveAux (\f -> if abs f >= 1/2 then zero else wave)
{-# INLINE fromControlledWave #-}
fromControlledWave ::
(Field.C t, Real.C t, Additive.C y) =>
(t -> Wave.T t y) -> (T t y)
fromControlledWave wave =
fromControlledWaveAux (\f0 ->
let f = abs f0
in if f >= 1/2
then zero
else wave f)
{-# INLINE fromControlledWaveAux #-}
fromControlledWaveAux :: (t -> Wave.T t y) -> (T t y)
fromControlledWaveAux wave =
Cons (\f p -> Wave.apply (wave f) p)
{- * Operations on waves -}
{-# INLINE raise #-}
raise :: (Additive.C y) => y -> T t y -> T t y
raise y = distort (y+)
{-# INLINE amplify #-}
amplify :: (Ring.C y) => y -> T t y -> T t y
amplify k = distort (k*)
{-# INLINE distort #-}
distort :: (y -> z) -> T t y -> T t z
distort g (Cons w) = Cons (\f p -> g (w f p))
{-# INLINE apply #-}
apply :: T t y -> (t -> Phase.T t -> y)
apply = decons
instance Additive.C y => Additive.C (T t y) where
{-# INLINE zero #-}
{-# INLINE (+) #-}
{-# INLINE (-) #-}
{-# INLINE negate #-}
zero = Cons (const zero)
(+) (Cons w) (Cons v) = Cons (\f p -> w f p + v f p)
(-) (Cons w) (Cons v) = Cons (\f p -> w f p - v f p)
negate = distort negate
instance Module.C a y => Module.C a (T t y) where
{-# INLINE (*>) #-}
s *> w = distort (s*>) w
{- * Examples -}
{- ** unparameterized -}
{- | map a phase to value of a sine wave -}
{-# INLINE sine #-}
sine :: (Trans.C a, Real.C a) => T a a
sine = fromWave Wave.sine
{-# INLINE cosine #-}
cosine :: (Trans.C a, Real.C a) => T a a
cosine = fromWave Wave.cosine
{- | saw tooth,
it's a ramp down in order to have a positive coefficient for the first partial sine
-}
{-# INLINE saw #-}
saw :: (Real.C a, Field.C a) => T a a
saw =
fromControlledWave (\f -> Wave.triangleAsymmetric (2*f-1))
{- | square -}
{-# INLINE square #-}
square :: (Real.C a, Field.C a) => T a a
square =
fromControlledWave (\f -> Wave.trapezoid (1-2*f))
{- | triangle -}
{-# INLINE triangle #-}
triangle :: (Real.C a, Field.C a) => T a a
triangle = fromWave Wave.triangle
{- |
Specify the wave by its harmonics.
The function is implemented quite efficiently
by applying the Horner scheme to a polynomial with complex coefficients
(the harmonic parameters)
using a complex exponential as argument.
-}
{-# INLINE composedHarmonics #-}
composedHarmonics :: (Trans.C a, Real.C a) => [Wave.Harmonic a] -> T a a
composedHarmonics hs =
let c = map (\h -> Complex.fromPolar (Wave.harmonicAmplitude h)
(2*pi * Phase.toRepresentative (Wave.harmonicPhase h))) hs
-- @take (ceiling (1/(2*f)))@ would fail for small @f@ especially @f==zero@
trunc f =
map snd . takeWhile ((<1/2) . fst) . zip (iterate (abs f +) zero)
in fromControlledWaveAux $ \f ->
Wave.distort
(Complex.imag . Poly.evaluate (Poly.fromCoeffs (trunc f c)))
Wave.helix
{-
GNUPlot.plotFunc [] (GNUPlot.linearScale 1000 (0,1::Double)) (composedHarmonics [harmonic 0 0, harmonic 0 0, harmonic 0 0, harmonic 0.25 1])
-}