FreeEQ8 / ProEQ8: A lock-free, variable-cadence Dynamic EQ engine built in JUCE/C++ (Looking for DSP & systems feedback)
Hey everyone,
I’ve been working on an open-source 8-band parametric EQ project called **FreeEQ8** (and its commercial 24-band sibling, ProEQ8). I just pushed the v2.2.3 and v2.2.4 stabilization updates, which focus heavily on thread-hardening, real-time safety, and optimizing dynamic coefficient churn.
The repo is fully open-source under GPL-3.0:[
https://github.com/GareBear99/FreeEQ8](https://github.com/GareBear99/FreeEQ8)
I’m currently writing a technical paper mapping out the architecture for conference submission and would love to get some peer feedback on the systems design and DSP routing.
# 1. Filter Topology & The 5-DOF Cramping Reality
For the production path, I moved away from classic RBJ TDF-II biquads to a 64-bit double-precision implementation of Andrew Simper’s trapezoidal integrated State Variable Filter (SVF) topology.
To be completely transparent: both topologies use standard Bilinear Transform (BLT) mapping ($g = \\tan(\\pi \\cdot f\_c / f\_s)$), meaning they exhibit identical steady-state frequency responses and the exact same high-frequency warping near Nyquist. However, the SVF was chosen strictly for its **modulation stability**. When updating coefficients per-sample for the Dynamic EQ path, TDF-II biquads can introduce noise or state explosions, whereas the SVF maintains bounded, noise-free coefficient interpolation.
# 2. Lock-Free Concurrency & Real-Time Safety
To isolate the real-time audio thread from UI rendering or background tasks (like FIR reconstruction), the engine enforces a strict allocation-free hot path:
* **SPSC Triple-Buffering:** The spectrum analysis data uses a 3-slot index swap chain (`writeSlot`, `midSlot`, `readSlot`). The audio thread writes frames and executes atomic swaps using `std::memory_order_release`, while the UI fetches data using `std::memory_order_acquire`. Zero mutexes, zero blocking.
* **Off-Thread FIR Reconstruction:** For the plugin’s "Natural Phase" mode (which uses a 256-tap FIR kernel to correct low-frequency phase distortion), all FFT/IFFT window reconstruction happens on a background worker thread, publishing to the audio thread via the same atomic swap protocol.
# 3. Optimization: Variable-Cadence Coefficient Gating
Per-sample coefficient recalculation for 8+ dynamic bands is incredibly CPU-intensive. To solve this, I implemented a variable-cadence heuristic based on psychoacoustic thresholds.
During sustained signals, if the envelope follower's delta ($\\delta$) between samples is less than $0.1\\text{ dB}$ (well below the human broadband level JND), the engine gates per-sample recalculations and falls back to a 4-sample batch window ($0.09\\text{ ms}$ at $44.1\\text{ kHz}$). The moment a transient spikes and hits $\\delta > 0.1\\text{ dB}$, the engine instantly snaps back to per-sample accuracy. This reduces coefficient calculation overhead by up to $75\\text{--}80\\%$ on steady signals.
# 4. Reproducible Benchmarks
The repository contains a standalone benchmarking suite (`Tests/FeatureBench.cpp`) that runs outside of JUCE/DAW overhead. On a single core at $44.1\\text{ kHz}$ (512-sample blocks):
* **Standard 8-Band SVF Stereo:** Consumes $\\sim 0.63\\%$ CPU headroom ($161\\times$ real-time headroom).
* **DAW Scaling Test:** CPU usage Scales cleanly up to 128 simultaneous instances with sub-linear overhead growth due to cache warmth on shared coefficient tables.
* **Worst-Case Dynamic EQ:** 8 bands simultaneously modulated by white noise spikes maxes out at only $3.27\\%$ CPU utilizing the variable-cadence engine.
# What I'm Looking For:
If you have a few minutes to dig into the source code, I'd love to hear your thoughts on:
1. Edge cases in the `SpectrumFIFO` atomic memory ordering fences.
2. The SIMD scaffolding logic for the packed `SvfBandArray<8>` structures.
3. Roadmap feedback on implementing RBJ’s proposed 5th-constraint geometric mean pinning to natively decramp the BLT curves in v2.4.0.
Check out the repo,