This is my reverb in FAUST, what do you think?
-
Ideas:
- Adjust gain curve to be more "exponential" for natural decay
- Add smoothing to crossover frequencies to prevent zipper noise
- Increase filter orders for steeper cutoffs
- Use fractional delays for smoother modulation (?)
import("stdfaust.lib"); // Early Reflections earlyReflections = par(i, 4, de.delay(ma.SR, delayTime(i)) * gain(i)) with { delayTime(i) = ba.take(i+1, (13, 17, 19, 23)) * 1.5; gain(i) = ba.take(i+1, (0.7, 0.6, 0.5, 0.4)); }; FDN = si.bus(2) : (preDelay, preDelay) <: (earlyReflections, earlyReflections, MIMO_Network ~ MATRIX) :> par(i, 2, (+:applyFilters)) with { // User-controllable parameters decayMs = hslider("Decay[unit:ms][style:knob]", 1000, 100, 10000, 1); preDelayMs = hslider("Pre-Delay[unit:ms][style:knob]", 20, 0, 200, 1); modDepth = hslider("Modulation Depth[unit:%][style:knob]", 0.5, 0, 2, 0.01) : *(0.01); modRate = hslider("Modulation Rate[unit:Hz][style:knob]", 0.5, 0.1, 2, 0.1); // // High crossover scales with decay time - longer reverbs need lower high crossover highCrossover = min(12000, max(2000, 8000 - (decayMs - 1000) * 0.4)); // Low crossover is related to high crossover but stays within reasonable bounds lowCrossover = min(500, max(100, highCrossover * 0.06)); // Decay ratios adapt based on decay time // Longer reverbs typically have more low-end build-up, so we reduce low decay ratio lowDecayRatio = max(0.4, min(1.2, 1.0 - (decayMs - 1000) * 0.0001)); // High frequencies decay faster in longer reverbs highDecayRatio = max(0.3, min(1.0, 0.8 - (decayMs - 1000) * 0.0002)); // Pre-delay maxPreDelay = 200; preDelay = de.sdelay(ma.SR * maxPreDelay / 1000, 512, preDelayMs * ma.SR / 1000); // Calculate decay factor based on milliseconds and frequency band calcDecayFactor(delayLengthSamples, decayMs, ratio) = pow(0.001, delayLengthSamples / (decayMs * ratio * ma.SR / 1000.0)); // Matrix MATRIX = ro.hadamard(16) : par(i, 16, *(1/4)); // Network MIMO_Network(fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16, in1, in2) = par(i, 16, DEL(i)) with { baseDelays = (3797, 3541, 3323, 3109, 2939, 2801, 2693, 2593, 2503, 2423, 2351, 2287, 2237, 2179, 2131, 2083); DEL(i) = de.fdelay(5000, modulatedDelay(i), fb(i) + input(i)) : multiband_decay with { delay = baseDelays : ba.selectn(16, i); modulatedDelay(i) = delay + os.osc(modRate + 0.01*i) * delay * modDepth; fb(i) = ba.selectn(16, i, fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16); input(i) = select2(i < 2, 0, ba.selectn(2, i, in1, in2)); // Multi-band decay using automatic parameters multiband_decay = _ <: ( fi.lowpass(2, lowCrossover) * calcDecayFactor(delay, decayMs, lowDecayRatio), fi.bandpass(2, lowCrossover, highCrossover) * calcDecayFactor(delay, decayMs, 1.0), fi.highpass(2, highCrossover) * calcDecayFactor(delay, decayMs, highDecayRatio) ) :> _; }; }; // Apply filters to each channel (oh! I see colours, that's so trippy :P) applyFilters = fi.lowpass(1, highCrossover * 1.5) : fi.highpass(1, lowCrossover * 0.5); }; process = FDN;
-
@Mighty23 if you use filterbank, you can be passing an argument to the function and decide on the number of bands that way.
https://faustlibraries.grame.fr/libs/filters/#fifilterbank
Also, your reverb will behave differently on different sampling rates because you're hardcoding the delay times in samples.
Modulation is also super expensive, and even if it's off, you're still running the calculations. You can bypass that using the hidden enable primitive (with ondemand coming soon)
modulatedDelay(i) = delay + enable(os.osc(modRate + 0.01*i) * delay * modDepth, modDepth>0);
-
@aaronventure Thanks for all these tips. I think I'll keep the post updated.
-
import("stdfaust.lib"); // User-controllable parameters decayMs = hslider("Decay[unit:ms][style:knob]", 1000, 100, 10000, 1); preDelayMs = hslider("Pre-Delay[unit:ms][style:knob]", 20, 0, 200, 1); modDepth = hslider("Modulation Depth[unit:%][style:knob]", 0.5, 0, 2, 0.01) : *(0.01); modRate = hslider("Modulation Rate[unit:Hz][style:knob]", 0.5, 0.1, 2, 0.1); freeze = checkbox("Freeze[style:knob]") : si.smoo; freezeInputMix = hslider("Freeze Input Mix[style:knob]", 0.5, 0, 1, 0.01) : si.smoo; // Early Reflections with dynamic timing and gains based on decay earlyReflections = par(i, 4, de.delay(ma.SR, delayTime(i)) * gain(i)) with { // Base delay times in milliseconds baseDelayRatios = (0.013, 0.017, 0.019, 0.023); // Represented as ratios of decay time minDelay = 10; // Minimum delay time in ms maxDelayRatio = 0.1; // Maximum delay time as ratio of decay time // Calculate delay time based on decay time and base ratios delayTime(i) = min( ba.take(i+1, baseDelayRatios) * decayMs, // Proportional to decay maxDelayRatio * decayMs // Capped at max ratio ) : max(minDelay) // Ensure minimum delay * ma.SR / 1000.0; // Convert to samples // Dynamic gain calculation based on delay time and decay gain(i) = pow(0.7, (i+1)) * (1.0 - (delayTime(i)/(ma.SR/2)) * 0.3); }; FDN = si.bus(2) : (preDelay, preDelay) <: (earlyReflections, earlyReflections, MIMO_Network ~ MATRIX) :> par(i, 2, (+:applyFilters)) with { // Filter bank crossover frequencies numBands = 4; // Number of frequency bands crossoverFreqs = (200, 800, 3200); // Crossover frequencies for 4 bands // Base decay ratios for each band - indexed using selectn baseDecayRatios = (1.2, 1.0, 0.8, 0.6); // Get decay ratio for a specific band getDecayRatio(band) = ba.selectn(4, band, baseDecayRatios); // Adapt decay ratio based on decay time adaptDecayRatio(ratio) = max(0.3, min(1.2, ratio - (decayMs - 1000) * 0.0002)); // Pre-delay maxPreDelay = 200; preDelay = de.sdelay(ma.SR * maxPreDelay / 1000, 512, preDelayMs * ma.SR / 1000); // Calculate decay factor based on milliseconds and frequency band calcDecayFactor(delayLengthSamples, ratio) = select2(freeze > 0.5, pow(0.001, delayLengthSamples / (decayMs * ratio * ma.SR / 1000.0)), 1.0); // Matrix MATRIX = ro.hadamard(16) : par(i, 16, *(1/4)); // Network MIMO_Network(fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16, in1, in2) = par(i, 16, DEL(i)) with { baseDelaysMs = (86.1, 80.3, 75.4, 70.5, 66.7, 63.5, 61.1, 58.8, 56.8, 55.0, 53.3, 51.9, 50.7, 49.4, 48.3, 47.2); DEL(i) = de.fdelay(maxDelay, modulatedDelay(i), processInput(fb(i), input(i))) : multiband_decay with { maxDelay = 200 * ma.SR / 1000; delay = baseDelaysMs : ba.selectn(16, i) * ma.SR / 1000; modulatedDelay(i) = delay + os.osc(modRate + 0.01*i) * delay * modDepth; fb(i) = ba.selectn(16, i, fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8, fb9, fb10, fb11, fb12, fb13, fb14, fb15, fb16); input(i) = select2(i < 2, 0, ba.selectn(2, i, in1, in2)); // Input processing freezeGain = freeze * freezeInputMix; normalGain = 1.0 - freeze; processInput(fbSignal, inSignal) = fbSignal + (inSignal * (normalGain + freezeGain)); // Multi-band decay using filterbank multiband_decay = _ <: fi.filterbank(3, crossoverFreqs) : par(i, numBands, * (calcDecayFactor(delay, adaptDecayRatio(getDecayRatio(i))))) :> _; }; }; // Final filtering applyFilters = fi.lowpass(2, 18000) : fi.highpass(2, 20); }; process = FDN;
This is a new version with filterbank and less hardcoded early reflection. I also tried to use your suggestions but I didn't quite understand how to optimize the use of resources in modulation depth.
-
@Mighty23 nice, you're converting to samples based on sampling rate properly.
You can always test this using the oversampling nodes.
The modulation optimization is the following
modulatedDelay(i) = delay + enable(os.osc(modRate + 0.01*i) * delay * modDepth, modDepth>0);
-
@aaronventure
When I test it in HISE I notice that the left channel volume is higher than the right channel volume.
I thought it was caused by modulation but even bypassing it in the implementation, I get no improvement. Could you advise me where to investigate?PS: The oversampling suggestion is great.
-
@Mighty23 it's probably your array of delay times due to how they're fed into the matrix. Try shuffling them around in the list.