-- | Unit Generators, which are the signal-generating/processing components of synths.
-- 
--   Most of your time reading documentation will probably be in this module
-- 
--   Most of these take named arguments with types like 'In', 'Freq', etc.
--   This just means you construct them with the same data constructor.
--   The data constructor is the same as its type ('In' and 'In', etc.).
--   So e.g. to make a lowpass filter which filters whitenoise at 440hz, you'd write:
-- 
--   > lpf (In whiteNoise) (Freq 440)
-- 
--   This is far from all the ones in SC, so I've exposed the internals so you can make
--   your own when you want. Some exports may disappear in future versions.

{-# OPTIONS_HADDOCK show-extensions #-}

{-# LANGUAGE NoRebindableSyntax #-}

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}

module Vivid.UGens (

   -- * Generators
   -- | Generate signals, which can then be processed

   -- ** Audio or control rate
   -- | These can be used as direct sound sources or as control parameters

     lfTri
   , lfSaw
   , sinOsc
   , fSinOsc
   , whiteNoise
   , pinkNoise
   , brownNoise


   -- ** Control rate
   -- | These wouldn't be useful as direct sound sources, but instead as
   --   parameters to other UGens

   , xLine
   , line

   -- * User input
   -- | Generators which get signals from user input

   -- ** Audio rate

   , soundIn0

   -- ** Control rate

   , mouseX
   , mouseY


   -- * Filters
   -- | Filter signals

   , bpf
   , lpf
   , hpf
   , clip


   -- * Buffers

   , playBuf1
   , recordBuf1


   -- * FFT
   -- | Stuff for Fast Fourier Transforms. Very incomplete atm.

   , localBuf
   , fft
   , ifft
   , pv_binScramble
   , pv_randComb


   -- * Signal math
   -- | Add, multiply, etc.

   -- ** Operators
   -- | Mnemonic: the ~ looks like a sound wave

   , (~*)
   , (~+)
   , (~/)
   , (~-)
   , (~>)

   -- ** Functions

   , midiCPS
   , abs'
   , neg
   , binaryOp
   , biOp
   , unaryOp
   , uOp

   -- * Uncategorized
   -- | Haven't organized yet
   , varSaw
   , syncSaw
   , impulse
   , pan2
   , out
   , lfPar
   , lfCub
   , lfPulse
   , mix
   , freeVerb
   , pitchShift
   , lag

   , module Vivid.UGens.Args
   ) where

import Vivid.SynthDef
import Vivid.UGens.Args

import Control.Applicative
import Data.ByteString (ByteString)
import Data.List.Split (chunksOf)

-- | \"A non-band-limited triangle oscillator. Output ranges from -1 to +1.\"
lfTri :: Freq -> SDState Signal
lfTri (Freq freq) = do
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S "LFTri") AR [freq', Constant 0] 1

-- | \"A non-band-limited sawtooth oscillator. Output ranges from -1 to +1.\"
lfSaw :: Freq -> SDState Signal
lfSaw (Freq freq) = do
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S "LFSaw") AR [freq', Constant 0] 1

-- | \"Generates noise whose spectrum has equal power at all frequencies.\"
whiteNoise :: SDState Signal
whiteNoise = addUGen $ UGen (UGName_S "WhiteNoise") AR [] 1

-- | \"Generates noise whose spectrum falls off in power by 3 dB per octave. This gives equal power over the span of each octave. This version gives 8 octaves of pink noise.\"
pinkNoise :: SDState Signal
pinkNoise = addUGen $ UGen (UGName_S "PinkNoise") AR [] 1

-- | \"Generates noise whose spectrum falls off in power by 6 dB per octave.\"
brownNoise :: SDState Signal
brownNoise = addUGen $ UGen (UGName_S "BrownNoise") AR [] 1

-- | Sine wave
sinOsc :: Freq -> SDState Signal
sinOsc (Freq i) = do
   i' <- toSigM i
   addUGen $ UGen (UGName_S "SinOsc") AR [i'] 1

-- | Band-pass filter
bpf :: In -> Freq -> Rq -> SDState Signal
-- Rq: bandwidth / cutofffreq
bpf (In i) (Freq freq) (Rq rq) = do
   i' <- toSigM i
   freq' <- toSigM freq
   rq' <- toSigM rq
   addUGen $ UGen (UGName_S "BPF") AR [i', freq', rq'] 1

-- also look at RLPF:
-- | Low-pass filter
lpf :: In -> Freq -> SDState Signal
lpf = passFilter "LPF"

-- | High-pass filter
hpf :: In -> Freq -> SDState Signal
hpf = passFilter "HPF"

passFilter :: ByteString -> In -> Freq -> SDState Signal
passFilter filterName (In inP) (Freq freq) = do
   in' <- toSigM inP
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S filterName) AR [in', freq'] 1

-- | Unlike in SuperCollider, you don't specify a \"lo\" parameter -- \"lo\" is always
--   negative \"hi\"
clip :: In -> {- Lo -> -} Hi -> SDState Signal
clip (In i) {- (Lo lo) -} (Hi hi) = do
   i' <- toSigM i
   -- lo' <- toSigM lo
   hi' <- toSigM hi
   lo' <- neg hi'
   addUGen $ UGen (UGName_S "Clip") AR [i', lo', hi'] 1

-- | Bus input (usually mic). \"0\" because it's from the 0th bus
soundIn0 :: SDState Signal -- this can easily be expressed now. oh also -- this is a good case where i might want to specify a ton of outputs
soundIn0 = do
   nob <- addUGen $ UGen (UGName_S "NumOutputBuses") IR [] 1
   addUGen $ UGen (UGName_S "In") AR [nob] 1

(~*) :: (ToSigM sig0, ToSigM sig1) => sig0 -> sig1 -> SDState Signal
(~*) = binaryOp Mul

(~+) :: (ToSigM i0, ToSigM i1) => i0 -> i1 -> SDState Signal
(~+) = binaryOp Add

(~/) :: (ToSigM i0, ToSigM i1) => i0 -> i1 -> SDState Signal
(~/) = binaryOp FDiv

(~>) :: (ToSigM i0, ToSigM i1) => i0 -> i1 -> SDState Signal
(~>) = binaryOp Gt

(~-) :: (ToSigM i0, ToSigM i1) => i0 -> i1 -> SDState Signal
(~-) = binaryOp Sub

-- | Build your own!
binaryOp :: (ToSigM s0, ToSigM s1) => BinaryOp -> s0 -> s1 -> SDState Signal
binaryOp theBiOp s0 s1 = do
   s0' <- toSigM s0
   s1' <- toSigM s1
   let sigs = [s0', s1']
   calcRate <- maximum <$> mapM getCalcRate sigs
   addUGen $ UGen (UGName_B theBiOp) calcRate sigs 1

-- | Alias of 'binaryOp'. Shorter, fer livecodin
biOp :: (ToSigM s0, ToSigM s1) => BinaryOp -> s0 -> s1 -> SDState Signal
biOp = binaryOp

-- | Build your own, from 'UnaryOp's
unaryOp :: (ToSigM sig) => UnaryOp -> sig -> SDState Signal
unaryOp theUOp sig = do
   sig' <- toSigM sig
   calcRate <- getCalcRate sig'
   addUGen $ UGen (UGName_U theUOp) calcRate [sig'] 1

-- | Alias of 'unaryOp'
uOp :: (ToSigM sig) => UnaryOp -> sig -> SDState Signal
uOp = unaryOp

-- | Convert from a midi note number (0-127, each representing a musical half step) to a
--   frequency in hz (cycles per second)
midiCPS :: (ToSigM i) => i -> SDState Signal
midiCPS = unaryOp MIDICPS

-- | Inverse of 'midiCPS'
cpsMIDI :: (ToSigM i) => i -> SDState Signal
cpsMIDI = unaryOp CPSMIDI

-- | The prime is to not conflict with \"abs\" in the prelude. May just use
--   \"uOp Abs\" in the future
abs' :: (ToSigM i) => i -> SDState Signal
abs' = unaryOp Abs

neg :: ToSigM i => i -> SDState Signal
neg = unaryOp Neg

out :: (ToSigM i) => Float -> [i] -> SDState [Signal]
out busNum is = do
   is' <- mapM toSigM is
   addPolyUGen $ UGen (UGName_S "Out") AR (Constant busNum : is') (length is)

lfPar :: Freq -> SDState Signal
lfPar (Freq freq) = do
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S "LFPar") AR [freq', Constant 0] 1

-- | \"Generates an exponential curve from the start value to the end value. Both the start and end values must be non-zero and have the same sign.\"
-- 
-- Defaults to KR
xLine :: Start -> End -> Dur -> DoneAction -> SDState Signal
xLine (Start start) (End end) (Dur dur) doneAction = do
   start' <- toSigM start
   end' <- toSigM end
   dur' <- toSigM dur
   addUGen $ UGen (UGName_S "XLine") KR [start', end', dur', Constant $ doneActionNum doneAction] 1

-- | \"Generates a line from the start value to the end value.\"
-- 
-- Defaults to KR
line :: Start -> End -> Dur -> DoneAction -> SDState Signal
line (Start start) (End end) (Dur dur) doneAction = do
   start' <- toSigM start
   end' <- toSigM end
   dur' <- toSigM dur
   addUGen $ UGen (UGName_S "Line") KR [start', end', dur', Constant $ doneActionNum doneAction] 1

lfCub :: Freq -> SDState Signal
lfCub (Freq freq) = do
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S "LFCub") AR [freq'] 1

impulse :: Freq -> SDState Signal
impulse (Freq freq) = do
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S "Impulse") AR [freq', Constant 0] 1

lfPulse :: Freq -> Width -> SDState Signal
lfPulse (Freq freq) (Width width) = do
   freq' <- toSigM freq
   width' <- toSigM width
   addUGen $ UGen (UGName_S "LFPulse") AR [freq', Constant 0, width'] 1


-- other options:
-- warp -- Mapping curve. 0 is linear, 1 is exponential (e. g. for freq or times). Alternatively you can specify: 'linear' or 'exponential'.
-- lag -- Lag factor to dezpipper cursor movement.
mouseY :: MinVal -> MaxVal -> SDState Signal
mouseY = mouseGeneral "MouseY"

mouseX :: MinVal -> MaxVal -> SDState Signal
mouseX = mouseGeneral "MouseX"

mouseGeneral :: ByteString -> (MinVal -> MaxVal -> SDState Signal)
mouseGeneral ugenName (MinVal minVal) (MaxVal maxVal) = do
   minVal' <- toSigM minVal
   maxVal' <- toSigM maxVal
   addUGen $ UGen (UGName_S ugenName) KR [minVal', maxVal', Constant 0, Constant 0.2] 1


varSaw :: Freq -> Width -> SDState Signal
varSaw (Freq freq) (Width width) = do
   freq' <- toSigM freq
   width' <- toSigM width
   addUGen $ UGen (UGName_S "VarSaw") AR [freq', Constant 0, width'] 1

syncSaw :: SyncFreq -> SawFreq -> SDState Signal
syncSaw (SyncFreq syncFreq) (SawFreq sawFreq) = do
   syncFreq' <- toSigM syncFreq
   sawFreq' <- toSigM sawFreq
   addUGen $ UGen (UGName_S "SyncSaw") AR [syncFreq', sawFreq'] 1

-- | Add a single LocalBuf for FFT
localBuf :: NumFrames -> NumChans -> SDState Signal
localBuf (NumFrames numFrames) (NumChans numChannels) = do
   -- don't know what the "1" is here:
   mlb <- addUGen $ UGen (UGName_S "MaxLocalBufs") IR [Constant 1] 1
   numChannels' <- toSigM numChannels
   numFrames' <- toSigM numFrames
   addUGen $ UGen (UGName_S "LocalBuf") IR [numChannels', numFrames', mlb] 1

fft :: Buf -> In -> SDState Signal
fft (Buf buf) (In inp) = do
   buf' <- toSigM buf
   inp' <- toSigM inp
      -- might want to change some of these args:
   let args = [buf', inp', Constant 0.5, Constant 0, Constant 1, Constant 0]
   addUGen $ UGen (UGName_S "FFT") KR args 1

ifft :: Buf -> SDState Signal
ifft (Buf buf) = do
   buf' <- toSigM buf
   addUGen $ UGen (UGName_S "IFFT") AR [buf', Constant 0, Constant 0] 1


   -- FFT FUNCTIONS: --

pv_binScramble :: Buf -> Wipe -> Width -> Trigger -> SDState Signal
pv_binScramble (Buf buf) (Wipe wipe) (Width width) (Trigger trigger) = do
   buf' <- toSigM buf
   wipe' <- toSigM wipe
   width' <- toSigM width
   trigger' <- toSigM trigger
   addUGen $ UGen (UGName_S "PV_BinScramble") KR [buf', wipe', width', trigger'] 1

pv_randComb :: Buf -> Wipe -> Trigger -> SDState Signal
pv_randComb (Buf buf) (Wipe wipe) (Trigger trigger) = do
   buf' <- toSigM buf
   wipe' <- toSigM wipe
   trigger' <- toSigM trigger
   addUGen $ UGen (UGName_S "PV_RandComb") KR [buf', wipe', trigger'] 1



   -- END FFT ---


-- | Mixes down a list of audio rate inputs to one. 
--   The list can't be empty.
-- 
--   This is more efficient than e.g. @foldl1 (~+)@
mix :: (ToSigM s) => [s] -> SDState Signal
mix [] = error "empty mix"
mix [x] = toSigM x
mix xs = mix =<< (mapM mix' . chunksOf 4) =<< mapM toSigM xs
 where
   mix' :: [Signal] -> SDState Signal
   mix' [] = error "something's broken"
   mix' [x] = return x
   mix' [a,b] = a ~+ b
   mix' ins@[_,_,_]   = addUGen $ UGen (UGName_S "Sum3") AR ins 1
   mix' ins@[_,_,_,_] = addUGen $ UGen (UGName_S "Sum4") AR ins 1
   mix' _ = error "that would be weird"

-- can i compute numchans?
-- also e.g. w reverb you dont want the doneaction to be 2
-- | Play a 1-channel buffer
playBuf1 :: {- NumChans -> -} Buf -> SDState Signal
 -- numchans "must be a fixed integer"
   -- args are in sc order, not osc:
playBuf1 {- (NumChans numChans) -} (Buf buf) = do
   -- numChans' <- toSigM numChans
   buf' <- toSigM buf
   addUGen $ UGen (UGName_S "PlayBuf") AR [buf', {- rate: -} Constant 1, {- trigger -} Constant 1, {- startPos -} Constant 0, {- loop: -} Constant 0, {- doneAction -} Constant 2] 1 -- numChans', 

-- | Record a 1-channel buffer
recordBuf1 :: In -> Buf -> SDState Signal
   -- args are in sc order, not osc:
recordBuf1 (In inp) (Buf buf) = do
   in' <- toSigM inp
   buf' <- toSigM buf
   addUGen $ UGen (UGName_S "RecordBuf") AR [buf', {- offset -} Constant 0, {- recLevel -} Constant 1, {- prelevel-} Constant 0, {- run -} Constant 1, {- loop -} Constant 0, {- trigger -} Constant 1, {- doneAction -} Constant 2, in'] 1

freeVerb :: In -> Mix -> Room -> Damp -> SDState Signal
freeVerb (In inp) (Mix mixS) (Room room) (Damp damp) = do
   in' <- toSigM inp
   mix' <- toSigM mixS
   room' <- toSigM room
   damp' <- toSigM damp
   addUGen $ UGen (UGName_S "FreeVerb") AR [in', mix', room', damp'] 1

pitchShift :: In -> Ratio -> SDState Signal
pitchShift (In inp) (Ratio ratio) = do
   in' <- toSigM inp
   ratio' <- toSigM ratio
   addUGen $ UGen (UGName_S "PitchShift") AR [in', {- windowSize: -} Constant 0.2, ratio', {-pitchDispersion -} Constant 0, {- timeDispersion -} Constant 0] 1

fSinOsc :: Freq -> SDState Signal
fSinOsc (Freq freq) = do
   freq' <- toSigM freq
   addUGen $ UGen (UGName_S "FSinOsc") AR [freq'] 1

-- | 'pos' is -1 to 1
pan2 :: In -> Pos -> SDState [Signal]
pan2 (In inp) (Pos pos) = do
   in' <- toSigM inp
   pos' <- toSigM pos
   addPolyUGen $ UGen (UGName_S "Pan2") AR [in', pos'] 2

-- | The \"Secs\" arg is the same as the \"lagTime\" arg in SC
lag :: In -> Secs -> SDState Signal
lag (In inp) (Secs secs) = do
   in' <- toSigM inp
   secs' <- toSigM secs
   addUGen $ UGen (UGName_S "Lag") AR [in', secs'] 1