-- | The vital tools.
module Csound.Air (
    -- * Oscillators
    
    -- ** Bipolar
    osc, saw, sq, tri, -- pulse, ramp,
    
    -- ** Unipolar
    unipolar, uosc, usaw, usq, utri, -- upulse, uramp,
    
    -- * Filters
    -- | Arguemnts are inversed to get most out of curruing. First come parameters and the last one is the signal.
    
    -- ** Simple filters
    lp, hp, bp, br,
    
    -- ** Butterworth filters
    blp, bhp, bbp, bbr,
    
    -- * Patterns
    once, mean,
    
    -- ** Series
    hase, whase,
    haseS, whaseS,
    
    -- ** Crossfade
    cfd, cfds, cfdSpec, cfdsSpec
) where

import Csound.Exp(Tab)
import Csound.Exp.Wrapper(Sig, Spec, SE, kr)
import Csound.Exp.Cons(withInits)
import Csound.Exp.Numeric
import Csound.Opcode(idur, oscil3, vco, pvscross, 
    atone, tone, areson, reson,
    buthp, butbp, butlp, butbr)
import Csound.Tab(hifi, sines, guardPoint)

--------------------------------------------------------------------------
-- oscillators

-- | Pure tone.
osc :: Sig -> Sig
osc cps = oscil3 1 cps (sines [1])

resolution = 12

-- | Sawtooth.
saw :: Sig -> Sig
saw cps = oscil3 1 cps (sines $ take resolution $ fmap (1 / ) [1 .. ])
-- vco 1 cps 1 0.5 `withInits` (sines [1])


-- | Square wave.
sq :: Sig -> Sig
sq cps = oscil3 1 cps (sines $ take resolution $ fmap f [1 .. ])
    where f x
            | even x    = 0
            | otherwise = 1 / fromIntegral x
-- vco 1 cps 2 0.5 `withInits` (sines [1])

-- | Triangle wave.
tri :: Sig -> Sig
tri cps = oscil3 1 cps (sines $ take resolution $ zipWith f (cycle [1, -1]) [1 ..])
    where f a x
            | even x    = 0
            | otherwise = a / fromIntegral (x ^ 2)
-- vco 1 cps 3 0.5 `withInits` (sines [1])

{-
-- | Square wave with variable dty cycle.
--
-- > pulse duty cps
--
-- First argument varies between 0 and 1 (0.5 equals to square wave)
pulse :: Sig -> Sig -> Sig
pulse duty cps = vco 1 cps 2 duty `withInits` (sines [1])

-- | Triangle wave with variable ramp character.
--
-- > ramp angle cps
--
-- First argument varies between 0 and 1 (0.5 equals to triangle wave)
ramp :: Sig -> Sig -> Sig
ramp angle cps = vco 1 cps 3 angle `withInits` (sines [1])
-}

-- unipolar waves

-- | Turns a bipolar sound (ranges from -1 to 1) to unipolar (ranges from 0 to 1)
unipolar :: Sig -> Sig
unipolar a = 0.5 + 0.5 * a

-- | Unipolar pure tone.
uosc :: Sig -> Sig
uosc = unipolar . osc

-- | Unipolar sawtooth.
usaw :: Sig -> Sig
usaw = unipolar . saw

-- | Unipolar square wave.
usq :: Sig -> Sig
usq = unipolar . sq

-- | Unipolar triangle wave.
utri :: Sig -> Sig
utri = unipolar . tri

{-
-- | Unipolar pulse.
upulse :: Sig -> Sig -> Sig
upulse a = unipolar . pulse a

uramp :: Sig -> Sig -> Sig
uramp a = unipolar . ramp a
-}
--------------------------------------------------------------------------
-- filters

-- | High-pass filter.
--
-- > hp cutoff sig
hp :: Sig -> Sig -> Sig
hp = flip atone

-- | Low-pass filter.
--
-- > lp cutoff sig
lp :: Sig -> Sig -> Sig
lp = flip tone

-- | Band-pass filter.
--
-- > bp cutoff bandwidth sig
bp :: Sig -> Sig -> Sig -> Sig
bp freq band a = reson a freq band

-- | Band-regect filter.
--
-- > br cutoff bandwidth sig
br :: Sig -> Sig -> Sig -> Sig 
br freq band a = areson a freq band

-- Butterworth filters

-- | High-pass filter.
--
-- > bhp cutoff sig
bhp :: Sig -> Sig -> Sig
bhp = flip buthp

-- | Low-pass filter.
--
-- > blp cutoff sig
blp :: Sig -> Sig -> Sig
blp = flip butlp

-- | Band-pass filter.
--
-- > bbp cutoff bandwidth sig
bbp :: Sig -> Sig -> Sig -> Sig
bbp freq band a = butbp a freq band

-- | Band-regect filter.
--
-- > bbr cutoff bandwidth sig
bbr :: Sig -> Sig -> Sig -> Sig 
bbr freq band a = butbr a freq band


--------------------------------------------------------------------------
-- patterns

-- | Reads table once during the note length. 
once :: Tab -> Sig
once a = oscil3 1 (1 / kr idur) a

-- | Mean value.
mean :: Fractional a => [a] -> a
mean xs = sum xs / (fromIntegral $ length xs)

-- | Harmonic series. Takes a function that transforms the signal by some parameter
-- and the list of parameters. It constructs the series of transformers and sums them
-- at the end with equal strength.
hase :: (a -> Sig -> Sig) -> [a] -> Sig -> Sig
hase f as x = mean $ fmap (( $ x) . f) as

-- | Harmonic series, but now you can specify the weights of the final sum.
whase :: (a -> Sig -> Sig) -> [(Sig, a)] -> Sig -> Sig
whase f as x = sum $ fmap (\(weight, param) -> weight * f param x) as

-- | Harmonic series for functions with side effects.
haseS :: (a -> Sig -> SE Sig) -> [a] -> Sig -> SE Sig
haseS mf as x = fmap mean $ mapM (\param -> mf param x) as

-- | Weighted harmonic series for functions with side effects.
whaseS :: (a -> Sig -> SE Sig) -> [(Sig, a)] -> Sig -> SE Sig
whaseS mf as x = fmap sum $ mapM (\(weight, param) -> fmap (weight * ) (mf param x)) as


-- | Crossfade.
--
-- > cfd coeff sig1 sig2
--
-- If coeff equals 0 then we get the first signal and if it equals 1 we get the second signal.
cfd :: Sig -> Sig -> Sig -> Sig
cfd coeff a b = (1 - coeff) * a + coeff * b
  
genCfds :: a -> (Sig -> a -> a -> a) -> [Sig] -> [a] -> a
genCfds zero mixFun cs xs = case xs of
    []   -> zero
    a:as -> foldl (\x f -> f x) a $ zipWith mix' cs as 
    where mix' c a b = mixFun c b a
  
-- | Generic crossfade for n coefficients and n+1 signals.
--
-- > cfds coeffs sigs
cfds :: [Sig] -> [Sig] -> Sig
cfds = genCfds 0 cfd

-- | Spectral crossfade.
cfdSpec :: Sig -> Spec -> Spec -> Spec
cfdSpec coeff a b = pvscross a b (1 - coeff) coeff

-- | Generic spectral crossfade.
cfdsSpec :: [Sig] -> [Spec] -> Spec
cfdsSpec = genCfds undefined cfdSpec