hdf-0.11: Sound/DF/UGen.hs
-- | Data flow node functions, or unit generators.
module Sound.DF.UGen where
import Control.Monad
import Sound.DF.Node
-- * Primitive unit generators
-- | Uniform input type operator.
uniform_operator :: Type -> Int -> String -> [Node] -> Node
uniform_operator t n s ps =
if all (\p -> node_type p == t) ps
then A s ps (replicate n (Port t 1))
else error (show ("output operator", ps))
-- | Single channel output.
out1 :: Node -> Node
out1 p = uniform_operator Real_Type 0 "df_out1" [p]
-- | Two channel output.
out2 :: (Node, Node) -> Node
out2 (p, q) = uniform_operator Real_Type 0 "df_out2" [p, q]
-- | Three channel output.
out3 :: (Node, Node, Node) -> Node
out3 (p, q, r) = uniform_operator Real_Type 0 "df_out3" [p, q, r]
-- | Operating sample rate.
sample_rate :: Node
sample_rate = A "df_sample_rate" [] [Port Real_Type 1]
-- | Equal to.
eq :: Node -> Node -> Node
eq = numerical_comparison_operator "df_eq"
-- | If 'p' then 'q' else 'r'.
select2 :: Node -> Node -> Node -> Node
select2 p q r =
if node_type p == Boolean_Type &&
node_type q == node_type r
then A "df_select2" [p, q, r] [Port (node_type q) 1]
else error (show ("select2", p, q, r))
-- | Binary boolean valued operator.
logical_operator :: String -> Node -> Node -> Node
logical_operator s p q =
if node_type p == Boolean_Type && node_type q == Boolean_Type
then A s [p, q] [Port Boolean_Type 1]
else error (show ("logical operator", s, p, q))
-- | Logical and.
n_and :: Node -> Node -> Node
n_and = logical_operator "df_and"
-- | Logical or.
n_or :: Node -> Node -> Node
n_or = logical_operator "df_or"
-- | Buffer read.
b_read :: Node -> Node -> Node
b_read p q =
if node_type p == Integer_Type && node_type q == Integer_Type
then A "df_b_read" [p, q] [Port Real_Type 1]
else error (show ("b_read", p, q))
-- | Buffer write.
b_write :: Node -> Node -> Node -> Node
b_write p q r =
if node_type p == Integer_Type &&
node_type q == Integer_Type &&
node_type r == Real_Type
then A "df_b_write" [p, q, r] []
else error (show ("b_write", p, q, r))
-- | White noise (0, 1).
white_noise_u :: Node -> Node
white_noise_u p = A "df_random" [p] [Port Real_Type 1]
-- * Ordinary unit generators
-- | Linear pan.
pan2 :: Node -> Node -> (Node, Node)
pan2 p q = (p * q, p * (q - 1.0))
-- | Reversed tuple constructor, (ie. @flip (,)@)
swap :: a -> b -> (b, a)
swap = flip (,)
-- | Duplicate a value into a tuple.
split :: a -> (a, a)
split p = (p, p)
-- | Single sample delay with indicated initial value.
unit_delay :: ID m => Constant -> Node -> m Node
unit_delay y0 = rec y0 . swap
-- | Single place infinte impulse response filter with indicated initial value.
iir1 :: ID m => Constant -> (Node -> Node -> Node) -> Node -> m Node
iir1 y0 f i = rec y0 (split . f i)
-- | Two place infinte impulse response filter. Inputs are: /f/=
-- function @(\x0 y1 y2 -> y0)@, /i/ = input signal.
--
-- > do { c1 <- iir2 (\x y1 _ -> x + y1) 0.001
-- > ; o1 <- sin_osc (c1 + 220.0) 0
-- > ; c2 <- iir2 (\x _ y2 -> x + y2) 0.001
-- > ; o2 <- sin_osc (c2 + 220.0) 0
-- > ; audition [] (out2 (o1 * 0.1, o2 * 0.1)) }
iir2 :: ID m => (Node -> Node -> Node -> Node) -> Node -> m Node
iir2 f i = recm
(Real_Constant 0)
(liftM split . (\y1 -> do y2 <- unit_delay (Real_Constant 0) y1
return (f i y1 y2)))
-- | Single place finte impulse response filter.
fir1 :: ID m => (Node -> Node -> Node) -> Node -> m Node
fir1 f i = do
x1 <- unit_delay (Real_Constant 0) i
return (f i x1)
-- | Two place finte impulse response filter.
fir2 :: ID m => (Node -> Node -> Node -> Node) -> Node -> m Node
fir2 f i = do
x1 <- unit_delay (Real_Constant 0) i
x2 <- unit_delay (Real_Constant 0) x1
return (f i x1 x2)
-- | Ordinary biquad filter section.
biquad :: ID m => (Node -> Node -> Node -> Node -> Node -> Node) -> Node -> m Node
biquad f i = recm
(Real_Constant 0)
(liftM split . (\y1 -> do x1 <- unit_delay (Real_Constant 0) i
x2 <- unit_delay (Real_Constant 0) x1
y2 <- unit_delay (Real_Constant 0) y1
return (f i x1 x2 y1 y2)))
-- | Counter from indicated initial value.
counter :: ID m => Constant -> Node -> m Node
counter y0 = iir1 y0 (+)
-- | Environment value, equal to @'two_pi' / 'sample_rate'@.
radians_per_sample :: Node
radians_per_sample = two_pi / sample_rate
-- | r = cycle (two-pi), hz = frequency, sr = sample rate
hz_to_incr :: Node -> Node -> Node -> Node
hz_to_incr r hz sr = (r / sr) * hz
-- | Two pi.
two_pi :: Floating a => a
two_pi = 2.0 * pi
-- | If 'q >= p' then 'q - p' else 'q'.
clipr :: Node -> Node -> Node
clipr p q = select2 (q `n_gte` p) (q - p) q
-- | r = right hand edge, ip = initial phase, x = increment
phasor :: ID m => Constant -> Node -> Node -> m Node
phasor ip r = iir1 ip (\x y1 -> clipr r (x + y1))
-- | Sine oscillator. Inputs are: /f/ = frequency (in hz), /ip/ =
-- initial phase.
--
-- > do { o <- sin_osc 440.0 0.0
-- > ; audition [] (out1 (o * 0.1)) }
--
-- Used as both Oscillator and LFO.
--
-- > do { f <- sin_osc 4.0 0.0
-- > ; o <- sin_osc (f * 400.0 + 400.0) 0.0
-- > ; audition [] (out1 (o * 0.1)) }
--
-- Cancellation.
--
-- > do { o1 <- sin_osc 440.0 0.0
-- > ; o2 <- sin_osc 440.0 pi
-- > ; audition [] (out1 (o1 + o2)) }
sin_osc :: ID m => Node -> Double -> m Node
sin_osc f ip = do
p <- phasor (Real_Constant ip) two_pi (hz_to_incr two_pi f sample_rate)
return (sin p)
-- | Non-band limited sawtooth oscillator. Output ranges from -1 to +1.
-- Inputs are: /f/ = frequency (in hertz), /ip/ = initial phase (0,2).
--
-- > do { o <- lf_saw 500.0 1.0
-- > ; audition [] (out1 (o * 0.1)) }
--
-- Used as both Oscillator and LFO.
--
-- > do { f <- lf_saw 4.0 0.0
-- > ; o <- lf_saw (f * 400.0 + 400.0) 0.0
-- > ; audition [] (out1 (o * 0.1)) }
lf_saw :: ID m => Node -> Double -> m Node
lf_saw f ip = do
p <- phasor (Real_Constant ip) 2.0 (hz_to_incr 2.0 f sample_rate)
return (p - 1.0)
-- | Non-band-limited pulse oscillator. Outputs a high value of one
-- and a low value of zero. Inputs are: /f/ = frequency (in hertz),
-- /ip/ = initial phase (0, 1), /w/ = pulse width duty cycle (0, 1).
--
-- > do { o1 <- fmap (\x -> x * 200.0 + 200.0) (lf_pulse 3.0 0.0 0.3)
-- > ; o2 <- fmap (\x -> x * 0.1) (lf_pulse o1 0.0 0.2)
-- > ; audition [] (out1 o2) }
lf_pulse :: ID m => Node -> Double -> Node -> m Node
lf_pulse f ip w =
do p <- phasor (Real_Constant ip) 1.0 (hz_to_incr 1.0 f sample_rate)
return (select2 (p `n_gte` w) 0.0 1.0)
-- | Midi note number to cycles per second.
midi_cps :: Floating a => a -> a
midi_cps a = 440.0 * (2.0 ** ((a - 69.0) * (1.0 / 12.0)))
-- | Multiply and add.
mul_add :: Num a => a -> a -> a -> a
mul_add i m a = (i * m) + a
-- | Calculate feedback multipler given /delay/ and /decay/ times.
calc_fb :: Floating a => a -> a -> a
calc_fb delayt decayt = exp ((log 0.001 * delayt) / decayt)
-- | Delay.
delay :: ID m => Node -> Node -> Node -> m Node
delay b s n = do
wi <- phasor (Integer_Constant 0) n 1
let ri = clipr n (wi + 1)
return (mrg (b_read b ri) (b_write b wi s))
-- | Non-interpolating comb filter. Inputs are: /b/ = buffer index,
-- /i/ = input signal, /dl/ = delay time, /dc/ = decay time.
--
-- All times are in seconds. The decay time is the time for the
-- echoes to decay by @60@ decibels. If this time is negative then the
-- feedback coefficient will be negative, thus emphasizing only odd
-- harmonics at an octave lower.
--
-- Comb used as a resonator. The resonant fundamental is equal to
-- reciprocal of the delay time.
--
-- > do { n <- white_noise_m
-- > ; dt <- let f x = lin_exp (x + 2.0) 1.0 2.0 0.0001 0.01
-- > in fmap f (lf_saw 0.1 0.0)
-- > ; c <- buf_comb_n 0 (n * 0.1) dt 0.2
-- > ; audition [b_alloc 0 44100] (out1 c) }
--
-- Comb used as an echo.
--
-- > do { i <- impulse 0.5 0.0
-- > ; n <- white_noise_m
-- > ; e <- decay (i * 0.5) 0.2
-- > ; c <- buf_comb_n 0 (e * n) 0.2 3.0
-- > ; audition [b_alloc 0 44100] (out1 c) }
buf_comb_n :: ID m => Node -> Node -> Node -> Node -> m Node
buf_comb_n b s dlt dct = do
let n = n_lrint (dlt * sample_rate)
fb = calc_fb dlt dct
c i = do x <- delay b i n
return (split (s + (fb * x)))
recm (Real_Constant 0) c
-- | Resonant low pass filter. Inputs are: /i/ = input signal, /f/ =
-- frequency (hertz), /rq/ = reciprocal of Q (resonance).
--
-- > do { n <- white_noise_m
-- > ; f <- fmap (\x -> x * 40.0 + 220.0) (sin_osc 0.5 0.0)
-- > ; r <- rlpf n f 0.1
-- > ; audition [] (out1 r) }
rlpf :: ID m => Node -> Node -> Node -> m Node
rlpf i f r =
let qr = max 0.001 r
pf = f * radians_per_sample
d = tan (pf * qr * 0.5)
c = (1.0 - d) / (1.0 + d)
b1 = (1.0 + c) * cos pf
b2 = negate c
a0 = (1.0 + c - b1) * 0.25
in iir2 (\x y1 y2 -> a0 * x + b1 * y1 + b2 * y2) i
-- | Constrain p in (-q, q).
clip2 :: Node -> Node -> Node
clip2 p q =
let nq = negate q
in min q (max p nq)
-- | White noise (-1, 1).
white_noise :: Node -> Node
white_noise p = white_noise_u p * 2.0 - 1.0
-- | White noise (-1, 1). Generates noise whose spectrum has equal
-- power at all frequencies.
--
-- > do { n <- white_noise_m
-- > ; audition [] (out1 (n * 0.1)) }
white_noise_m :: ID m => m Node
white_noise_m = do
i <- generateID
return (white_noise (n_integer_constant i))
-- | Brown noise (-1, 1). Generates noise whose spectrum falls off in
-- power by 6 dB per octave.
--
-- > do { n <- brown_noise_m
-- > ; audition [] (out1 (n * 0.1)) }
--
-- > do { n <- brown_noise_m
-- > ; let f = lin_exp n (-1.0) 1.0 64.0 9600.0
-- > in do { o <- sin_osc f 0
-- > ; audition [] (out1 (o * 0.1)) } }
brown_noise_m :: ID m => m Node
brown_noise_m = do
w <- white_noise_m
let w8 = w / 8.0
iir1
(Real_Constant 0)
(\x y1 -> let z = x + y1
r = select2 (z `n_lt` (-1.0)) ((-2.0) - z) z
in select2 (z `n_gt` 1.0) (2.0 - z) r)
w8
-- | Two zero fixed midpass filter.
bpz2 :: ID m => Node -> m Node
bpz2 = fir2 (\x _ x2 -> (x - x2) * 0.5)
-- | Two zero fixed midcut filter.
brz2 :: ID m => Node -> m Node
brz2 = fir2 (\x _ x2 -> (x + x2) * 0.5)
-- | Two point average filter
lpz1 :: ID m => Node -> m Node
lpz1 = fir1 (\x x1 -> (x + x1) * 0.5)
-- | Two zero fixed lowpass filter
lpz2 :: ID m => Node -> m Node
lpz2 = fir2 (\x x1 x2 -> (x + (2.0 * x1) + x2) * 0.25)
-- | One pole filter.
one_pole :: ID m => Node -> Node -> m Node
one_pole i cf = iir1
(Real_Constant 0)
(\x y1 -> ((1.0 - abs cf) * x) + (cf * y1))
i
-- | One zero filter.
one_zero :: ID m => Node -> Node -> m Node
one_zero i cf = fir1 (\x x1 -> ((1.0 - abs cf) * x) + (cf * x1)) i
-- | Second order filter section.
sos :: ID m => Node -> Node -> Node -> Node -> Node -> Node -> m Node
sos i a0 a1 a2 b1 b2 =
let f x x1 x2 y1 y2 = a0*x + a1*x1 + a2*x2 + b1*y1 + b2*y2
in biquad f i
-- | Impulse oscillator (non band limited).
-- Outputs non band limited single sample impulses.
-- Inputs are: /f/ = frequency (in hertz), /ip/ = phase offset (0..1)
--
-- > do { o <- impulse 800.0 0.0
-- > ; audition [] (out1 (o * 0.1)) }
--
-- > do { f <- fmap (\x -> x * 2500.0 + 2505.0) (sin_osc 0.25 0.0)
-- > ; o <- impulse f 0.0
-- > ; audition [] (out1 (o * 0.1)) }
impulse :: ID m => Node -> Double -> m Node
impulse f ip = do
let i = hz_to_incr 1.0 f sample_rate
p <- phasor (Real_Constant ip) 1.0 i
x1 <- unit_delay (Real_Constant 0) p
let s = (x1 `n_lt` 0.5) `n_and` (p `n_gte` 0.5)
return (select2 s 1.0 0.0)
-- | A two pole resonant filter with zeroes at z = +/- 1. Based on
-- K. Steiglitz, \"A Note on Constant-Gain Digital Resonators\",
-- /Computer Music Journal/, vol 18, no. 4, pp. 8-10, Winter 1994.
-- The reciprocal of Q is used rather than Q because it saves a divide
-- operation inside the unit generator.
--
-- Inputs are: /i/ = input signal, /f/ = resonant frequency (in
-- hertz), /rq/ = bandwidth ratio (reciprocal of Q); where /rq/ =
-- bandwidth / centerFreq.
--
-- > do { n <- white_noise_m
-- > ; r <- resonz (n * 0.5) 440.0 0.1
-- > ; audition [] (out1 r) }
--
-- Modulate frequency
--
-- > do { n <- white_noise_m
-- > ; f <- fmap (\x -> x * 3500.0 + 4500.0) (lf_saw 0.1 0.0)
-- > ; r <- resonz (n * 0.5) f 0.05
-- > ; audition [] (out1 r) }
resonz :: ID m => Node -> Node -> Node -> m Node
resonz i f rq =
let ff = f * radians_per_sample
b = ff * rq
r = 1.0 - b * 0.5
two_r = 2.0 * r
r2 = r * r
ct = (two_r * cos ff) / (1.0 + r2)
b1 = two_r * ct
b2 = negate r2
a0 = (1.0 - r2) * 0.5
in iir2 (\x y1 y2 -> let y0 = x + b1 * y1 + b2 * y2
in a0 * (y0 - y2)) i
-- | Sample and hold. Holds input signal value when triggered. Inputs
-- are: /i/ = input signal, /t/ = trigger (non-positive to positive).
--
-- > do { n <- white_noise_m
-- > ; i <- impulse 9.0 0.0
-- > ; l <- latch n i
-- > ; o <- sin_osc (l * 400.0 + 500.0) 0.0
-- > ; audition [] (out1 (o * 0.2)) }
latch :: ID m => Node -> Node -> m Node
latch i t = iir1
(Real_Constant 0)
(select2 (t `n_gt` 0.0))
i
-- | Linear range conversion.
--
-- > map (\i -> lin_lin i (-1) 1 0 1) [-1, -0.9 .. 1.0]
--
-- > do { s <- lf_saw 1.0 0.0
-- > ; o <- sin_osc (lin_lin s (-1.0) 1.0 220.0 440.0) 0.0
-- > ; audition [] (out1 (o * 0.1)) }
lin_lin :: Fractional a => a -> a -> a -> a -> a -> a
lin_lin i in_l in_r out_l out_r =
let s = (out_r - out_l) / (in_r - in_l)
o = out_l - (s * in_l)
in (i * s) + o
-- | Exponential range conversion.
--
-- > map (\i -> lin_exp i 1 2 1 3) [1, 1.1 .. 2]
--
-- > do { s <- lf_saw 0.25 0.0
-- > ; o <- sin_osc (lin_exp (s + 1.0) 0.0 2.0 220.0 440.0) 0.0
-- > ; audition [] (out1 (o * 0.1)) }
lin_exp :: Floating a => a -> a -> a -> a -> a -> a
lin_exp i in_l in_r out_l out_r =
let rt = out_r / out_l
rn = 1.0 / (in_r - in_l)
rr = rn * negate in_l
in out_l * (rt ** (i * rn + rr))
-- | Exponential decay. Inputs are: /i/ = input signal, /t/ = decay
-- time. This is essentially the same as Integrator except that
-- instead of supplying the coefficient directly, it is caculated from
-- a 60 dB decay time. This is the time required for the integrator to
-- lose 99.9 % of its value or -60dB. This is useful for exponential
-- decaying envelopes triggered by impulses.
--
-- Used as an envelope.
--
-- > do { n <- brown_noise_m
-- > ; f <- lf_saw 0.1 0.0
-- > ; i <- impulse (lin_lin f (-1.0) 1.0 2.0 5.0) 0.25
-- > ; e <- decay i 0.2
-- > ; audition [] (out1 (e * n)) }
decay :: ID m => Node -> Node -> m Node
decay i dt =
let b1 = exp (log 0.001 / (dt * sample_rate))
in iir1 (Real_Constant 0) (\x y1 -> x + b1 * y1) i
-- | Exponential decay (equvalent to @decay dcy - decay atk@).
decay2 :: ID m => Node -> Node -> Node -> m Node
decay2 i atk dcy = liftM2 (-) (decay i dcy) (decay i atk)
-- | Single sample delay.
delay1 :: ID m => Node -> m Node
delay1 = iir1 (Real_Constant 0) (\_ y1 -> y1)
-- | Two sample delay.
delay2 :: ID m => Node -> m Node
delay2 = iir2 (\_ _ y2 -> y2)
-- | Simple averaging filter. Inputs are: /i/ = input signal, /t/ =
-- lag time.
--
-- > do { s <- sin_osc 0.05 0.0
-- > ; let f = lin_lin s (-1.0) 1.0 220.0 440.0
-- > in do { o <- sin_osc f 0.0
-- > ; f' <- lag f 1.0
-- > ; o' <- sin_osc f' 0.0
-- > ; audition [] (out2 (o * 0.2, o' * 0.2)) } }
lag :: ID m => Node -> Node -> m Node
lag i t = let b1 = exp (log (0.001 / (t * sample_rate)))
in iir1 (Real_Constant 0) (\x y1 -> x + b1 * (y1 - x)) i
-- | Nested lag filter.
lag2 :: ID m => Node -> Node -> m Node
lag2 i t = do
a <- lag i t
lag a t
-- | Twice nested lag filter.
lag3 :: ID m => Node -> Node -> m Node
lag3 i t = do
a <- lag i t
b <- lag a t
lag b t