packages feed

hsc3-0.21: Sound/Sc3/Ugen/Bindings/Composite.hs

-- | Common unit generator graphs.
module Sound.Sc3.Ugen.Bindings.Composite where

import Data.List {- base -}
import Data.Maybe {- base -}

import Sound.Sc3.Common.Enum
import Sound.Sc3.Common.Envelope
import Sound.Sc3.Common.Math
import Sound.Sc3.Common.Math.Filter.Beq
import Sound.Sc3.Common.Math.Operator
import Sound.Sc3.Common.Rate
import Sound.Sc3.Common.Uid
import Sound.Sc3.Common.Unsafe

import Sound.Sc3.Ugen.Bindings.Db
import Sound.Sc3.Ugen.Bindings.Hw
import Sound.Sc3.Ugen.Math
import Sound.Sc3.Ugen.Mce
import Sound.Sc3.Ugen.Types
import Sound.Sc3.Ugen.Util

-- | Generate a localBuf and use setBuf to initialise it.
asLocalBufId :: ID i => i -> [Ugen] -> Ugen
asLocalBufId z xs =
  let b = localBufId z 1 (fromIntegral (length xs))
      s = setBuf' b xs 0
  in mrg2 b s

asLocalBufM :: Uid m => [Ugen] -> m Ugen
asLocalBufM = liftUid1 asLocalBufId

asLocalBuf :: [Ugen] -> Ugen
asLocalBuf = liftUnsafe1 asLocalBufM

-- | balance2 with Mce input.
balanceStereo :: Ugen -> Ugen -> Ugen -> Ugen
balanceStereo sig pos level = let (x, y) = unmce2 sig in balance2 x y pos level

-- | 24db/oct rolloff - 4th order resonant Low Pass Filter
bLowPass4 :: Ugen -> Ugen -> Ugen -> Ugen
bLowPass4 i f rq =
  let (a0, a1, a2, b1, b2) = bLowPassCoef sampleRate f rq
      flt z = sos z a0 a1 a2 b1 b2
  in flt (flt i)

-- | 24db/oct rolloff - 4th order resonant Hi Pass Filter
bHiPass4 :: Ugen -> Ugen -> Ugen -> Ugen
bHiPass4 i f rq =
  let (a0, a1, a2, b1, b2) = bHiPassCoef sampleRate f rq
      flt z = sos z a0 a1 a2 b1 b2
  in flt (flt i)

-- | Buffer reader (no interpolation).
bufRdN :: Int -> Rate -> Ugen -> Ugen -> Loop Ugen -> Ugen
bufRdN n r b p l = bufRd n r b p l NoInterpolation

-- | Buffer reader (linear interpolation).
bufRdL :: Int -> Rate -> Ugen -> Ugen -> Loop Ugen -> Ugen
bufRdL n r b p l = bufRd n r b p l LinearInterpolation

-- | Buffer reader (cubic interpolation).
bufRdC :: Int -> Rate -> Ugen -> Ugen -> Loop Ugen -> Ugen
bufRdC n r b p l = bufRd n r b p l CubicInterpolation

-- | Triggers when a value changes
changed :: Ugen -> Ugen -> Ugen
changed input threshold = abs (hpz1 input) `greater_than` threshold

-- | 'mce' variant of 'lchoose'.
chooseId :: ID m => m -> Ugen -> Ugen
chooseId z = lchooseId z . mceChannels

-- | 'liftUid' of 'choose'.
chooseM :: Uid m => Ugen -> m Ugen
chooseM = liftUid1 chooseId

choose :: Ugen -> Ugen
choose = liftUnsafe1 chooseM

-- | 'clearBuf' of 'localBuf'.
clearLocalBufId :: ID a => a -> Ugen -> Ugen -> Ugen
clearLocalBufId z nc nf = clearBuf (localBufId z nc nf)

clearLocalBufM :: Uid m => Ugen -> Ugen -> m Ugen
clearLocalBufM = liftUid2 clearLocalBufId

clearLocalBuf :: Ugen -> Ugen -> Ugen
clearLocalBuf = liftUnsafe2 clearLocalBufM

-- | Demand rate (:) function.
dconsId :: ID m => (m, m, m) -> Ugen -> Ugen -> Ugen
dconsId (z0, z1, z2) x xs =
  let i = dseqId z0 1 (mce2 0 1)
      a = dseqId z1 1 (mce2 x xs)
  in dswitchId z2 i a

-- | Demand rate (:) function.
dconsM :: (Uid m) => Ugen -> Ugen -> m Ugen
dconsM x xs = do
  i <- dseqM 1 (mce2 0 1)
  a <- dseqM 1 (mce2 x xs)
  dswitchM i a

dcons :: Ugen -> Ugen -> Ugen
dcons = liftUnsafe2 dconsM

-- | Dynamic klang, dynamic sine oscillator bank
dynKlang :: Rate -> Ugen -> Ugen -> Ugen -> Ugen
dynKlang r fs fo s =
  let gen (f : a : ph : xs) = sinOsc r (f * fs + fo) ph * a + gen xs
      gen _ = 0
  in gen (mceChannels s)

-- | Dynamic klank, set of non-fixed resonating filters.
dynKlank :: Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
dynKlank i fs fo ds s =
  let gen (f : a : d : xs) = ringz i (f * fs + fo) (d * ds) * a + gen xs
      gen _ = 0
  in gen (mceChannels s)

-- | Pan2 without (k-rate only) level input
eqPan2 :: Ugen -> Ugen -> Ugen
eqPan2 input pos = pan2 input pos 1

-- | 'linExp' with input range of (-1,1).
exprange :: Ugen -> Ugen -> Ugen -> Ugen
exprange l r s = linExp s (-1) 1 l r

-- | Variant of `exprange` with arguments to make writing post-fix nicer.
in_exprange :: Ugen -> (Ugen, Ugen) -> Ugen
in_exprange s (l, r) = exprange l r s

{- | Variant FFT constructor with default values for hop size (0.5),
window type (0), active status (1) and window size (0).
-}
fft' :: Ugen -> Ugen -> Ugen
fft' buf i = fft buf i 0.5 0 1 0

{- | 'fft' variant that allocates 'localBuf'.

> let c = ffta 'α' 2048 (soundIn 0) 0.5 0 1 0
> in audition (out 0 (ifft c 0 0))
-}
fftAllocId :: ID i => i -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
fftAllocId z nf i h wt a ws =
  let b = localBufId z 1 nf
  in fft b i h wt a ws

fftAllocM :: Uid m => Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> m Ugen
fftAllocM = liftUid6 fftAllocId

fftAlloc :: Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
fftAlloc = liftUnsafe6 fftAllocM

-- | Sum of 'numInputBuses' and 'numOutputBuses'.
firstPrivateBus :: Ugen
firstPrivateBus = numInputBuses + numOutputBuses

-- | Frequency shifter, in terms of 'hilbert' (see also 'freqShift').
freqShift_hilbert :: Ugen -> Ugen -> Ugen -> Ugen
freqShift_hilbert i f p =
  let o = sinOsc ar f (mce [p + 0.5 * pi, p])
      h = hilbert i
  in mix (h * o)

{- | Ugen function to re-trigger an EnvGen envelope.
     Inputs are /gate/ (as set at EnvGen) and /reset/.
     The four state logic is: (1,0)->1 (1,1)->-1 (0,1)->0 (0,0)->0.
     If the gate input to EnvGen.kr is -1 the envelope ramps to zero in one control period.
     The reset input sequence 0,1,0 when the gate is open produces (1,-1,1), which resets the envelope.

>>> map (uncurry gateReset) [(1,0),(1,1),(0,1),(0,0)]
[1,-1,0,0]
-}
gateReset :: Num a => a -> a -> a
gateReset gt tr = gt - (gt * tr * 2)

{- | Variant of 'hilbert' using FFT (with a delay) for better results.
Buffer should be 2048 or 1024.
2048 = better results, more delay.
1024 = less delay, little choppier results.
-}
hilbertFIR :: Ugen -> Ugen -> Ugen
hilbertFIR s b =
  let c0 = fft' b s
      c1 = pv_PhaseShift90 c0
      delay = bufDur kr b
  in mce2 (delayN s delay delay) (ifft' c1)

-- | Variant ifft with default value for window type.
ifft' :: Ugen -> Ugen
ifft' buf = ifft buf 0 0

{-
-- | Linear interpolating variant on index.
indexL :: Ugen -> Ugen -> Ugen
indexL b i =
    let x = index b i
        y = index b (i + 1)
    in linLin (frac i) 0 1 x y
-}

{- | Generalised Klan(k/g) specification rule.  /f/ unwraps inputs, /g/ wraps output.

> let r = [220,0.2,0,219,0.1,1,221,0.1,2]
> in klanx_spec_f id id [220,219,221] [0.2,0.1,0.1] [0,1,2] == r
-}
klanx_spec_f :: (a -> [b]) -> ([b] -> c) -> a -> a -> a -> c
klanx_spec_f f g fr am z = g ((concat . transpose) [f fr, f am, f z])

-- | Format frequency, amplitude and decay time data as required for klank.
klangSpec :: [Ugen] -> [Ugen] -> [Ugen] -> Ugen
klangSpec = klanx_spec_f id mce

-- | Variant of 'klangSpec' for non-Ugen inputs.
klangSpec_k :: Real n => [n] -> [n] -> [n] -> Ugen
klangSpec_k = klanx_spec_f (map constant) mce

-- | Variant of 'klangSpec' for 'Mce' inputs.
klangSpec_mce :: Ugen -> Ugen -> Ugen -> Ugen
klangSpec_mce = klanx_spec_f mceChannels mce

-- | Format frequency, amplitude and decay time data as required for klank.
klankSpec :: [Ugen] -> [Ugen] -> [Ugen] -> Ugen
klankSpec = klanx_spec_f id mce

-- | Variant for non-Ugen inputs.
klankSpec_k :: Real n => [n] -> [n] -> [n] -> Ugen
klankSpec_k = klanx_spec_f (map constant) mce

-- | Variant of 'klankSpec' for 'Mce' inputs.
klankSpec_mce :: Ugen -> Ugen -> Ugen -> Ugen
klankSpec_mce = klanx_spec_f mceChannels mce

-- | Randomly select one of a list of Ugens (initialisation rate).
lchooseId :: ID m => m -> [Ugen] -> Ugen
lchooseId z a = select (iRandId z 0 (fromIntegral (length a))) (mce a)

-- | 'liftUid' of 'lchoose'.
lchooseM :: Uid m => [Ugen] -> m Ugen
lchooseM = liftUid1 lchooseId

lchoose :: [Ugen] -> Ugen
lchoose = liftUnsafe1 lchooseM

-- | 'linExp' of (-1,1).
linExp_b :: Ugen -> Ugen -> Ugen -> Ugen
linExp_b i = linExp i (-1) 1

-- | 'linExp' of (0,1).
linExp_u :: Ugen -> Ugen -> Ugen -> Ugen
linExp_u i = linExp i 0 1

-- | Map from one linear range to another linear range.
linLin :: Ugen -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
linLin = linlin_ma mulAdd

-- | 'linLin' where source is (0,1).
linLin_u :: Ugen -> Ugen -> Ugen -> Ugen
linLin_u i = linLin i 0 1

-- | 'linLin' where source is (-1,1).
linLin_b :: Ugen -> Ugen -> Ugen -> Ugen
linLin_b i = linLin i (-1) 1

-- | Variant with defaults of zero.
localIn' :: Int -> Rate -> Ugen
localIn' nc r = localIn nc r (mce (replicate nc 0))

{- | Generate an 'envGen' Ugen with @fadeTime@ and @gate@ controls.

> import Sound.Sc3
> audition (out 0 (makeFadeEnv 1 * sinOsc ar 440 0 * 0.1))
> withSc3 (send (n_set1 (-1) "gate" 0))
-}
makeFadeEnv :: Double -> Ugen
makeFadeEnv fadeTime =
  let dt = control kr "fadeTime" (realToFrac fadeTime)
      gate_ = control kr "gate" 1
      startVal = dt `less_than_or_equal_to` 0
      env = Envelope [startVal, 1, 0] [1, 1] [EnvLin, EnvLin] (Just 1) Nothing 0
  in envGen kr gate_ 1 0 dt RemoveSynth env

-- | Variant that is randomly pressed.
mouseButtonRand :: Rate -> Ugen -> Ugen -> Ugen -> Ugen
mouseButtonRand rt l r tm =
  let o = lfClipNoiseId 'z' rt 1
  in lag (linLin o (-1) 1 l r) tm

-- | Randomised mouse Ugen (see also 'mouseX'' and 'mouseY'').
mouseRandId :: ID a => a -> Rate -> Ugen -> Ugen -> Warp Ugen -> Ugen -> Ugen
mouseRandId z rt l r ty tm =
  let f = case ty of
        Linear -> linLin
        Exponential -> linExp
        _ -> undefined
  in lag (f (lfNoise1Id z rt 1) (-1) 1 l r) tm

mouseRandM :: Uid m => Rate -> Ugen -> Ugen -> Warp Ugen -> Ugen -> m Ugen
mouseRandM = liftUid5 mouseRandId

mouseRand :: Rate -> Ugen -> Ugen -> Warp Ugen -> Ugen -> Ugen
mouseRand = liftUnsafe5 mouseRandM

-- | Variant that randomly traverses the mouseX space.
mouseXRand :: Rate -> Ugen -> Ugen -> Warp Ugen -> Ugen -> Ugen
mouseXRand = mouseRandId 'x'

-- | Variant that randomly traverses the mouseY space.
mouseYRand :: Rate -> Ugen -> Ugen -> Warp Ugen -> Ugen -> Ugen
mouseYRand = mouseRandId 'y'

-- | Translate onset type string to constant Ugen value.
onsetType :: Num a => String -> a
onsetType s =
  let t = ["power", "magsum", "complex", "rcomplex", "phase", "wphase", "mkl"]
  in fromIntegral (fromMaybe 3 (elemIndex s t))

-- | Onset detector with default values for minor parameters.
onsetsDefault :: Ugen -> Ugen -> Ugen -> Ugen
onsetsDefault c t o = onsets c t o 1 0.1 10 11 1 0

-- | Format magnitude and phase data data as required for packFFT.
packFFTSpec :: [Ugen] -> [Ugen] -> Ugen
packFFTSpec m p =
  let interleave x = concat . zipWith (\a b -> [a, b]) x
  in mce (interleave m p)

-- | Calculate size of accumulation buffer given FFT and IR sizes.
partConv_calcAccumSize :: Int -> Int -> Int
partConv_calcAccumSize fft_size ir_length =
  let partition_size = fft_size `div` 2
      num_partitions = (ir_length `div` partition_size) + 1
  in fft_size * num_partitions

{- | PM oscillator.
cf = carrier frequency, mf = modulation frequency, pm = pm-index = 0.0, mp = mod-phase = 0.0
-}
pmOsc :: Rate -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
pmOsc r cf mf pm mp = sinOsc r cf (sinOsc r mf mp * pm)

{- | Variant of 'poll' that generates an 'mrg' value with the input
signal at left, and that allows a constant /frequency/ input in
place of a trigger.
-}
pollExt :: Ugen -> Ugen -> Ugen -> Ugen -> Ugen
pollExt optTrig in_ label_ trigId =
  let tr = if isConstant optTrig then impulse kr optTrig 0 else optTrig
  in mrg [in_, poll tr in_ label_ trigId]

-- | Variant of 'in'' offset so zero if the first private bus.
privateIn :: Int -> Rate -> Ugen -> Ugen
privateIn nc rt k = in' nc rt (k + firstPrivateBus)

-- | Variant of 'out' offset so zero if the first private bus.
privateOut :: Ugen -> Ugen -> Ugen
privateOut k = out (k + firstPrivateBus)

{- | Apply function /f/ to each bin of an @FFT@ chain, /f/ receives
magnitude, phase and index and returns a (magnitude,phase).
-}
pvcollect :: Ugen -> Int -> (Ugen -> Ugen -> Int -> (Ugen, Ugen)) -> Int -> Int -> Ugen -> Ugen
pvcollect c nf f from to z =
  let m = unpackFFT c nf from to 0
      p = unpackFFT c nf from to 1
      i = [from .. to]
      e = zipWith3 f m p i
      mp = uncurry packFFTSpec (unzip e)
  in packFFT c nf from to z mp

{- | /dur/ and /hop/ are in seconds, /frameSize/ and /sampleRate/ in
frames, though the latter maybe fractional.

> pv_calcPVRecSize 4.2832879818594 1024 0.25 48000.0 == 823299
-}
pv_calcPVRecSize :: Double -> Int -> Double -> Double -> Int
pv_calcPVRecSize dur frame_size hop sample_rate =
  let frame_size' = fromIntegral frame_size
      raw_size = ceiling ((dur * sample_rate) / frame_size') * frame_size
  in ceiling (fromIntegral raw_size * recip hop + 3)

-- | 'rand' with left edge set to zero.
rand0Id :: ID a => a -> Ugen -> Ugen
rand0Id z = randId z 0

-- | 'Uid' form of 'rand0'.
rand0M :: Uid m => Ugen -> m Ugen
rand0M = randM 0

rand0 :: Ugen -> Ugen
rand0 = liftUnsafe1 rand0M

{- | 'rand' with left edge set to negative /n/.
     Note rand2 is also a UnaryOp Ugen, however hsc3 does not store Ids for operators.
-}
rand2Id :: ID a => a -> Ugen -> Ugen
rand2Id z n = randId z (negate n) n

-- | 'Uid' form of 'rand2'.
rand2M :: Uid m => Ugen -> m Ugen
rand2M n = randM (negate n) n

rand2 :: Ugen -> Ugen
rand2 = liftUnsafe1 rand2M

-- | rotate2 with Mce input.
rotateStereo :: Ugen -> Ugen -> Ugen
rotateStereo sig pos = let (x, y) = unmce2 sig in rotate2 x y pos

-- | RMS variant of 'runningSum'.
runningSumRMS :: Ugen -> Ugen -> Ugen
runningSumRMS z n = sqrt (runningSum (z * z) n * recip n)

-- | Mix one output from many sources
selectX :: Ugen -> Ugen -> Ugen
selectX ix xs =
  let s0 = select (roundTo ix 2) xs
      s1 = select (trunc ix 2 + 1) xs
  in xFade2 s0 s1 (fold2 (ix * 2 - 1) 1) 1

-- | Set local buffer values.
setBuf' :: Ugen -> [Ugen] -> Ugen -> Ugen
setBuf' b xs o = Sound.Sc3.Ugen.Bindings.Db.setBuf b o (fromIntegral (length xs)) (mce xs)

-- | Silence.
silent :: Int -> Ugen
silent n = let s = dc ar 0 in mce (replicate n s)

{- | Zero indexed audio input buses.
     Optimises case of consecutive Ugens.

>>> soundIn (mce2 0 1) == in' 2 ar numOutputBuses
True

>>> soundIn (mce2 0 2) == in' 1 ar (numOutputBuses + mce2 0 2)
True
-}
soundIn :: Ugen -> Ugen
soundIn u =
  let r = in' 1 ar (numOutputBuses + u)
  in case u of
      Mce_U m ->
        let n = mceProxies m
        in case uncons n of
            Nothing -> error "soundIn"
            Just (h, t) ->
              if all (== 1) (zipWith (-) t n)
                then in' (length n) ar (numOutputBuses + h)
                else r
      _ -> r

{- | Pan a set of channels across the stereo field.

input, spread:1, level:1, center:0, levelComp:true
-}
splay :: Ugen -> Ugen -> Ugen -> Ugen -> Bool -> Ugen
splay i s l c lc =
  let n = max 2 (fromIntegral (fromMaybe 1 (mceDegree i)))
      m = n - 1
      p = map ((+ (-1.0)) . (* (2 / m))) [0 .. m]
      a = if lc then sqrt (1 / n) else 1
  in mix (pan2 i (s * mce p + c) 1) * l * a

-- | Single tap into a delayline.  ar only.
tap :: Int -> Rate -> Ugen -> Ugen -> Ugen
tap numChannels rt bufnum delaytime =
  let n = delaytime * negate sampleRate
  in playBuf numChannels rt bufnum 1 0 n Loop DoNothing

-- | Randomly select one of several inputs on trigger.
tChooseId :: ID m => m -> Ugen -> Ugen -> Ugen
tChooseId z t a = select (tiRandId z 0 (mceSize a - 1) t) a

-- | Randomly select one of several inputs.
tChooseM :: (Uid m) => Ugen -> Ugen -> m Ugen
tChooseM t a = do
  r <- tiRandM 0 (constant (length (mceChannels a) - 1)) t
  return (select r a)

tChoose :: Ugen -> Ugen -> Ugen
tChoose = liftUnsafe2 tChooseM

-- | Triggered Line, implemented in terms of EnvGen.
tLine :: Rate -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
tLine rt start end dur trig_ =
  let p = envCoord [(0, 0), (0, start), (dur, end)] 1 1 EnvLin
  in envGen rt trig_ 1 0 1 DoNothing p

-- | Triggered xLine, implemented in terms of EnvGen.
tXLine :: Rate -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
tXLine rt start end dur trig_ =
  let p = envCoord [(0, 0), (0, start), (dur, end)] 1 1 EnvExp
  in envGen rt trig_ 1 0 1 DoNothing p

{- | Triangle wave as sum of /n/ sines.
For partial n, amplitude is (1 / square n) and phase is pi at every other odd partial.
-}
triAS :: Int -> Ugen -> Ugen
triAS n f0 =
  let mk_freq i = f0 * fromIntegral i
      mk_amp i = if even i then 0 else 1 / fromIntegral (i * i)
      mk_ph i = if i + 1 `mod` 4 == 0 then pi else 0
      m = [1, 3 .. n]
      param = zip3 (map mk_freq m) (map mk_ph m) (map mk_amp m)
  in sum_opt (map (\(fr, ph, am) -> sinOsc ar fr ph * am) param)

-- | Randomly select one of several inputs on trigger (weighted).
tWChooseId :: ID m => m -> Ugen -> Ugen -> Ugen -> Ugen -> Ugen
tWChooseId z t a w n =
  let i = tWindexId z t n w
  in select i a

-- | Randomly select one of several inputs (weighted).
tWChooseM :: (Uid m) => Ugen -> Ugen -> Ugen -> Ugen -> m Ugen
tWChooseM t a w n = do
  i <- tWindexM t n w
  return (select i a)

tWChoose :: Ugen -> Ugen -> Ugen -> Ugen -> Ugen
tWChoose = liftUnsafe4 tWChooseM

-- | Unpack an FFT chain into separate demand-rate FFT bin streams.
unpackFFT :: Ugen -> Int -> Int -> Int -> Ugen -> [Ugen]
unpackFFT c nf from to w = map (\i -> unpack1FFT c (constant nf) (constant i) w) [from .. to]

-- | VarLag in terms of envGen.  Note: in SC3 curvature and warp are separate arguments.
varLag_env :: Ugen -> Ugen -> Envelope_Curve Ugen -> Maybe Ugen -> Ugen
varLag_env in_ time warp start =
  let rt = rateOf in_
      start_ = fromMaybe in_ start
      e = Envelope [start_, in_] [time] [warp] Nothing Nothing 0
      -- e[6] = curve; e[7] = curvature;
      time_ch = if rateOf time == InitialisationRate then 0 else changed time 0
      tr = changed in_ 0 + time_ch + impulse rt 0 0
  in envGen rt tr 1 0 1 DoNothing e

{- | k channel white noise.

> whiteNoiseN 2 ar * 0.1
-}
whiteNoiseMN :: Uid m => Int -> Rate -> m Ugen
whiteNoiseMN k r = fmap mce (mapM (\_ -> whiteNoiseM r) [1 .. k])

whiteNoiseN :: Int -> Rate -> Ugen
whiteNoiseN k = liftUnsafe1 (whiteNoiseMN k)

{- | If @z@ isn't a sink node route to an @out@ node writing to @bus@.
     If @fadeTime@ is given multiply by 'makeFadeEnv'.

> import Sound.Sc3 {\- hsc3 -\}
> audition (wrapOut (Just 1) (sinOsc ar 440 0 * 0.1))

> import Sound.Osc {\- hosc -\}
> withSc3 (sendMessage (n_set1 (-1) "gate" 0))
-}
wrapOut :: Maybe Double -> Ugen -> Ugen
wrapOut fadeTime z =
  if isSink z
    then z
    else out (control kr "out" 0) (maybe z ((* z) . makeFadeEnv) fadeTime)

-- * wslib

-- | Cross-fading version of 'playBuf'.
playBufCF :: Int -> Ugen -> Ugen -> Ugen -> Ugen -> Loop Ugen -> Ugen -> Int -> Ugen
playBufCF nc bufnum rate trigger startPos loop lag' n =
  let trigger' =
        if rateOf trigger == DemandRate
          then tDuty ar trigger 0 DoNothing 1 0
          else trigger
      index' = stepper trigger' 0 0 (constant n - 1) 1 0
      on =
        map
          (\i -> inRange index' (i - 0.5) (i + 0.5))
          [0 .. constant n - 1]
      rate' = case rateOf rate of
        DemandRate -> map (\on' -> demand on' 0 rate) on
        ControlRate -> map (gate rate) on
        AudioRate -> map (gate rate) on
        InitialisationRate -> map (const rate) on
      startPos' =
        if rateOf startPos == DemandRate
          then demand trigger' 0 startPos
          else startPos
      lag'' = 1 / lag'
      s =
        zipWith
          ( \on' r ->
              let p = playBuf nc ar bufnum r on' startPos' loop DoNothing
              in p * sqrt (slew on' lag'' lag'')
          )
          on
          rate'
  in sum_opt s

-- * adc

-- | An oscillator that reads through a table once.
osc1 :: Rate -> Ugen -> Ugen -> DoneAction Ugen -> Ugen
osc1 rt buf dur doneAction =
  let ph = line rt 0 (bufFrames ir buf - 1) dur doneAction
  in bufRd 1 rt buf ph NoLoop LinearInterpolation