packages feed

musicw-0.2.0: src/Sound/MusicW/Transformations.hs

module Sound.MusicW.Transformations where

import Control.Monad

import Sound.MusicW.AudioContext
import Sound.MusicW.Node
import Sound.MusicW.SynthDef
import Sound.MusicW.FloatArraySpec

clipAt :: AudioIO m => Double -> NodeRef -> SynthDef m NodeRef
clipAt x input = do
  let nSamples = 65536 :: Int
  let portion = (1.0 - x) / 2.0
  let nConstSamples = floor $ portion * fromIntegral nSamples
  let left = Const (nConstSamples-1) (-x) EmptyArray
  let mid = listToArraySpec [((2.0 * fromIntegral i) / (fromIntegral  nSamples)) - 1 | i <- [nConstSamples..(nSamples-nConstSamples)-1]]
  let right = Const (nConstSamples-1) x EmptyArray
  waveShaper (Right $ mconcat [left, mid, right]) NoOversampling input

-- | A panner not based on the WebAudio API panner node but rather on
-- straightforward equal power paining using two gain nodes.

equalPowerPan :: AudioIO m => Double -> NodeRef -> SynthDef m NodeRef
equalPowerPan x input = do
  left <- gain (gainFromDistance x) input
  right <- gain (gainFromDistance (1-x)) input
  channelMerger [left,right]

gainFromDistance :: Double -> Double
gainFromDistance x | abs x > 1 = 0
gainFromDistance x | otherwise = cos $ abs x * pi / 2

-- | circlePan pans a given single-channel signal around a circle of outputs
-- positions of 0 or 1 pan directly to the first output channel
-- positions between 0 and 1 move through the other outputs

circlePan :: AudioIO m => Int -> Double -> NodeRef -> SynthDef m NodeRef
circlePan 0 _ _ = constantSource 0
circlePan 1 _ input = return input
circlePan n pos input = do
  let pos' = pos - (fromIntegral $ floor pos)
  let pos'' = (fromIntegral n) * pos'
  let gs = fmap (gainFromDistance . ((-) pos'')) $ take n [0..]
  let g0 = gainFromDistance $ pos'' - (fromIntegral n)
  let gs' = (head gs + g0):tail gs -- taking account of "wrap-around"
  xs <- mapM (\x -> gain x input) gs'
  channelMerger xs

-- | splay pans a list of signals by distributing them equally over an array
-- of speakers/outputs. When there are more than 2 outputs (as indicated by first
-- argument) the signals are equidistantly distributed around a "circle" (using
-- circlePan, above) with the first signal in the list at position 0. When there
-- are only 2 outputs, equalPowerPan (above) is used instead, and the signals are
-- panned such that a single signal will be centre panned, two signals will be
-- panned hard left and right, and two or more signals will be distributed so that
-- the first signal is guaranteed to be hard left and the last signal is hard right.

splay :: AudioIO m => Int -> [NodeRef] -> SynthDef m NodeRef
splay n [] = constantSource 0 >>= circlePan n 0
splay 2 (i:[]) = equalPowerPan 0.5 i
splay n (i:[]) = circlePan n 0.5 i
splay 2 inputs = do
  let i = 1/(fromIntegral $ length inputs - 1)
  let positions = fmap ((*) i) $ take (length inputs) [0..]
  xs <- zipWithM equalPowerPan positions inputs
  mix xs
splay n inputs = do
  let i = 1/(fromIntegral $ length inputs)
  let positions = fmap ((*) i) $ take (length inputs) [0..]
  xs <- zipWithM (circlePan n) positions inputs
  mix xs