-- | A gallery of sound processors (effects).
module Csound.Catalog.Effect(
    nightChorus, nightReverb, 
    vibroDelay, delayLine, bassEnhancment, declick,
    sweepFilter, loopSweepFilter,
    -- * Presets
    bayAtNight, vestigeOfTime
) where

import Control.Monad

import Csound.Base

-- | A signal goes throgh the chain of varible delays. 
-- Delay time is affected by vibrato.
--
-- > aout = vibroDelay n delayBufferSize vibDepth vibRate asig
--
-- * @n@ -- number of delay lines
--
-- * @delayBufSize@ -- buffer size for the delay lines (it should be greater
-- than absolute maximum of the depth of the vibrato)
--
-- * @vibDepth@ -- the amplitude of the delay line time vibrato
--
-- * @vibRate@ -- the frequency of the delay lie time vibrato
vibroDelay :: Int -> D -> Sig -> Sig -> Sig -> Sig
vibroDelay order delayBufSize vibDepth vibRate asig = balance aout asig
    where aout = mean $ take order $ iterate del asig
          del x = vdelay x (vibDepth * uosc vibRate) delayBufSize

-- | Chorus effect, borrowed from http://www.jlpublishing.com/Csound.htm
-- I made some of its parameters accesible trhough score
-- delay in milliseconds (by John Lato in Csound)
--
-- > nightChorus idlym iscale asig
--
-- * idlym  -- delay in milliseconds
--
-- * iscale -- amplitude of the vibrato on delay time (in milliseconds).
nightChorus :: D -> D -> Sig -> Sig
nightChorus idlym iscale asig = 0.5 * aout
    where 
        phi cps maxDel = vdelay3 asig (sig (idlym / 5) + sig (idlym / iscale) * osc cps) maxDel
        aout = sum $ zipWith phi 
            [1, 0.995, 1.05, 1]
            [900, 700, 700, 900]

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

-- | Reverb
--
-- A bunch of delay lines FDN reverb, with feedback matrix based upon 
-- physical modeling scattering junction of 8 lossless waveguides
-- of equal characteristic impedance. Based on Julius O. Smith III, 
-- \"A New Approach to Digital Reverberation using Closed Waveguide
-- Networks,\" Proceedings of the International Computer Music 
-- Conference 1985, p. 47-53 (also available as a seperate
-- publication from CCRMA), as well as some more recent papers by
-- Smith and others.
--
-- Coded by Sean Costello, October 1999 (in Csound)
--
-- > nightReverb n igain ipitchmod itone cps
--
--  * @n@               -- a number of delay lines (typical value is 8)
--
--  * @igain@           -- gain of reverb. adjust empirically
--                      for desired reverb time. 0.6 gives
--                      a good small \"live\" room sound, 0.8
--                      a small hall, 0.9 a large hall,
--                      0.99 an enormous stone cavern.
--  
--  * @ipitchmod@      -- amount of random pitch modulation
--                     for the delay lines. 1 is the \"normal\"
--                     amount, but this may be too high for
--                     held pitches such as piano tones.
--                     adjust to taste.
--  
--  * @itone@           -- cutoff frequency of lowpass filters
--                       in feedback loops of delay lines,
--                       in hz. lower cutoff frequencies results
--                       in a sound with more high-frequency
--                       damping.
--
nightReverb :: Int -> D -> D -> D -> Sig -> SE (Sig, Sig)
nightReverb n igain ipitchmod itone asig = do
    afiltRefs   <- mapM newRef $ replicate n 0
    afilts1     <- mapM readRef afiltRefs 
    let apj     = (2 / fromIntegral n) * sum afilts1
    adels       <- sequence $ zipWith3 (del apj) idels ks afilts1
    zipWithM_ (\ref x -> writeRef ref $ filt x) afiltRefs adels
    afilts2     <- mapM readRef afiltRefs
    return (mean $ odds afilts2, mean $ evens afilts2)    
    where
        idels = cycle $ fmap ( / getSampleRate) [2473, 2767, 3217, 3557, 3907, 4127, 2143, 1933]
        ks    = cycle $ zipWith3 (\a b c -> randi a b `withSeed` c) 
            [0.001, 0.0011, 0.0017, 0.0006, 0.001, 0.0011, 0.0017, 0.0006]
            [3.1,   3.5,    1.11,   3.973,  2.341, 1.897,  0.891,  3.221]
            [0.06,  0.9,    0.7,    0.3,    0.63,  0.7,    0.9,    0.44]

        del apj idel k afilt = do
            _ <- delayr 1
            adel1 <- deltapi $ sig idel + k * sig ipitchmod
            delayw $ asig  + apj - afilt
            return adel1

        filt adel = tone (adel * sig igain) (sig itone)

-- | Enhances all frequencies below the give frequency by the given coefficient.
-- Original signal is added to the filtered signal with low-pass filter and scaled.
--
-- > bassEnhancment centerFrequency coefficient asig
bassEnhancment :: D -> D -> Sig -> Sig
bassEnhancment cfq k asig = sig k * butlp asig (sig cfq) + asig

-- | A chain of delay lines.
--
-- > delayLine n k dt asig
-- 
-- A signal (@asig@) is passed through the chain of fixed time delays (A @dt@ is the delay time 
-- @n@ is a number of filters, k - is scale of the signals that is passed through each delay line).
delayLine :: Int -> D -> D -> Sig -> (Sig, Sig)
delayLine n k dt asig = (mean $ asig : odds asigs, mean $ asig : evens asigs)
    where phi x = delaySig dt (x * sig k)
          asigs = take n $ iterate phi (delaySig dt asig)

-- | Adds a very short fade in to remove the click at the beggining of the note.
declick :: Sig -> Sig
declick = (fadeIn 0.01 * )

-- | Sweep band pass filter (center frequency ramps from one value to another)
--
-- > sweepFilter dur startCps endCps bandWidth asignal
sweepFilter :: D -> D -> D -> Sig -> Sig -> Sig
sweepFilter dur start end bandWidth = bp centerFreq bandWidth
    where centerFreq = linseg [start, dur, end]

-- | Sweep band pass filter in loops (center frequency ramps from one value to another and back)
--
-- > sweepFilter dur startCps endCps bandWidth asignal
loopSweepFilter :: D -> D -> D -> Sig -> Sig -> Sig
loopSweepFilter dur start end bandWidth = bp centerFreq bandWidth
    where centerFreq = loopseg [sig start, 1, sig end, 1, sig start] (1 / sig dur)

-- | The effect that was used in the piece \"Bay at night\".
bayAtNight :: Sig -> SE (Sig, Sig)
bayAtNight
    = mapOut (bassEnhancment 100 1.5)
    . nightReverb 8 0.98 0.8 20000 
    . nightChorus 2 30
    where mapOut f = fmap (\(a, b) -> (f a, f b))

-- | The effect that was used in the piece \"Vestige of time\".
vestigeOfTime :: Sig -> (Sig, Sig)
vestigeOfTime 
    = mapOut ((* 0.3) . (\x -> reverb2 x 2 0.2))     
    . delayLine 6 1.2 0.9
    where mapOut f (a, b) = (f a, f b)