delays.lib
Delays library. Its official prefix is de.
This library provides reusable building blocks for delay-based processing: single and multi-tap delays, fractional delays and utilities for echo and spatial effects.
The Delays library is organized into 4 sections:
References
Basic Delay Functions
(de.)delay
Simple d samples delay where n is the maximum delay length as a number of
samples. Unlike the @ delay operator, here the delay signal d is explicitly
bounded to the interval [0..n]. The consequence is that delay will compile even
if the interval of d can't be computed by the compiler.
delay is a standard Faust function.
Usage
_ : delay(n,d) : _
Where:
n: the max delay length in samplesd: the delay length in samples (integer)
Test
de = library("delays.lib");
os = library("oscillators.lib");
delay_test = os.tosc(440) : de.delay(44100, 22050);
TODO: add MBH np2
(de.)fdelay
Simple d samples fractional delay based on 2 interpolated delay lines where n is
the maximum delay length as a number of samples.
fdelay is a standard Faust function.
Usage
_ : fdelay(n,d) : _
Where:
n: the max delay length in samplesd: the delay length in samples (float)
Test
de = library("delays.lib");
os = library("oscillators.lib");
ba = library("basics.lib");
ma = library("maths.lib");
no = library("noises.lib");
fdelay_test = os.tosc(440) : de.fdelay(44100, 22050.5);
fdelay_slider_test = os.tosc(440) : de.fdelay(44100, hslider("fdelay:d", 22050.5, 0, 44100, 0.1));
fdelay_modulated_test = no.noise : de.fdelay(256, 16 + 112*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
(de.)sdelay
s(mooth)delay: a mono delay that doesn't click and doesn't transpose when the delay time is changed.
Usage
_ : sdelay(n,it,d) : _
Where:
n: the max delay length in samplesit: interpolation time (in samples), for example 1024d: the delay length in samples (float)
Test
de = library("delays.lib");
os = library("oscillators.lib");
ba = library("basics.lib");
ma = library("maths.lib");
no = library("noises.lib");
sdelay_test = os.tosc(440) : de.sdelay(44100, 1024, 22050.5);
sdelay_slider_test = os.tosc(440) : de.sdelay(44100, 1024, hslider("sdelay:d", 22050.5, 0, 44100, 0.1));
sdelay_jump_test = no.noise : de.sdelay(4096, 1024, select2(sq, 1000, 3000)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; };
(de.)prime_power_delays
Prime Power Delay Line Lengths: outputs N constant delay lengths in samples,
each one a power of a distinct prime.
Delay line i is a power of the ith prime, chosen closest (in the log domain)
to a length spaced geometrically between the travel times of pathmin and
pathmax at the speed of sound. Each power is capped at the largest one that does
not exceed 8192 samples, so that a delay line of 8192 samples holds all of them
(8192 samples is 63.7 m at 44.1 kHz; at higher sampling rates, longer paths are capped).
Usage
prime_power_delays(N,pathmin,pathmax) : si.bus(N)
Where:
N: positive integer up to 16 (for higher powers of 2, extend 'primes' array below)pathmin: minimum acoustic ray length in the reverberator (in meters)pathmax: maximum acoustic ray length (meters) - think "room size"
The N outputs are the delay-line lengths in samples, all at most 8192.
Test
de = library("delays.lib");
prime_power_delays_test = de.prime_power_delays(4, 1, 10);
prime_power_delays_capped_test = de.prime_power_delays(16, 46, 63);
prime_power_delays_N1_test = de.prime_power_delays(1, 1, 10);
References
Lagrange Interpolation
(de.)fdelaylti, (de.)fdelayltv
Fractional delay line using Lagrange interpolation.
Usage
_ : fdelaylti(N, n, d) : _
_ : fdelayltv(N, n, d) : _
Where:
N: the order of the Lagrange interpolation polynomial (1, 2, 3, ...), a constant numerical expressionn: the max delay length in samplesd: the delay length in samples
fdelaylti is most efficient, but designed for constant/slowly-varying delay.
fdelayltv is more expensive and more robust when the delay varies rapidly.
Note: the requested delay should not be less than (N-1)/2.
Test
de = library("delays.lib");
os = library("oscillators.lib");
ba = library("basics.lib");
ma = library("maths.lib");
no = library("noises.lib");
fdelaylti_test = os.tosc(440) : de.fdelaylti(3, 44100, 22050.5);
fdelayltv_test = os.tosc(440) : de.fdelayltv(3, 44100, 22050.5);
fdelaylti_slider_test = os.tosc(440) : de.fdelaylti(3, 44100, hslider("fdelaylti:d", 22050.5, 1, 44100, 0.1));
fdelaylti_modulated_test = no.noise : de.fdelaylti(3, 256, 2 + 62*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
fdelayltv_slider_test = os.tosc(440) : de.fdelayltv(3, 44100, hslider("fdelayltv:d", 22050.5, 1, 44100, 0.1));
fdelayltv_modulated_test = no.noise : de.fdelayltv(3, 256, 2 + 62*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
fdelayltv_max_test = no.noise : de.fdelayltv(3, 64, 63.3);
References
- https://ccrma.stanford.edu/~jos/pasp/Lagrange_Interpolation.html
- Fixed-delay case
- Variable-delay case
- Timo I. Laakso et al., "Splitting the Unit Delay - Tools for Fractional Delay Filter Design", IEEE Signal Processing Magazine, vol. 13, no. 1, pp. 30-60, Jan 1996.
- Philippe Depalle and Stephan Tassart, "Fractional Delay Lines using Lagrange Interpolators", ICMC Proceedings, pp. 341-343, 1996.
(de.)fdelay[N]
For convenience, fdelay1, fdelay2, fdelay3, fdelay4, fdelay5
are also available where N is the order of the interpolation, built using fdelayltv.
Usage
_ : fdelay1(maxdel,d) : _
_ : fdelay2(maxdel,d) : _
_ : fdelay3(maxdel,d) : _
_ : fdelay4(maxdel,d) : _
_ : fdelay5(maxdel,d) : _
Where:
maxdel: maximum delay in samplesd: current (float) delay in samples; must satisfyd > (N-1)/2for order-NLagrange interpolation (seede.fdelayltv)
Test
de = library("delays.lib");
ba = library("basics.lib");
ma = library("maths.lib");
no = library("noises.lib");
os = library("oscillators.lib");
fdelay1_test = os.tosc(440) : de.fdelay1(44100, 22050.5);
fdelay2_test = os.tosc(440) : de.fdelay2(44100, 22050.5);
fdelay3_test = os.tosc(440) : de.fdelay3(44100, 22050.5);
fdelay4_test = os.tosc(440) : de.fdelay4(44100, 22050.5);
fdelay5_test = os.tosc(440) : de.fdelay5(44100, 22050.5);
fdelay3_modulated_test = no.noise : de.fdelay3(256, 2 + 62*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
Thiran Allpass Interpolation
Thiran Allpass Interpolation.
References
(de.)fdelay[N]a
Delay lines interpolated using Thiran allpass interpolation.
Usage
_ : fdelay[N]a(n, d) : _
(exactly like fdelay)
Where:
N=1,2,3, or 4is the order of the Thiran interpolation filter (constant numerical expression), and the delay argument is at leastN-1/2. First-order:dat least 0.5, second-order:dat least 1.5, third-order:dat least 2.5, fourth-order:dat least 3.5.n: the max delay length in samplesd: the delay length in samples
Test
de = library("delays.lib");
os = library("oscillators.lib");
ba = library("basics.lib");
ma = library("maths.lib");
no = library("noises.lib");
fdelay2a_test = os.tosc(440) : de.fdelay2a(44100, 22050.5);
fdelay1a_test = os.tosc(440) : de.fdelay1a(44100, 22050.5);
fdelay3a_test = os.tosc(440) : de.fdelay3a(44100, 22050.5);
fdelay4a_test = os.tosc(440) : de.fdelay4a(44100, 22050.5);
fdelay2a_slider_test = os.tosc(440) : de.fdelay2a(44100, hslider("fdelay2a:d", 22050.5, 1.5, 44100, 0.1));
fdelay1a_modulated_test = no.noise : de.fdelay1a(256, 0.6 + 30*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
fdelay1a_jump_test = no.noise : de.fdelay1a(256, select2(sq, 0.6, 40)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; };
fdelay2a_modulated_test = no.noise : de.fdelay2a(256, 1.6 + 30*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
fdelay2a_jump_test = no.noise : de.fdelay2a(256, select2(sq, 1.6, 40)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; };
fdelay3a_modulated_test = no.noise : de.fdelay3a(256, 2.6 + 30*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
fdelay3a_jump_test = no.noise : de.fdelay3a(256, select2(sq, 2.6, 40)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; };
fdelay4a_modulated_test = no.noise : de.fdelay4a(256, 3.6 + 30*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
fdelay4a_jump_test = no.noise : de.fdelay4a(256, select2(sq, 3.6, 40)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; };
Note
The interpolated delay should not be less than N-1/2.
(The allpass delay ranges from N-1/2 to N+1/2).
This constraint can be alleviated by altering the code,
but be aware that allpass filters approach zero delay
by means of pole-zero cancellations.
Delay arguments too small will produce an UNSTABLE allpass!
Because allpass interpolation is recursive, it is not as robust as Lagrange interpolation under time-varying conditions (you may hear clicks when changing the delay rapidly).
Others
(de.)multiTapSincDelay
Variable delay line using multi-tap sinc interpolation.
This function implements a continuously variable delay line by superposing (2K+2) auxiliary delayed signals whose positions and gains are determined by a sinc-based interpolation method. It extends the traditional crossfade delay technique to significantly reduce spectral coloration artifacts, which are problematic in applications like Wave Field Synthesis (WFS) and auralization.
Operation:
- If tau1 and tau2 are very close (|tau2 - tau1| ≈ 0), a simple fixed fractional delay is applied
-
Otherwise, a variable delay is synthesized by:
- Computing (2K+2) taps symmetrically distributed around tau1 and tau2
- Applying sinc-based weighting to each tap, based on its offset from the target interpolated delay tau
- Summing all the weighted taps to produce the output
Features:
- Smooth delay variation without introducing Doppler pitch shifts
- Significant reduction of comb-filter coloration compared to classical crossfading
- Switching between fixed and variable delay modes to ensure stability
Usage
_ : multiTapSincDelay(K, MaxDelay, tau1, tau2, alpha) : _
Where:
K (integer): number of auxiliary tap pairs (a constant numerical expression). Total number of taps = 2*K + 2MaxDelay: maximum allowable delay in samples (buffer size)tau1: initial delay in samples (can be fractional)tau2: target delay in samples (can be fractional)alpha: interpolation factor between tau1 and tau2 (in [0,1] with 0 = tau1, 1 = tau2)
Test
de = library("delays.lib");
os = library("oscillators.lib");
ba = library("basics.lib");
ma = library("maths.lib");
no = library("noises.lib");
multiTapSincDelay_test = os.tosc(440) : de.multiTapSincDelay(2, 4096, 1024.0, 1536.0, 0.5);
multiTapSincDelay_slider_test = os.tosc(440) : de.multiTapSincDelay(2, 4096, hslider("multiTapSincDelay:tau1", 1024, 0, 4000, 0.1), hslider("multiTapSincDelay:tau2", 1536, 0, 4000, 0.1), hslider("multiTapSincDelay:alpha", 0.5, 0, 1, 0.01));
multiTapSincDelay_modulated_test = no.noise : de.multiTapSincDelay(2, 4096, 1024.0, 1536.0, tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); };
multiTapSincDelay_near_test = os.tosc(440) : de.multiTapSincDelay(2, 4096, 1024.0, 1024.001, 0.5);
References
T. Carpentier, "Implementation of a continuously variable delay line by crossfading between several tap delays", 2024: https://hal.science/hal-04646939