-- | 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