content: {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  {-# LANGUAGE NoIncoherentInstances #-}
  {-# LANGUAGE NoMonomorphismRestriction #-}
  {-# LANGUAGE NoUndecidableInstances #-}
  
  module Vivid.UGens.Multichannel (
  
       -- * Multichannel > Ambisonics
  
  ---     biPanB2
  ---   , decodeB2
  ---   , panB
  ---   , panB2
  ---   , rotate2
  
       -- * Multichannel > Panners
  
  ---   , balance2
  ---   , linPan2
       pan2
  ---   , pan4
  ---   , panAz
       -- See above:
     -- , rotate2
     , splay
  ---   , splayAz
  
       -- * Multichannel > Select
  
  ---   , linSelectX
  ---   , linXFade2
     , select
  ---   , selectX
  ---   , selectXFocus
  ---   , xFade2
  
       -- * Multichannel
       
     , mix
  ---   , numChannels
  
     , addChannels
     ) where
  
  import Vivid.SC.SynthDef.Types (CalculationRate(..))
  import Vivid.SynthDef
  --import Vivid.SynthDef.FromUA
  import Vivid.UGens.Algebraic
  import Vivid.UGens.Args
  import Vivid.UGens.Generators.SingleValue (dc)
  import Vivid.SynthDef.FromUA
  
  import Control.Monad (foldM, zipWithM)
  import Data.List.Split (chunksOf)
  
  --- biPanB2 ::
  --- biPanB2 =
  --- decodeB2 ::
  --- decodeB2 =
  --- panB ::
  --- panB =
  --- panB2 ::
  --- panB2 =
  --- rotate2 ::
  --- rotate2 =
  --- balance2 ::
  --- balance2 =
  --- linPan2 ::
  --- linPan2 =
  
  -- | 'pos' is -1 to 1
  -- 
  --   'level' is \"a control-rate level input\"
  pan2 :: Args '["in","pos"] '["level"] a => a -> SDBody a [Signal]
  pan2 = makePolyUGen
     2 "Pan2" AR
     (Vs::Vs '["in","pos","level"])
     (level_ (1::Float))
  
  -- return a tuple?: -- no no that's exactly when you run into problems with the foldable shit -- people get a tuple when they expect a list....
  --- pan4 ::
  --- pan4 =
  --- panAz ::
  --- panAz =
  
  -- | "Spreads [a list] of channels across the stereo field."
  splay :: ToSig s a => [s] -> SDBody' a [Signal]
  splay sigsMono = do
     -- sigs' <- mapM toSig sigs
     let numChans :: Float
         numChans = toEnum $ max 2 $ (length::[a]->Int) sigsMono
     let positions :: [Float]
         positions =
           map ((\x->x-1) . (*(2/(numChans-1)))) [0..(numChans-1)]
      -- note SC has a different calculation for KR:
     let level = sqrt (recip numChans)
     sigsStereo <- (\x -> zipWithM x sigsMono positions) $ \sig pos ->
        pan2 (in_ sig, pos_ pos)
     -- todo: is the rate correct in ALL cases?:
     mapM (level ~*) =<< foldM addChannels [] sigsStereo
  
  
  -- | "Spreads [a list] of channels across the stereo field. Optional arguments are spread
  --   and center, and equal power levelCompensation. The formula for the stereo position
  --   is:
  -- 
  --   > ((0 .. (n - 1)) * (2 / (n - 1) - 1) * spread + center
  --- splay' :: 
  --- splay' =
  
  --- splayAz ::
  --- splayAz =
  
  -- Don't implement: the geometry is wrong here and it's been deprecated:
  -- splayZ ::
  
  --- linSelectX ::
  --- linSelectX =
  --- linXFade2 ::
  --- linXFade2 =
  
  select :: ToSig s as => s -> [SDBody' as Signal] -> SDBody' as Signal
  select which array = do
     which' <- toSig which
     array' <- mapM toSig array
     addUGen $ UGen (UGName_S "Select") AR (which' : array') 1
  
  --- selectX ::
  --- selectX =
  --- selectXFocus ::
  --- selectXFocus =
  --- xFade2 ::
  --- xFade2 =
  
  -- | Mixes down a list of audio rate inputs to one. 
  -- 
  --   This is more efficient than e.g. @foldl1 (~+)@
  --
  --   If the list is empty this is the same as @dc 0@
  mix :: ToSig s a => [s] -> SDBody' a Signal
  mix [] = dc 0
  mix [x] = toSig x
  mix xs = mix =<< (mapM mix' . chunksOf 4) =<< mapM toSig xs
   where
     mix' :: [Signal] -> SDBody' a 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"
  
  --- numChannels ::
  --- numChannels =
  
  -- | Like 'zipWithM' but if the lists are of different lengths, doesn't shorten the longer one
  addChannels :: (ToSig s0 a, ToSig s1 a) => [s0] -> [s1] -> SDBody' a [Signal]
  addChannels [] xs = mapM toSig xs
  addChannels xs [] = mapM toSig xs
  addChannels (x:xs) (y:ys) = do
     foo <- toSig x ~+ toSig y
     (foo:) <$> addChannels xs ys
  