I’ve been working with Claude Code recently, and wanted to see how well it could create a SynthDef from code in another language that isn’t SuperCollider. I decided to start with something challenging, and chose the VCV Rack implementation of Dattorro’s Plateau reverb found here. I chose that because it’s such a great sounding reverb.
The code works, but is too echoey. I was wondering if anyone is familiar with this reverb algorithm, and is willing to offer some pointers? I’ve tried adding more input diffusers and all-pass filters in the “tanks” (those aren’t in the code I included, though). That did help, but not completely. Maybe I need more? By the way, the code is Python as I use Supriya, the Python API for SuperCollider.
"""
Plateau Reverb - Dattorro 1997 Plate Reverb Algorithm
Based on Jon Dattorro's "Effect Design Part 1: Reverberator and Other Filters" (1997)
Ported from Valley Audio's Plateau VCV Rack module
=============================================================================
ALGORITHM OVERVIEW
=============================================================================
This is an implementation of Jon Dattorro's figure-8 plate reverb algorithm,
which creates a dense, lush reverb tail through cascaded allpass diffusion and
cross-coupled stereo delay tanks with modulated feedback.
The algorithm consists of these main stages:
1. Pre-delay (optional time offset before reverb starts)
2. Input bandwidth limiting (HPF + LPF on input signal)
3. Input diffusion network (4 cascaded allpass filters)
4. Modulation oscillators (sine/noise LFOs for chorus effect)
5. Stereo tank (cross-coupled feedback loops with diffusion)
6. Tank damping filters (frequency-dependent decay control)
7. Output taps (8 per channel for dense early reflections)
8. Output bandwidth limiting (DC blocking)
9. Dry/wet mix
=============================================================================
INPUT PARAMETERS
=============================================================================
pre_delay: float (0.0-0.5 seconds)
Time offset before reverb starts. Creates gap between dry signal and
reverb tail. Useful for simulating room size or creating special effects.
size: float (0.0-1.0)
Scales all delay times proportionally. Controls the apparent size of the
reverberant space. Smaller = tighter sound, larger = more spacious.
At 1.0, delay times match Dattorro's original specification exactly.
decay: float (0.1-0.9999)
Controls how long the reverb tail lasts. Applied quadratically for more
intuitive control curve. Higher values = longer decay time.
At 0.9999, decay time approaches infinity (infinite reverb).
diffusion: float (0.0-10.0)
Controls the feedback gain in the tank allpass filters (0-0.7 actual gain).
Higher diffusion = denser, smoother reverb tail. Lower = more discrete echoes.
Dattorro recommends 0.7 max to avoid metallic artifacts.
input_low_cut: float (0.0-10.0 pitch, converted to Hz)
High-pass filter on input before diffusion. Removes low frequencies from
reverb tail. Useful for preventing muddy bass buildup.
Pitch range: 0 = 13.75 Hz, 5 = 440 Hz, 10 = 14080 Hz
input_high_cut: float (0.0-10.0 pitch, converted to Hz)
Low-pass filter on input before diffusion. Removes high frequencies from
reverb tail. Creates darker, warmer reverb character.
Pitch range: 0 = 13.75 Hz, 5 = 440 Hz, 10 = 14080 Hz
reverb_low_cut: float (0.0-10.0 pitch, converted to Hz)
High-pass filter in feedback loop. Controls frequency-dependent decay -
low frequencies decay faster. Prevents bass buildup in long tails.
Applied inside the tank feedback loop for exponential effect.
reverb_high_cut: float (0.0-10.0 pitch, converted to Hz)
Low-pass filter in feedback loop. Controls frequency-dependent decay -
high frequencies decay faster. Creates natural damping like real rooms.
Applied inside the tank feedback loop for exponential effect.
mod_speed: float (0.0-1.0, squared then mapped to 1-100 Hz)
Controls LFO frequency for chorus modulation. Prevents metallic ringing
by detuning delay lines. Dattorro spec calls for 1-2 Hz base frequency.
0.0 = 1-2 Hz (subtle), 1.0 = 10-18 Hz (intense chorus/vibrato)
mod_depth: float (0.0-16.0)
Maximum delay time modulation in samples (±16 samples max per Dattorro).
Controls intensity of chorus effect. Higher = more pitch variation.
Combined with mod_speed to create lush, moving reverb tail.
mod_shape: float (0.0-1.0)
Crossfades between sine wave LFOs (0.0) and noise LFOs (1.0).
Sine = smooth, periodic modulation. Noise = random, irregular modulation.
Intermediate values blend both for complex modulation patterns.
dry: float (0.0-1.0)
Dry signal level (original unprocessed input).
1.0 = full dry signal, 0.0 = completely wet (reverb only).
wet: float (0.0-1.0, scaled by 10x internally)
Wet signal level (reverb output). Scaled by 10.0 internally for gain.
0.5 = moderate reverb mix, 1.0 = very wet mix.
=============================================================================
SIGNAL FLOW
=============================================================================
Input (stereo) → Sum to mono → Pre-delay → Input filters (HPF/LPF)
↓
Input diffusion network
(4 cascaded allpass)
↓
┌────────────────┴────────────────┐
↓ ↓
LEFT TANK (figure-8) RIGHT TANK (figure-8)
↓ ↓
┌──────────────────→ + ←─────────────┐ ┌──────────────────→ + ←─────────────┐
│ ↓ │ │ ↓ │
│ Allpass 1 │ │ Allpass 1 │
│ (modulated) │ │ (modulated) │
│ ↓ │ │ ↓ │
│ Delay 1 │ │ Delay 1 │
│ (damping filters) │ │ (damping filters) │
│ ↓ │ │ ↓ │
│ Allpass 2 │ │ Allpass 2 │
│ (modulated) │ │ (modulated) │
│ ↓ │ │ ↓ │
│ Delay 2 │ │ Delay 2 │
│ ↓ │ │ ↓ │
└─────── decay ─────┘ │ └─────── decay ─────┘ │
(feeds right tank) │ (feeds left tank) │
└───────────────┬──────────────────────────┘
↓
Output taps
(8 taps per channel, ±0.6 coefficients)
↓
DC blocking (HPF)
↓
Dry/wet mix → Output (stereo)
=============================================================================
STAGE DETAILS
=============================================================================
1. PRE-DELAY
- Optional delay (0-500ms) before reverb starts
- Simulates distance between sound source and reflective surfaces
- Implemented with DelayC (cubic interpolation)
2. INPUT BANDWIDTH LIMITING
- High-pass filter (input_low_cut) removes rumble and DC offset
- Low-pass filter (input_high_cut) shapes tonal character
- Applied before diffusion to prevent frequency buildup
3. INPUT DIFFUSION NETWORK
- 4 cascaded allpass filters with coprime delays (141, 107, 379, 277 samples)
- Creates dense early reflections from single input
- Fixed gains: 0.75, 0.75, 0.625, 0.625 (per Dattorro spec)
- Coprime delays prevent periodic artifacts
- All delays scaled by 'size' parameter
4. MODULATION OSCILLATORS
- Four independent LFO pairs (sine + noise for each tank allpass)
- Base frequencies: 1.0, 1.5, 1.2, 1.8 Hz (per Dattorro: 1-2 Hz range)
- Prevents comb filter resonances and metallic ringing
- mod_shape crossfades between smooth (sine) and random (noise)
- mod_depth controls ±16 sample excursion maximum
- mod_speed scales frequencies 1x-10x for tempo sync or special effects
5. STEREO TANK (Figure-8 topology)
Each tank has identical structure but different delay times:
a) Cross-coupled input mixing
- Left tank receives: diffused input + right tank feedback
- Right tank receives: diffused input + left tank feedback
- Creates stereo width through decorrelation
b) Modulated allpass filter (decay diffusion 1)
- Left: 672 samples, Right: 908 samples
- Negative gain (-tank_diffusion) for inversion
- Modulated by LFO for chorus effect
- Creates early reflections within tank
c) Long delay line 1
- Left: 4453 samples (~150ms), Right: 4217 samples (~142ms)
- Main reverb time accumulator
- Coprime lengths prevent periodic artifacts
d) Tank damping filters
- Applied after delay 1, before allpass 2
- Frequency-dependent decay (reverb_low_cut, reverb_high_cut)
- In feedback loop = exponential effect on decay spectrum
e) Decay scaling
- Applied after damping to control overall reverb time
- Quadratic curve for intuitive control (decay²)
f) Modulated allpass filter (decay diffusion 2)
- Left: 1800 samples, Right: 2656 samples
- Positive gain (+tank_diffusion)
- Modulated by different LFO for variety
- Adds complexity to late reflections
g) Long delay line 2
- Left: 3720 samples (~125ms), Right: 3163 samples (~106ms)
- Secondary reverb time accumulator
- Output point for feedback and taps
6. OUTPUT TAPS
- 8 delay taps per channel from various points in both tanks
- Specific coefficients from Dattorro Table 2: ±0.6
- Pattern per channel: +0.6, +0.6, -0.6, +0.6 (same tank)
-0.6, -0.6, -0.6, -0.6 (cross tank)
- Asymmetric tap pattern creates wide stereo image
- Cross-channel taps increase decorrelation
- No equal weighting - specific phase relationships prevent cancellation
7. OUTPUT BANDWIDTH LIMITING
- 20 Hz high-pass filter removes DC drift from feedback accumulation
- Prevents speaker cone excursion and low-frequency buildup
8. DRY/WET MIX
- Dry signal preserved in parallel
- Wet scaled by 10.0 for gain compensation
- Independent control of dry and wet levels
=============================================================================
TECHNICAL SPECIFICATIONS
=============================================================================
Reference Sample Rate: 29,761 Hz (Dattorro original specification)
- All delay times specified at this rate
- Automatically scaled to actual sample rate
Input Diffusion Gains:
- Filters 1-2: 0.75 (fixed, per Dattorro)
- Filters 3-4: 0.625 (fixed, per Dattorro)
Tank Diffusion Gain Range:
- Minimum: 0.0 (no diffusion, discrete echoes)
- Maximum: 0.7 (per Dattorro, prevents metallic artifacts)
- Scaled from diffusion parameter (0-10 → 0-0.7)
LFO Modulation:
- Maximum excursion: ±16 samples (per Dattorro)
- Base frequencies: 1.0, 1.5, 1.2, 1.8 Hz
- Four independent oscillators (different frequencies)
Delay Times (at size=1.0):
- Input diffusion: 141, 107, 379, 277 samples
- Left tank: 672, 4453, 1800, 3720 samples
- Right tank: 908, 4217, 2656, 3163 samples
- All coprime to minimize periodic artifacts
Output Tap Delays (samples at size=1.0):
- Left from left: 266, 2974, 1913, 1996
- Left from right: 1990, 187, 1066, 0
- Right from right: 353, 3627, 1228, 2673
- Right from left: 2111, 335, 121, 0
Decay Curve:
- Input: 0.1-0.9999 (linear)
- Applied: quadratic (1 - (1 - decay)²)
- Creates more intuitive control response
=============================================================================
REFERENCES
=============================================================================
Jon Dattorro (1997). "Effect Design Part 1: Reverberator and Other Filters"
Journal of the Audio Engineering Society, Vol. 45, No. 9, pp. 660-684
https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf
Valley Audio Plateau VCV Rack module
https://github.com/ValleyAudio/ValleyRackFree
Key Features of This Algorithm:
- Figure-8 feedback topology for infinite reverb
- Modulated allpass filters prevent metallic ringing
- Coprime delay lengths minimize periodic artifacts
- Cross-coupled stereo tanks create wide image
- Frequency-dependent damping for natural decay
- Multiple output taps for dense early reflections
- Asymmetric tap weighting prevents phase cancellation
=============================================================================
"""
import math
from supriya import synthdef
from supriya.ugens import (
In,
Out,
AllpassC,
DelayC,
LPF,
HPF,
LinExp,
LocalIn,
LocalOut,
SinOsc,
LFNoise1,
LinLin,
)
# Dattorro reference sample rate
DATTORRO_SR = 29761.0
# Fixed coefficients from Dattorro paper (input diffusion stays fixed)
INPUT_DIFFUSION_1 = 0.75
INPUT_DIFFUSION_2 = 0.625
# Tank diffusion max from Dattorro paper
MAX_TANK_DIFFUSION = 0.7
# LFO max excursion (samples)
LFO_MAX_EXCURSION = 16.0
def gain_to_decay_time(gain, delay_time):
"""
Convert allpass gain coefficient to SuperCollider decay_time (RT60).
decay_time = delay_time * -60 / (20 * log10(|gain|))
"""
if abs(gain) < 0.001:
return 0.001
if abs(gain) >= 1.0:
return 100.0 # Very long decay
return delay_time * -60.0 / (20.0 * math.log10(abs(gain)))
def pitch_to_frequency(pitch):
"""
Convert pitch value (0-10) to frequency in Hz using LinExp.
Uses Valley Audio's formula: freq = 440.0 * 2^(pitch - 5.0)
Range: pitch 0 → 13.75 Hz, pitch 5 → 440 Hz, pitch 10 → 14080 Hz
Using LinExp for safe exponential mapping without power operations.
"""
# Map pitch (0-10) to frequency (13.75-14080 Hz) using exponential curve
# LinExp maps linearly from input range to exponentially from output range
return LinExp.kr(
source=pitch,
input_minimum=0.0,
input_maximum=10.0,
output_minimum=13.75,
output_maximum=14080.0
)
@synthdef()
def plateau_reverb(
in_bus: int = 2,
out_bus: int = 0,
# Valley Audio parameter ranges (matched exactly)
pre_delay: float = 0.0, # 0.0-0.5 seconds
size: float = 0.5, # 0.0-1.0 scale
decay: float = 0.54995, # 0.1-0.9999
diffusion: float = 10.0, # 0.0-10.0
input_low_cut: float = 10.0, # 0.0-10.0 pitch (converted to Hz)
input_high_cut: float = 10.0, # 0.0-10.0 pitch (converted to Hz)
reverb_low_cut: float = 10.0, # 0.0-10.0 pitch (converted to Hz)
reverb_high_cut: float = 10.0, # 0.0-10.0 pitch (converted to Hz)
mod_speed: float = 0.0, # 0.0-1.0 (squared, then 1-100 Hz)
mod_depth: float = 0.5, # 0.0-16.0
mod_shape: float = 0.5, # 0.0-1.0 (0=sine, 1=noise)
dry: float = 1.0, # 0.0-1.0
wet: float = 0.5, # 0.0-1.0
):
# Apply quadratic decay curve (from Plateau.cpp)
decay_factor = 1.0 - decay
decay_scaled = 1.0 - (decay_factor * decay_factor)
# Apply wet gain scaling (multiply by 10.0 from Plateau.cpp)
wet_scaled = wet * 10.0
# Scale tank diffusion: 0-10 → 0-0.7 (max from Dattorro paper)
tank_diffusion = (diffusion / 10.0) * MAX_TANK_DIFFUSION
# Convert damping pitch values (0-10) to Hz
input_low_cut_hz = pitch_to_frequency(input_low_cut)
input_high_cut_hz = pitch_to_frequency(input_high_cut)
reverb_low_cut_hz = pitch_to_frequency(reverb_low_cut)
reverb_high_cut_hz = pitch_to_frequency(reverb_high_cut)
# Read stereo input
input_signal = In.ar(bus=in_bus, channel_count=2)
dry_signal = input_signal
# Convert stereo to mono for processing
mono_input = (input_signal[0] + input_signal[1]) * 0.5
# Pre-delay (up to 0.5 seconds)
if pre_delay > 0.001:
pre_delayed = DelayC.ar(source=mono_input, maximum_delay_time=0.5, delay_time=pre_delay)
else:
pre_delayed = mono_input
# Input bandwidth filtering
filtered = pre_delayed
if input_low_cut_hz > 20.0:
filtered = HPF.ar(source=filtered, frequency=input_low_cut_hz)
if input_high_cut_hz < 20000.0:
filtered = LPF.ar(source=filtered, frequency=input_high_cut_hz)
# INPUT DIFFUSION: 4 allpass filters in series
# Delay times from Dattorro paper: 141, 107, 379, 277 samples
scale = size
# Diffuser 1: 141 samples, gain = 0.75
d1_time = (141.0 / DATTORRO_SR) * scale
d1_decay = gain_to_decay_time(INPUT_DIFFUSION_1, d1_time)
diffuser_1 = AllpassC.ar(
source=filtered,
maximum_delay_time=0.1,
delay_time=d1_time,
decay_time=d1_decay
)
# Diffuser 2: 107 samples, gain = 0.75
d2_time = (107.0 / DATTORRO_SR) * scale
d2_decay = gain_to_decay_time(INPUT_DIFFUSION_1, d2_time)
diffuser_2 = AllpassC.ar(
source=diffuser_1,
maximum_delay_time=0.1,
delay_time=d2_time,
decay_time=d2_decay
)
# Diffuser 3: 379 samples, gain = 0.625
d3_time = (379.0 / DATTORRO_SR) * scale
d3_decay = gain_to_decay_time(INPUT_DIFFUSION_2, d3_time)
diffuser_3 = AllpassC.ar(
source=diffuser_2,
maximum_delay_time=0.2,
delay_time=d3_time,
decay_time=d3_decay
)
# Diffuser 4: 277 samples, gain = 0.625
d4_time = (277.0 / DATTORRO_SR) * scale
d4_decay = gain_to_decay_time(INPUT_DIFFUSION_2, d4_time)
diffuser_4 = AllpassC.ar(
source=diffuser_3,
maximum_delay_time=0.2,
delay_time=d4_time,
decay_time=d4_decay
)
# diffused_input = diffuser_8
diffused_input = diffuser_4
# MODULATION OSCILLATORS
# Four different base LFO frequencies: 1.0, 1.5, 1.2, 1.8 Hz (per Dattorro paper: 1-2 Hz)
# mod_speed scales from 1x (at 0.0) to 10x (at 1.0) the base frequencies
# This gives a range of 1-2 Hz (default) up to 10-18 Hz (max)
mod_scale = 1.0 + (mod_speed * 9.0)
lfo_1 = SinOsc.kr(frequency=1.0 * mod_scale)
lfo_2 = SinOsc.kr(frequency=1.5 * mod_scale)
lfo_3 = SinOsc.kr(frequency=1.2 * mod_scale)
lfo_4 = SinOsc.kr(frequency=1.8 * mod_scale)
# Noise modulators (doubled frequency for variety)
noise_1 = LFNoise1.kr(frequency=2.0 * mod_scale)
noise_2 = LFNoise1.kr(frequency=3.0 * mod_scale)
noise_3 = LFNoise1.kr(frequency=2.4 * mod_scale)
noise_4 = LFNoise1.kr(frequency=3.6 * mod_scale)
# Interpolate between sine and noise based on mod_shape
mod_1 = LinLin.kr(
source=mod_shape,
input_minimum=0.0,
input_maximum=1.0,
output_minimum=lfo_1,
output_maximum=noise_1
)
mod_2 = LinLin.kr(
source=mod_shape,
input_minimum=0.0,
input_maximum=1.0,
output_minimum=lfo_2,
output_maximum=noise_2
)
mod_3 = LinLin.kr(
source=mod_shape,
input_minimum=0.0,
input_maximum=1.0,
output_minimum=lfo_3,
output_maximum=noise_3
)
mod_4 = LinLin.kr(
source=mod_shape,
input_minimum=0.0,
input_maximum=1.0,
output_minimum=lfo_4,
output_maximum=noise_4
)
# Scale modulation depth (Valley Audio range: 0-16, maps to ±16 samples)
mod_depth_samples = (mod_depth / 16.0) * (LFO_MAX_EXCURSION / DATTORRO_SR) * scale
mod_1_scaled = mod_1 * mod_depth_samples
mod_2_scaled = mod_2 * mod_depth_samples
mod_3_scaled = mod_3 * mod_depth_samples
mod_4_scaled = mod_4 * mod_depth_samples
# STEREO TANK with feedback using LocalIn/LocalOut
# 2 channels of feedback
feedback = LocalIn.ar(channel_count=2)
# ============ LEFT TANK ============
# Mix input with right channel feedback (cross-feedback)
# Note: decay is already applied in the feedback signal
left_input = diffused_input + feedback[1]
# Decay diffusion 1 (modulated allpass) - 672 samples, gain = -tank_diffusion
left_apf_1_delay = (672.0 / DATTORRO_SR) * scale + mod_1_scaled
left_apf_1_decay = gain_to_decay_time(tank_diffusion, (672.0 / DATTORRO_SR) * scale)
# Negative gain: invert input, apply allpass, invert output
left_apf_1 = AllpassC.ar(
source=-left_input,
maximum_delay_time=0.2,
delay_time=left_apf_1_delay,
decay_time=left_apf_1_decay
)
left_apf_1 = -left_apf_1
# Delay line 1 - 4453 samples
left_delay_1 = DelayC.ar(
source=left_apf_1,
maximum_delay_time=0.5,
delay_time=(4453.0 / DATTORRO_SR) * scale
)
# Damping filters (post-delay1, pre-APF2)
if reverb_high_cut_hz < 20000.0:
left_delay_1 = LPF.ar(source=left_delay_1, frequency=reverb_high_cut_hz)
if reverb_low_cut_hz > 20.0:
left_delay_1 = HPF.ar(source=left_delay_1, frequency=reverb_low_cut_hz)
left_delay_1 = left_delay_1 * decay_scaled
# Decay diffusion 2 (modulated allpass) - 1800 samples, gain = +tank_diffusion
left_apf_2_delay = (1800.0 / DATTORRO_SR) * scale + mod_2_scaled
left_apf_2_decay = gain_to_decay_time(tank_diffusion, (1800.0 / DATTORRO_SR) * scale)
left_apf_2 = AllpassC.ar(
source=left_delay_1,
maximum_delay_time=0.2,
delay_time=left_apf_2_delay,
decay_time=left_apf_2_decay
)
# Delay line 2 - 3720 samples
left_delay_2 = DelayC.ar(
source=left_apf_2,
maximum_delay_time=0.5,
delay_time=(3720.0 / DATTORRO_SR) * scale
)
# Output for feedback (attenuated by decay)
left_feedback = left_delay_2 * decay_scaled
# ============ RIGHT TANK ============
# Mix input with left channel feedback (cross-feedback)
# Note: decay is already applied in the feedback signal
right_input = diffused_input + feedback[0]
# Decay diffusion 1 (modulated allpass) - 908 samples, gain = -tank_diffusion
right_apf_1_delay = (908.0 / DATTORRO_SR) * scale + mod_3_scaled
right_apf_1_decay = gain_to_decay_time(tank_diffusion, (908.0 / DATTORRO_SR) * scale)
# Negative gain: invert input, apply allpass, invert output
right_apf_1 = AllpassC.ar(
source=-right_input,
maximum_delay_time=0.2,
delay_time=right_apf_1_delay,
decay_time=right_apf_1_decay
)
right_apf_1 = -right_apf_1
# Delay line 1 - 4217 samples
right_delay_1 = DelayC.ar(
source=right_apf_1,
maximum_delay_time=0.5,
delay_time=(4217.0 / DATTORRO_SR) * scale
)
# Damping filters (post-delay1, pre-APF2)
if reverb_high_cut_hz < 20000.0:
right_delay_1 = LPF.ar(source=right_delay_1, frequency=reverb_high_cut_hz)
if reverb_low_cut_hz > 20.0:
right_delay_1 = HPF.ar(source=right_delay_1, frequency=reverb_low_cut_hz)
right_delay_1 = right_delay_1 * decay_scaled
# Decay diffusion 2 (modulated allpass) - 2656 samples, gain = +tank_diffusion
right_apf_2_delay = (2656.0 / DATTORRO_SR) * scale + mod_4_scaled
right_apf_2_decay = gain_to_decay_time(tank_diffusion, (2656.0 / DATTORRO_SR) * scale)
right_apf_2 = AllpassC.ar(
source=right_delay_1,
maximum_delay_time=0.2,
delay_time=right_apf_2_delay,
decay_time=right_apf_2_decay
)
# Delay line 2 - 3163 samples
right_delay_2 = DelayC.ar(
source=right_apf_2,
maximum_delay_time=0.5,
delay_time=(3163.0 / DATTORRO_SR) * scale
)
# Output for feedback (attenuated by decay)
right_feedback = right_delay_2 * decay_scaled
# Close the feedback loop (send decay-attenuated signals)
LocalOut.ar(source=[left_feedback, right_feedback])
# OUTPUT TAPS - 8 per channel with cross-channel routing
# Tap coefficients from Dattorro Table 2: 0.6 with specific +/- signs
# Left output: +0.6, +0.6, -0.6, +0.6 (left), -0.6, -0.6, -0.6, -0.6 (right cross)
left_wet = (
# From left channel
0.6 * DelayC.ar(source=left_apf_1, maximum_delay_time=0.3, delay_time=(266.0 / DATTORRO_SR) * scale) +
0.6 * DelayC.ar(source=left_apf_1, maximum_delay_time=0.3, delay_time=(2974.0 / DATTORRO_SR) * scale) +
-0.6 * DelayC.ar(source=left_delay_1, maximum_delay_time=0.5, delay_time=(1913.0 / DATTORRO_SR) * scale) +
0.6 * DelayC.ar(source=left_apf_2, maximum_delay_time=0.3, delay_time=(1996.0 / DATTORRO_SR) * scale) +
# From right channel (cross-taps)
-0.6 * DelayC.ar(source=right_delay_1, maximum_delay_time=0.5, delay_time=(1990.0 / DATTORRO_SR) * scale) +
-0.6 * DelayC.ar(source=right_apf_2, maximum_delay_time=0.3, delay_time=(187.0 / DATTORRO_SR) * scale) +
-0.6 * DelayC.ar(source=right_delay_2, maximum_delay_time=0.5, delay_time=(1066.0 / DATTORRO_SR) * scale) +
-0.6 * right_delay_2
)
# Right output: +0.6, +0.6, -0.6, +0.6 (right), -0.6, -0.6, -0.6, -0.6 (left cross)
right_wet = (
# From right channel
0.6 * DelayC.ar(source=right_apf_1, maximum_delay_time=0.3, delay_time=(353.0 / DATTORRO_SR) * scale) +
0.6 * DelayC.ar(source=right_apf_1, maximum_delay_time=0.3, delay_time=(3627.0 / DATTORRO_SR) * scale) +
-0.6 * DelayC.ar(source=right_delay_1, maximum_delay_time=0.5, delay_time=(1228.0 / DATTORRO_SR) * scale) +
0.6 * DelayC.ar(source=right_apf_2, maximum_delay_time=0.3, delay_time=(2673.0 / DATTORRO_SR) * scale) +
# From left channel (cross-taps)
-0.6 * DelayC.ar(source=left_delay_2, maximum_delay_time=0.5, delay_time=(2111.0 / DATTORRO_SR) * scale) +
-0.6 * DelayC.ar(source=left_delay_2, maximum_delay_time=0.5, delay_time=(335.0 / DATTORRO_SR) * scale) +
-0.6 * DelayC.ar(source=left_delay_2, maximum_delay_time=0.5, delay_time=(121.0 / DATTORRO_SR) * scale) +
-0.6 * left_delay_2
)
# DC blocking on outputs (20 Hz HPF)
left_wet = HPF.ar(source=left_wet, frequency=20.0)
right_wet = HPF.ar(source=right_wet, frequency=20.0)
# Combine dry and wet (wet is scaled by 10.0)
left_out = (dry_signal[0] * dry) + (left_wet * wet_scaled)
right_out = (dry_signal[1] * dry) + (right_wet * wet_scaled)
# Output stereo signal
Out.ar(bus=out_bus, source=[left_out, right_out])