{-# OPTIONS -O2 -fno-implicit-prelude -fglasgow-exts #-}
{- |
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])
-}