Ambivalence is a hard fork of **[Ambience 1.0.1](https://github.com/OTODESK4193/Ambience1.0.1)**, a professional-grade algorithmic reverb built on a 16-channel Feedback Delay Network (FDN) architecture. Developed with the JUCE 8 framework and modern C++20, it delivers lush, natural-sounding reverberation with the stability and precision demanded by professional audio production. This port adds macOS and Linux support and removes the original Windows-only build configuration.
- **Header Bar** - plugin title, EQ button, ER SOLO button, IN/OUT VU meters, and a footer showing the technical spec (`16ch FDN | SAPF | ISM-ER | 44.1-192kHz`).
Seven reverb topology buttons appear below the header. Selecting an algorithm resets HF Damping, LF Absorption, Diffusion, and all EQ Mode band multipliers to their defaults for that algorithm.
| PRE-DELAY | 0-500 ms | 10 ms | Delay before the reverb onset. Separates the dry signal from the reverb tail, creating a sense of space without muddying the attack. |
| ROOM SIZE | 0.3-2.0 | 1.0 | Scales all 16 FDN delay lines proportionally. Larger values create a larger perceived room. Also scales Early Reflection tap timings. |
| DECAY | 0.1-20.0 s | 1.5 s | Sets the mid-band RT60 target (500 Hz reference). The Stage 2 GEQ solver uses this as the anchor for all frequency bands. |
### FREQUENCY Section
| Parameter | Range | Default | Description |
| --------- | ----- | ------- | ----------- |
| HF DAMP | 0.0-1.0 | 0.0 | High-frequency absorption. At 0.0 the RT60 curve is flat; increasing reduces RT60 of high bands (2k-16k Hz), simulating carpet or heavy drapes. Reflected in the RT60 graph (orange curve). |
| LF ABSORB | 0.0-1.0 | 0.0 | Low-frequency absorption. Increasing reduces RT60 of low bands (31-250 Hz), simulating rooms with less bass build-up. Reflected in the RT60 graph. |
Both default to 0.0. When a new algorithm is loaded they reset to 0.0 so the orange and gray RT60 curves are perfectly aligned.
### DIFFUSION Section
| Parameter | Range | Default | Description |
| --------- | --------- | ------- | ----------- |
| DIFFUSION | 0.0-1.0 | 0.7 | Controls density of early reflections and tail diffusion. Low values = sparser echoes (small room); high values = smooth dense tail (large hall). Adjusts Input Diffuser Allpass gain and Nested Allpass gain within the FDN. Algorithm-specific sensitivity multipliers apply. |
| MOD AMT | 0.0-1.0 | 0.25 | Depth of LFO modulation applied to FDN delay times. Higher values add pitch modulation and reduce metallic resonances at the cost of mild pitch drift. Keep below 0.3 for natural sound; higher for chorus/shimmer effects. |
| MOD RATE | 0.05-2.0 Hz | 0.5 Hz | Base frequency of the BandlimitedNoise LFO. Each of the 16 FDN channels uses a unique rate multiplier (Golden Ratio Weyl sequence), so no two channels are ever synchronized. |
### STEREO Section
| Parameter | Range | Default | Description |
| --------- | ------- | ------- | ----------- |
| WIDTH | 0.0-1.0 | 0.8 | Stereo width of the reverb output. At 0.0 the reverb is mono; at 1.0 L/R are fully decorrelated. Controls three mechanisms: (1) side-component injection strength into the FDN (`sideBoost = width x 1.5`), (2) FDN output L/R cross-bleed (`crossLeak = 1 - width`), (3) ER tap left/right separation (`erLeakage`). |
### CHARACTER Section
| Parameter | Range | Default | Description |
| --------- | ------- | ------- | ----------- |
| ER LEVEL | 0.0-1.0 | 0.6 | Level of the Early Reflections path (ISM tap pattern), blended with the Late Reverb output. Set to 0 for a pure diffuse tail; increase for clarity and room definition. |
| SATURATE | 0.0-1.0 | 0.0 | Amount of ADAA saturation applied to the wet signal. Algorithm-dependent multiplier (PLATE 1.00x, SPRING 1.05x, ROOM 0.90x). Drive curve: `1 + amount^2 x 2.5`. Wet mix: `amount^2 x 0.7`. Dry mix: `1 - amount x 0.25`. At 0, saturation is completely bypassed. |
| WET | -60 to 0 dB | -4 dB | Wet signal level. An internal -1 dB offset is applied, so 0 dB on the knob = -1 dB effective. Smoothed sample-accurately via `SmoothedValue` to prevent zipper noise under automation. |
| DRY | -60 to 0 dB | 0 dB | Dry (unprocessed) signal level. The dry path bypasses all DSP - no EQ, saturation, or limiting. Smoothed sample-accurately. |
> **Send/return usage:** Set DRY to -60 dB (silent) and control the return level with WET exclusively.
### OUT EQ Section
| Parameter | Range | Default | Description |
| --------- | -------- | ------- | ----------- |
| LO CUT | 20-500 Hz | 20 Hz | High-pass filter applied to the wet path only. Linkwitz-Riley topology (1st-order IIR x 2 stages = 12 dB/oct). At 20 Hz the filter is effectively bypassed with no phase shift. |
| HI CUT | 1k-20k Hz | 20 kHz | Low-pass filter applied to the wet path only. Same Linkwitz-Riley topology (12 dB/oct). At 20 kHz, effectively bypassed. |
### DUCKING Section
Ducking reduces the wet signal level when the input exceeds the threshold, creating space between the dry signal and the reverb tail - particularly effective on percussive material and vocals.
| AMOUNT | 0-20 dB | 0 dB | Maximum gain reduction applied to the wet signal when the input exceeds the threshold. Set to 0 to disable ducking entirely. |
| THRESH | -60 to 0 dB | -20 dB | Input level (peak) at which ducking begins. |
| ATTACK | 0.5-100 ms | 10 ms | Time for the ducking envelope to follow an increase in input level. |
| RELEASE | 10-2000 ms | 200 ms | Time for the gain to recover after the input drops below the threshold. Longer values prevent pumping after transients. |
Press the **EQ** button in the header bar to reveal the EQ Mode panel. Two rows of advanced controls replace the Normal Mode rows. All EQ Mode adjustments are retained when switching back to Normal Mode - they are only reset when a different algorithm is selected.
| SAT TYPE | Selects the ADAA Saturator character: Warm, Tape, Tube, Hard (see [DSP Technical Reference](#dsp-technical-reference)). Only visible in EQ Mode. |
| LO CUT (EQ) | Same parameter as Normal Mode LO CUT - both knobs control the same value. |
| HI CUT (EQ) | Same parameter as Normal Mode HI CUT - both knobs control the same value. |
| ER Zone | 0-200 ms | Blue | Expanded 2x zoom showing Early Reflection taps. Each tap is a 5 px bar with gradient fill and a diamond marker at the peak; the ER envelope connects all tap peaks. |
| Late Zone | 200 ms - max | Orange | Theoretical exponential decay curve based on mid-band RT60, with a 2D gradient fill representing the Late Reverb energy envelope. |
| Boundary line | 200 ms | Gray | Vertical separator between ER and Late zones. |
> The visualizer updates at 15 Hz to keep CPU usage minimal. RT60 values shown are based on the 500 Hz mid-band (`cachedRT60Mid`).
---
## Acoustic Metrics
Four metrics, computed from the actual reverb output signal in real time, appear in the lower-right of the Decay Curve panel.
| D50 | Definition | E(0-50ms) / E(total) | Ratio of early to total energy. Values > 0.5 indicate high clarity. Low in large halls, high in small rooms. |
| C50 | Clarity (speech) | 10 log10[E(0-50ms) / E(50ms+)] | Positive values = more early energy. > 0 dB recommended for speech intelligibility. |
| C80 | Clarity (music) | 10 log10[E(0-80ms) / E(80ms+)] | Positive values favor direct sound. -2 to +4 dB is typical for music spaces. |
| EDT | Early Decay Time | RT60 estimated from first 10 dB | Perceptually dominant measure of reverb character; correlates with subjective "liveness". |
| `<` (PREV) | Load the previous preset in alphabetical order (wraps from first to last). |
| Preset Name (combo) | Drop-down of all available presets. Select to preview; click LOAD to apply. |
| `>` (NEXT) | Load the next preset in alphabetical order. |
| SAVE | Opens a dialog to name and save the current plugin state as a `.ambpreset` file. Existing names are overwritten. |
| LOAD | Loads the currently selected preset from the combo box. |
| DELETE | Deletes the currently selected preset after confirmation. Cannot be undone. |
### Preset File Location
All presets - factory and user-created - are stored as `.ambpreset` files in:
```
~/Documents/Ambivalence/Presets/
```
(`Documents` resolves to `C:\Users\<YourName>\Documents` on Windows.) The folder is created automatically on first use. Each `.ambpreset` file is a standard JUCE binary XML state file containing all plugin parameters.
### Sharing Presets
Send individual `.ambpreset` files directly; recipients place them in their own `Documents/Ambivalence/Presets/` folder and they appear in the browser on next plugin load. To share a complete library, ZIP the entire Presets folder.
> Preset files are version-forward compatible: unknown parameters from newer versions are silently ignored and fall back to defaults.
---
## Signal Flow
| Stage | Module | Description |
| ----- | ------------------- | ----------- |
| 1 | Input Split | Stereo L/R converted to Mid/Side. Mid feeds the FDN; Side injects stereo character. |
| 2 | Input Diffuser | 4-stage series Allpass filter (Hall, Goldfoil only). Diffusion gain = DIFFUSION x algorithm sensitivity (1.0 Hall, 0.5 Spring, etc.). |
| 3 | Early Reflections | ISM-based tapped delay line with algorithm-specific tap patterns. ER Level and Room Size control output. Bypassed for Plate, Spring, Goldfoil. |
| 4 | FDN (16 ch) | 16-channel Feedback Delay Network with FWHT feedback matrix and sign-flip decorrelation. |
| 4a | LFO Modulation | Per-channel BandlimitedNoise LFO modulates delay times (MOD AMT x rate multiplier). |
| 4b | Stage 2 Absorption | 10-band biquad GEQ cascade per channel, WLS-solved to match the target RT60 per band. |
| 4c | Micro-Saturation | Pade `x(27+x^2)/(27+9x^2)` limiter in the feedback loop. `kInScale=0.15`, nearly linear. |
| 4d | Nested Allpass | Per-channel Allpass filter for additional diffusion. Gain = `apfGain x (0.6 + diffusion x 0.4)`. |
| 5 | Mix | `ER x erLevel + Late x lateMakeupGain` blended to stereo. Ducking envelope applied. |
| 9 | Final Mix | `Wet x SmoothedWetGain + Dry x SmoothedDryGain`, sample-accurate smoothing. |
---
## DSP Technical Reference
### FDN Architecture
A 16-channel Feedback Delay Network whose feedback matrix is a normalized Fast Walsh-Hadamard Transform (FWHT) with sign flipping for energy preservation and modal density. Delay times use the Nearest-Prime logarithmic distribution: a target delay time is computed by logarithmically interpolating between the min and max delay (determined by Room Size), then the nearest unused prime is selected. This guarantees:
- All 16 delay lines are mutually coprime (no shared factors).
- Delay times are distributed logarithmically (perceptually uniform spacing).
- No comb-filter clustering at any Room Size setting.
### Stage 2 GEQ Absorption (SAPF)
Each of the 16 FDN channels has an independent 10-stage biquad cascade designed with the Valimaki-Liski Accurate Cascade Graphic Equalizer method. A Weighted Least Squares system is solved to find biquad coefficients approximating the target attenuation per band:
```
targetDb[b] = -60 x delaySamples / (fs x targetRT60[b])
```
HF Damping and LF Absorption modify the target RT60 before the WLS solve, creating perceptually natural roll-off at the spectral extremes.
### BandlimitedNoise LFO
Each FDN channel has an independent LFO driven by a white-noise source (XOR-shift PRNG) filtered by a 1st-order IIR low-pass. The IIR cutoff is MOD RATE x rateMultiplier per channel. Rate multipliers use the Golden Ratio Weyl sequence:
```
angle = channelIndex x 1.6180339887
rateMultiplier = 0.80 + frac(angle) x 0.40 // range 0.80 - 1.20
```
No two channels share an LFO rate, preventing audible beating from periodic modulation.
### ADAA Saturator
Anti-Derivative Anti-Aliasing prevents aliasing when the saturator introduces nonlinear distortion. ADAA computes the output as the finite difference of the antiderivative rather than evaluating the nonlinear function directly:
```
y[n] = (F(x[n]) - F(x[n-1])) / (x[n] - x[n-1])
```
A fallback to direct evaluation is used when `|x[n] - x[n-1]| < 1e-5` (near-constant input) or at zero crossings.
| Mode | Function f(x) | Antiderivative F(x) | Character |
| Hard | `clip(x, -1, 1)` | `x^2/2 (|x|<=1)`, `x-0.5 (x>1)`, `-x-0.5 (x<-1)` | Hard clip with ADAA. Bright, aggressive. C1-continuous antiderivative. |
### Linkwitz-Riley Output EQ
Two cascaded 1st-order IIR filters with identical cutoff frequencies yield a 12 dB/oct roll-off with flat passband magnitude.
```
HPF: y[n] = R x (y[n-1] + x[n] - x[n-1]) where R = exp(-2*pi*fc/fs)
LPF: y[n] = (1-R) x x[n] + R x y[n-1]
```
L and R are processed independently through 2 cascaded stages each (4 filter states per filter type). At bypass frequencies (LO CUT <= 20 Hz, HI CUT >= 20 kHz) the filter computation is skipped entirely - zero CPU cost and zero phase shift.
---
## Real-Time Safety
The plugin is engineered for strict real-time audio operation, with hardening for DAW edge cases.
| Measure | Details |
| ------------------------------- | ------- |
| No heap allocation on audio thread | All buffers (FDN delays, wet buffer, absorption coefficients) are pre-allocated in `prepareToPlay()`. No `new`/`malloc`/`vector::resize()` in `processBlock()`. |
| Dirty-flag parameter dispatch | A `DSPParams` equality check prevents `setParams()` when nothing changed; the expensive Stage 2 WLS matrix solve (16ch x 10-band LDLT) is skipped when idle. |
| SmoothedValue gain changes | WET and DRY gain changes are interpolated at sample rate over 50 ms to eliminate zipper noise under automation. |
| ScopedNoDenormals | Applied at the start of every `processBlock()` to flush denormals to zero, preventing CPU spikes in feedback loops. |
| Sample-rate guard | A check at the start of `processBlock()` compares the current sample rate against the stored rate; mismatch triggers a safe internal reset. |
| Safe editor destruction | The editor destructor explicitly calls `stopTimer()`, `setLookAndFeel(nullptr)`, and resets all APVTS attachments before destruction. |
| VST3 parameter sync | Internal parameter changes use `setValueNotifyingHost()` to keep automation state synchronized and prevent value rollback during recording. |
---
## Tips & Workflow
### Send vs. Insert Usage
- **Insert (on a track):** keep DRY at 0 dB, adjust WET to taste.
- **Send/Return (on a bus):** set DRY to -60 dB and WET to 0 dB; control the reverb level from the send amount.
- **Parallel compression style:** use multiple instances with different algorithms at low WET levels, blended together.
### Using ER SOLO
Enable ER SOLO to hear only the Early Reflections:
- Fine-tune ROOM SIZE - listen for natural pre-echo spacing.
- Adjust ER LEVEL - hear how much definition the early reflections add.
- Verify the algorithm's ER pattern matches the intended space character.
### Reading the RT60 Graph
The gray and orange curves overlap when HF DAMP = 0 and LF ABSORB = 0 (the default after loading a preset). Divergence indicates active spectral shaping:
- Orange below gray at high frequencies = HF DAMP active.
| Plugin does not appear in DAW | VST3 not in the correct folder | Confirm the `.vst3` is in the VST3 directory for your OS and rescan. |
| No sound output | DRY and WET both at -60 dB, or routing | Check WET level; confirm the plugin is on an audio or return track with signal. |
| Reverb sounds metallic / ringy | DIFFUSION too low, or MOD AMT too low for PLATE/SPRING | Increase DIFFUSION. For PLATE, increase MOD AMT slightly (0.2-0.3). |
| Reverb too short despite high Decay | HF DAMP or LF ABSORB reducing some bands | Set both to 0.0; verify the orange curve on the RT60 graph. |
| Orange/gray RT60 curves misaligned at startup | HF DAMP / LF ABSORB not at 0 | Reset both to 0.0, or load a fresh preset. |
| Preset not appearing in browser | File in the wrong folder | Confirm the `.ambpreset` is in `Documents/Ambivalence/Presets/`, then restart. |
| Audio dropout / CPU spike | Block size too small, or too many instances | Increase the audio buffer size in your DAW. |
---
## Building from Source
### Prerequisites
- CMake 3.22+
- A JUCE 8.0.x checkout (`modules/juce_core` must exist)
- A C++20 compiler (Apple Clang, GCC 12+, or MSVC 2022)
### CMake
```sh
cmake -S . -B build -DJUCE_PATH=/path/to/JUCE
cmake --build build --config Release -j
```
JUCE is auto-detected from the project directory, `build/JUCE` (auto-fetched by `make`), `$HOME/JUCE`, `/opt/JUCE`, `/usr/local/JUCE`, and `/Applications/JUCE` if `-DJUCE_PATH` is not given. Build artifacts land in `build/Ambivalence_artefacts/` (VST3 + Standalone).
make install # copies .vst3 + standalone into the per-OS plugin/application dirs
make clean
```
On the first build, JUCE 8 (`JUCE_VERSION`, default `8.0.15`) is shallow-cloned into `build/JUCE` so the build is self-contained. Use an existing checkout with `make JUCE_PATH=/path/to/JUCE`. On macOS, artifacts are installed to `~/Library/Audio/Plug-Ins/VST3/` and `~/Applications`; on Linux to `~/.local/lib/vst3/` and `~/.local/bin`.
`build/BuildInfo.h` is generated from `Source/BuildInfo.h.in` at configure time and embeds the Git branch, commit, and dirty state.