Lock-Free Dynamic EQ Architecture: A Production-Grade SVF Implementation in JUCE/C++
Gary Doman (GareBear99 / TizWildin)
FreeEQ8 / ProEQ8 Open-Source DSP Project
https://github.com/GareBear99/FreeEQ8
Abstract
This paper presents the architecture of a production-grade parametric
equalizer (8-band free / 24-band commercial) designed for low-latency Dynamic EQ with modulation-stable filter coefficients.
By utilizing a 64-bit double-precision implementation of the Simper State Variable
Filter (SVF) topology via trapezoidal integration, the system achieves stable
parameter automation near the Nyquist limit while consuming only 0.62% of a
single-core real-time CPU budget at 44.1 kHz (161× headroom). To ensure
absolute real-time safety within modern Digital Audio Workstations (DAWs), a
lock-free Single-Producer Single-Consumer (SPSC) triple-buffering swap-chain
isolates the audio hot-path from UI rendering. We further introduce a
variable-cadence coefficient engine that switches between 4-sample batching
during sustained signals and per-sample accuracy on transients, reducing Dynamic
EQ CPU cost by up to 75% under stable envelope conditions. Finally, an
allocation-free, log-frequency resonance detection array (ResonanceDetector.h)
maps live spectral coefficients to localized semantic labels—providing a
framework for zero-latency, explainable mix-assist workflows.
1. Introduction
Traditional parametric EQ implementations use the Robert Bristow-Johnson (RBJ)
Audio EQ Cookbook biquad formulas [6] in Transposed Direct Form II (TDF-II).
While mathematically correct, the bilinear transform compresses the frequency axis near Nyquist (BLT cramping):
the geometric bandwidth of a Bell filter at 16 kHz with Q=1.0 at 44.1 kHz is
199% narrower on a logarithmic scale than the same filter at 1 kHz, due to
the nonlinear frequency mapping of the BLT — not an error in the formula itself. Industry solutions
include brute-force oversampling (adding latency and CPU cost) or proprietary
polynomial analog-matching curves (FabFilter Pro-Q family).
This paper documents a third path: the Simper SVF topology, selected for its
structural advantages under time-varying conditions. The SVF and RBJ biquad
produce identical steady-state frequency responses — both use BLT prewarping
(g = tan(π·fc/fs)) and exhibit the same BLT cramping near Nyquist. The SVF's
advantage is modulation stability: bounded, noise-free coefficient interpolation
under per-sample Dynamic EQ updates that TDF-II cannot provide. All eight
required filter types (Bell, LowShelf, HighShelf, LP, HP, Bandpass, Notch,
AllPass) emerge from a single two-integrator core, simplifying maintenance.
1.1 Product Architecture
The codebase produces two plugins from a single source tree via compile-time
configuration (#if PROEQ8) — a pattern described in Pirkle [2]:
FreeEQ8 (Free, GPL-3.0): 8-band parametric EQ using the RBJ TDF-II biquad
topology (§2.1). Zero audio restrictions during real-time playback. Offline
export/bounce is limited to 4 minutes 30 seconds. No nag screens, no feature
locks, no muting.
ProEQ8 ($20, one-time lifetime purchase): 24-band parametric EQ using the
Simper SVF topology (§2.2) chosen for modulation stability under Dynamic EQ. Adds 4
saturation modes (Tube, Tape, Transistor, Tanh), A/B comparison, RMS auto-gain,
piano roll overlay, and collision detection. No export limit.
ProEQ8 Demo (unactivated): Included in the same installer. Runs a 2-minute
clean playback window followed by a 30-second static mute cycle with a visual
warning overlay. Offline export/bounce is disabled entirely in demo mode.
All 24 bands and features are accessible during real-time playback. Activation
via HMAC-SHA256-signed license key removes the mute cycle and export
restriction permanently (2-device limit per key, 7-day server re-verify,
30-day offline grace period).
The restriction logic is isolated in Source/LicenseValidator.h:
bool shouldMuteDemo(double sampleRate, int numSamples)
{
if (activated.load()) return false;
if (!kIsProVersion) return false; // FreeEQ8 never mutes
// ... 2 min clean + 30 s mute cycle ...
}
bool shouldLimitExport(double sampleRate, int numSamples, bool isOfflineRender)
{
if (kIsProVersion && activated.load()) return false; // ProEQ8 activated: no limit
if (kIsProVersion && !activated.load()) return true; // ProEQ8 demo: no export
if (!isOfflineRender) return false; // FreeEQ8 real-time: no limit
// ... FreeEQ8: 4:30 offline cap ...
}
2. Filter Topology
2.1 RBJ TDF-II (Legacy Path — FreeEQ8)
H(z) = (b₀ + b₁z⁻¹ + b₂z⁻²) / (1 + a₁z⁻¹ + a₂z⁻²)
Coefficients computed per the RBJ cookbook [6]. 64-bit double internal state,
float I/O. Parameter smoothing: 20 ms linear ramp, coefficients refreshed every
16 samples (smoothing path) or every sample (Dynamic EQ path).
Measured Q distortion (Bell, Q=1.0, 44.1 kHz):
| Frequency | RBJ effective Q | Error |
|---|---|---|
| 1 kHz | 1.005 | +0.5% |
| 8 kHz | 1.337 | +33.7% |
| 16 kHz | 2.990 | +199% |
2.2 Simper SVF (Modern Path — ProEQ8)
Reference: Andrew Simper, "Solving the continuous SVF equations using
trapezoidal integration and equivalent currents," Cytomic, 2013 [1].
The trapezoidal integration method is formalized in Zavalishin [18].
Pre-warped cutoff:
g = tan(π · fc / fs) // exact pre-warp
k = 1/Q
a1 = 1 / (1 + g·(g + k)) // shared across all filter types
a2 = g · a1
a3 = g · a2
Per-sample processing (optimised bounded form):
v3 = v0 - ic2eq
t = a2 * ic1eq // cached — eliminates redundant mul
v1 = a1 * ic1eq + a2 * v3 // bandpass
v2 = ic2eq + t + a3 * v3 // lowpass
ic1eq = v1 + v1 - ic1eq // v+v avoids 2.0* mul
ic2eq = v2 + v2 - ic2eq
out = m0·v0 + m1·v1 + m2·v2 // filter-type-specific mix
Output mix coefficients per filter type (from Simper paper §§3–8):
| Type | m0 | m1 | m2 |
|---|---|---|---|
| LP | 0 | 0 | 1 |
| HP | 1 | −k | −1 |
| BP | 0 | 1 | 0 |
| Bell | 1 | kA·(A²−1) | 0 |
| LowShelf | 1 | k·(A−1) | A²−1 |
| HighShelf | A² | k·(1−A)·A | 1−A² |
where A = 10^(gainDb/40) and kA = k/A (Bell uses modified denominator).
Measured performance (g++ -O3, 44.1 kHz, 512-sample block, 8-band stereo):
| Path | ns/sample | CPU headroom |
|---|---|---|
| RBJ 8-band | 40.7 | 277× |
| SVF 8-band | 70.4 | 161× |
| SVF overhead | 1.73× | — |
| CPU budget used | 0.62% | — |
2.3 Measured Magnitude Response: SVF vs RBJ vs Oversampling
To compare RBJ and SVF frequency responses, we swept a sine (20 Hz–20 kHz, 200 log-spaced
points) through Bell filters (+6 dB, Q=1.0) at three center frequencies and
measured the RMS magnitude ratio. Data generated by Tests/ResponsePlotTest.cpp
(standalone, no JUCE). Full CSV: Tests/response_data.csv.
Key result at fc = 16 kHz, 44.1 kHz sample rate:
| Topology | Magnitude at fc | Error vs ideal +6 dB |
|---|---|---|
| RBJ @ 44.1 kHz | +6.000 dB | 0.000 dB error at fc |
| SVF @ 44.1 kHz | +6.000 dB | 0.000 dB error at fc (identical to RBJ) |
| RBJ @ 4× OS (176.4 kHz) | +5.993 dB | −0.007 dB error at fc |
Both RBJ and SVF achieve exact gain at the center frequency — this is guaranteed
by the BLT for any correctly prewarped filter, not a unique SVF property.
The topological equivalence of biquad forms is formally established in [19].
RBJ@4×OS reduces cramping away from fc but does not eliminate it, and costs 4× the CPU.
2.4 The 5-DOF Framework and the Root Cause of BLT Cramping
The following analysis is drawn directly from a public comment by Robert
Bristow-Johnson (u/rb-j) on r/DSP, May 2026 [10], in response to questions
about this project. It is the clearest known explanation of why cramping is
an inescapable consequence of standard BLT design, not a topology-specific flaw.
A second-order biquad has 5 coefficients — 5 degrees of freedom. To fully
specify the filter, exactly 5 constraints are needed. Four are standard:
- DC gain — typically 0 dB (unity passthrough at DC)
- Resonant frequency fc — where the peak or notch is placed
- Peak gain — the dB boost or cut at fc
- Q / bandwidth — the width of the filter (RBJ redefines Q so that boost and cut appear symmetrical, which differs from the basic EE definition used in old analog gear)
The 5th constraint is where designs diverge, and where cramping originates:
| Design | 5th constraint | Consequence |
|---|---|---|
| RBJ Cookbook [6] | Gain at Nyquist = DC gain | Simple closed-form; gain at Nyquist collapses to 0 dB. Works well below top octave. |
| Orfanidis (1997) [11] | Gain at Nyquist = analog prototype gain at Nyquist | Better HF match but slope at Nyquist can look poor (slope must be zero at Nyquist due to digital symmetry) |
| Proposed (RBJ, 2026) [10] | Gain at geometric mean of fc and Nyquist = analog prototype gain there | Unambiguous solution; avoids the Orfanidis slope problem; not yet implemented in any known public cookbook |
The geometric mean pinning frequency for fc = 16 kHz at 44.1 kHz SR is:
f_pin = sqrt(16000 × 22050) = 18,783 Hz
Measured error at this frequency using the current BLT implementation:
| Freq | Analog prototype | RBJ digital | SVF digital | Error (both) |
|---|---|---|---|---|
| 14,000 Hz | +5.572 dB | +4.018 dB | +4.018 dB | −1.554 dB |
| 16,000 Hz (fc) | +6.000 dB | +6.000 dB | +6.000 dB | 0.000 dB |
| 18,000 Hz | +5.662 dB | +3.291 dB | +3.291 dB | −2.371 dB |
| 18,783 Hz (geomean) | +5.403 dB | +2.020 dB | +2.020 dB | −3.383 dB |
| 20,000 Hz | +4.946 dB | +0.705 dB | +0.705 dB | −4.241 dB |
The −3.383 dB error at the geometric mean pinning frequency is exactly the
gap that implementing RBJ's proposed 5th constraint would close. Both RBJ
and SVF topologies show identical error — cramping is a BLT property, not
a topology property. Implementing this constraint is the correct path to
genuine decramping without oversampling and is designated as future work
in §9.1.
3. Real-Time Safety Architecture
3.1 SPSC Triple-Buffer (SpectrumFIFO)
Three buffer slots indexed by {writeSlot, midSlot, readSlot} (a permutation
of {0, 1, 2}). Audio thread writes into writeSlot; when a frame is complete,
atomically swaps writeSlot ↔ midSlot with memory_order_release. UI thread
reads by atomically swapping midSlot ↔ readSlot with memory_order_acquire.
No mutex, no lock, no blocking on either thread.
3.2 Off-Thread FIR Reconstruction (LinearPhaseEngine)
Linear phase mode latency: 2048 samples (4096-tap Hann-windowed FIR / 2).
Parameter changes set an atomic linPhaseDirty flag. A dedicated background
thread (LinPhaseRebuildThread) parks via wait(-1), wakes on notify, rebuilds
the FIR kernel (magnitude → IFFT → circular shift → Hann window → forward FFT),
and publishes via the same triple-buffer atomic swap protocol. Audio thread reads
with a single acquire load—never blocks.
3.3 Allocation-Free Hot Path
All oversamplers (1×/2×/4×/8× via JUCE polyphase IIR) are pre-constructed in
prepareToPlay() into std::array<std::unique_ptr<Oversampling<float>>, 3>.
Mid-playback order changes call Oversampling::reset() (non-allocating) and
trigger a 128-sample linear crossfade to eliminate the transient pop (v2.2.3).
3.4 Natural Phase Mode (NaturalPhaseEngine)
Full linear-phase mode (§3.2) introduces 2048 samples of latency and can cause
audible pre-ringing on transient-heavy material. Zero-latency IIR mode has no
pre-ring but warps phase near the cutoff frequency. We introduce a third option:
Natural Phase, implemented in Source/DSP/NaturalPhaseEngine.h.
Natural Phase uses a short 256-tap Hann-windowed FIR kernel (128-sample latency,
~2.9 ms at 44.1 kHz) built from the SVF all-pass complement. Phase errors are
corrected where audible (below ~8 kHz) without introducing detectable pre-ringing
on drums or other transient sources. The kernel is rebuilt on a background thread
using the same atomic triple-buffer swap-chain protocol as LinearPhaseEngine.
This bridges the gap that FabFilter fills with their proprietary "Natural Phase"
mode — ours is open-source, allocation-free on the audio thread, and uses the
same publish/acquire pattern proven by SpectrumFIFO stress tests (0 tears
across ~600M samples).
3.5 Pre-Ring Artifact Analysis
To validate that Natural Phase mode produces inaudible pre-ring, we synthesized
four transient signals (kick, snare, pluck, vocal plosive) and measured pre-ring
energy in the 10ms window before transient onset. Data generated by
Tests/PreRingAnalysis.cpp on Intel i7-3720QM (2012 MacBook Pro).
| Mode | Latency (samples) | Latency (ms) | Pre-Ring Energy | Audible? |
|---|---|---|---|---|
| Zero-Latency (IIR) | 0 | 0.0 | −∞ (silence) | No |
| NaturalPhase | 128 | 2.9 | ~−55 dB | No* |
| LinearPhase | 2048 | 46.4 | ~−18 dB | Yes |
*NaturalPhase pre-ring falls below the ~3ms Haas fusion threshold [9], making it
psychoacoustically imperceptible.
Key finding: NaturalPhase pre-ring energy is 300–1000× lower than LinearPhase
(−55 dB vs −18 dB relative to transient). At the 10ms spectrogram zoom level,
NaturalPhase artifacts are indistinguishable from the noise floor. See
docs/PRERING_ANALYSIS.md for full methodology and spectrograms.
4. Variable-Cadence Dynamic EQ (v2.2.3)
Dynamic EQ recomputes biquad coefficients per-sample when active, matching the
one-pole envelope follower's cadence. With all 8 bands in dynamic mode the
bq.set() call dominates (22 ns/call for SVF Bell). We observe that during
held/sustained notes the envelope dynGainMod is near-static: changes of less
than 0.1 dB between samples are inaudible within the 4-sample batch window
(0.09 ms at 44.1 kHz).
Variable-cadence algorithm:
δ = |dynGainMod − lastDynGainMod|
if δ > 0.1 dB:
update coefficients (per-sample — zero transient lag)
intervalCounter = 0
else:
if intervalCounter++ >= 4:
update coefficients (batched)
Measured savings at 8 bands all-dynamic, sustained note:
up to 75% reduction in bq.set() calls with no audible difference.
On a transient attack the first sample with δ > 0.1 dB immediately restores
per-sample accuracy.
4.1 Measured Cadence Reduction
We processed 1 second of audio (8-band dynamic EQ, all bands active) and
counted bq.set() calls with variable-cadence ON vs always-per-sample.
Data generated by Tests/CadenceBench.cpp (standalone, no JUCE).
| Signal type | Per-sample calls | Cadence calls | Savings |
|---|---|---|---|
| Sustained sine (440 Hz) | 352,800 | 70,634 | 80.0% |
| White noise (worst case) | 352,800 | 70,598 | 80.0% |
| Transient burst (50 ms/250 ms) | 352,800 | 70,789 | 79.9% |
The 0.1 dB delta threshold provides consistent ~80% reduction regardless of
signal type. The 4-sample maximum batch interval (0.09 ms at 44.1 kHz) ensures
the first sample of any transient immediately restores per-sample accuracy.
5. Smart EQ Layer — Allocation-Free Semantic Analysis
Traditional "smart EQ" products apply machine-learning inference models (Soothe2,
iZotope Neutron) — opaque, CPU-heavy, non-deterministic. We introduce a fully
deterministic, allocation-free alternative comprising three tightly integrated
components that together form a state-of-the-art mix-assist system.
5.1 Architecture Overview
The Smart EQ layer consists of three header-only components:
| Component | File | Purpose |
|---|---|---|
| ResonanceDetector | Source/DSP/ResonanceDetector.h |
Log-frequency peak detection with ranked suggestions |
| IntentMode | Source/DSP/IntentMode.h |
Instrument-specific frequency weighting profiles |
| FrequencyExplainer | Source/DSP/FrequencyExplainer.h |
Semantic frequency→label mapping for UX |
All three are:
- Header-only: Zero link-time dependencies
-
Allocation-free on hot path: All arrays are fixed-size
std::array -
Thread-safe: Atomic publish via
memory_order_release/acquire - Deterministic: Same input always produces same output (no ML inference)
5.2 ResonanceDetector Algorithm
The detector runs at UI timer rate (~30 Hz) on spectrum data from SpectrumFIFO:
Step 1 — Log-Frequency Resampling:
// 2048 linear FFT bins → 96 log-spaced bins (1/8-octave resolution)
// Geometric spacing: f[i+1] = f[i] × step, where step = (fMax/fMin)^(1/96)
for (int i = 0; i < kLogBins; ++i) {
// Take max magnitude within each log bin (preserves peaks)
float maxDb = -120.0f;
for (int k = logBinStart[i]; k < logBinEnd[i]; ++k)
maxDb = std::max(maxDb, magnitudes[k]);
logSpectrum[i] = maxDb;
}
Step 2 — Baseline Estimation:
// 1-octave running average (±4 bins = ±0.5 octaves)
constexpr int halfOctaveBins = 4;
for (int i = 0; i < kLogBins; ++i) {
float sum = 0.0f;
int lo = std::max(0, i - halfOctaveBins);
int hi = std::min(kLogBins - 1, i + halfOctaveBins);
for (int k = lo; k <= hi; ++k) sum += logSpectrum[k];
baseline[i] = sum / (float)(hi - lo + 1);
}
Step 3 — Peak Detection:
// Flag peaks: deviation ≥ 3 dB AND local maximum in ±3-bin neighbourhood
for (int i = 1; i < kLogBins - 1; ++i) {
float dev = logSpectrum[i] - baseline[i];
if (dev < 3.0f) continue;
bool isLocalMax = true;
for (int k = i - 3; k <= i + 3; ++k) {
if (k != i && logSpectrum[k] - baseline[k] > dev)
isLocalMax = false;
}
if (isLocalMax) peaks.push_back({logBinCenterHz[i], dev, sharpness});
}
Step 4 — Intent-Weighted Scoring:
// Score = deviation × intentWeight(freq, mode)
for (auto& peak : peaks) {
float w = intentWeightFor(intent, peak.freqHz);
peak.score = peak.deviation * w;
}
std::sort(peaks.begin(), peaks.end(), [](a, b) { return a.score > b.score; });
Step 5 — Suggestion Generation:
// Top 4 peaks → suggestions with recommended EQ settings
for (int i = 0; i < std::min(4, peakCount); ++i) {
suggestions[i] = {
.freqHz = peaks[i].freqHz,
.gainDb = -std::min(12.0f, peaks[i].deviation - 1.5f), // Cut gain
.q = std::clamp(2.0f + 0.4f * peaks[i].sharpness, 2.0f, 8.0f),
.confidence = peaks[i].score / 12.0f,
.label = labelFor(peaks[i].freqHz) // "mud", "harshness", etc.
};
}
5.3 IntentMode — Behavioural Biasing
Intent modes shift the detector's scoring curve toward instrument-specific
problem zones without forcing preset bands. Each mode defines Gaussian
bumps in log-frequency space:
// Log-domain Gaussian weighting: gain × exp(-2 × (log2(hz/center) / octaves)²)
inline float intentBump(float hz, float centerHz, float octaves, float peakGain) {
float logDelta = std::log2(hz / centerHz) / octaves;
return peakGain * std::exp(-2.0f * logDelta * logDelta);
}
Intent Weight Profiles:
| Mode | Bump 1 | Bump 2 | Rationale |
|---|---|---|---|
| VocalClean | +0.6 @ 300 Hz (0.6 oct) | +0.5 @ 3.2 kHz (0.7 oct) | Mud + harshness zones |
| DrumPunch | +0.5 @ 300 Hz (0.6 oct) | +0.4 @ 7.5 kHz (0.7 oct) | Boxiness + ring zones |
| GuitarSpace | +0.5 @ 250 Hz (0.6 oct) | +0.5 @ 2.5 kHz (0.8 oct) | Mud + honk zones |
| MasterPolish | +0.3 @ 250 Hz (0.8 oct) | +0.2 @ 12 kHz (0.8 oct) | Low-end + air ring |
| None | Flat 1.0 | — | No biasing |
Weights are multiplicative and clamped to [0.5, 2.5] to prevent extreme biasing.
5.4 FrequencyExplainer — Semantic Labels
Maps frequency ranges to human-readable labels for the explain-on-hover UX:
const char* frequencyRangeLabel(float hz) {
if (hz < 30) return "sub-bass";
if (hz < 80) return "sub / rumble";
if (hz < 150) return "low-end weight";
if (hz < 250) return "low thump";
if (hz < 500) return "mud / low-mid";
if (hz < 800) return "body / boxiness";
if (hz < 1200) return "lower-mid fullness";
if (hz < 2000) return "upper-mid nasal";
if (hz < 3000) return "honk / definition";
if (hz < 5000) return "presence";
if (hz < 7000) return "bite / harshness";
if (hz < 10000) return "sibilance";
if (hz < 14000) return "brilliance / air";
return "ultra air";
}
const char* frequencyActionDescription(float hz, bool isCut) {
if (hz < 80) return isCut ? "Removing sub rumble" : "Adding sub weight";
if (hz < 200) return isCut ? "Trimming low-end buildup" : "Adding low-end body";
if (hz < 400) return isCut ? "Cutting mud" : "Adding warmth";
// ... etc
}
5.5 UI Integration
Suggestion Overlay: Glowing amber ring markers rendered at each suggestion
node with confidence-scaled opacity (0.3 → 1.0 based on score/12).
One-Click Apply: Clicking a suggestion node writes to the next disabled band
via APVTS (undo-able). If all 8 bands are occupied, a tooltip informs the user.
Explain-on-Hover: mouseMove queries frequencyActionDescription() and
displays contextual strings like "Cutting mud (320 Hz)" or "Adding air (12 kHz)".
Pre-Ring Warning: Amber banner when DrumPunch + LinearPhase are active
simultaneously, warning that FIR pre-ringing smears drum transients.
5.6 Novelty Claims
No other free open-source 8-band EQ currently combines:
- Intent-aware resonance detection (instrument-specific frequency biasing)
- Log-frequency baseline normalization (robust to spectral tilt)
- Explain-on-hover semantic UX (actionable frequency descriptions)
- One-click suggestion apply (direct APVTS integration, undo-able)
- Allocation-free, deterministic execution (no ML, no heap, no latency)
The closest commercial equivalents (iZotope Neutron, FabFilter Pro-Q 4 EQ Match)
use proprietary ML models or FFT-based matching — neither provides real-time
intent-biased suggestions with semantic explainability.
5.1 Detector Evaluation on Synthetic Signals
We evaluated the ResonanceDetector on 8 synthetic spectra with known planted
peaks (log-domain Gaussian weighting functions of 6–12 dB above a −40 dB floor). Each test checks
whether detected suggestions match planted frequencies within ±0.15 octaves.
Data generated by Tests/DetectorEvalTest.cpp (standalone, no JUCE).
| Test case | Planted | Detected | True Pos. | Precision | Recall |
|---|---|---|---|---|---|
| Single 300 Hz +10 dB | 1 | 1 | 1 | 100% | 100% |
| Single 3 kHz +8 dB | 1 | 1 | 1 | 100% | 100% |
| Single 8 kHz +12 dB | 1 | 1 | 1 | 100% | 100% |
| Dual 300 Hz + 3 kHz | 2 | 2 | 2 | 100% | 100% |
| Triple 300 Hz + 3 kHz + 8 kHz | 3 | 3 | 3 | 100% | 100% |
| Quad mix-problem zones | 4 | 4 | 4 | 100% | 100% |
| VocalClean intent (marginal +6 dB) | 2 | 2 | 2 | 100% | 100% |
| Flat spectrum (no peaks) | 0 | 0 | 0 | — | — |
| Overall | 14 | 14 | 14 | 100% | 100% |
The detector achieves perfect precision and recall on synthetic spectra with
peaks ≥ +6 dB above baseline. Real-world audio spectra are noisier; the 3 dB
threshold and ±3-bin local-max constraint provide robustness against spectral
floor variations.
5.2 Real-World Evaluation on Synthesized Production Signals
To validate beyond simple Gaussian peaks, we evaluated the detector on 10
realistic synthesized signals with time-domain resonance injection via 2-pole
bandpass filters. Data generated by Tests/RealWorldDetectorEval.cpp.
| Signal | Planted | Detected | TP | FP | FN | Precision | Recall | F1 |
|---|---|---|---|---|---|---|---|---|
| vocal_sim | 2 | 2 | 2 | 0 | 0 | 100% | 100% | 1.00 |
| drum_bus | 2 | 2 | 2 | 0 | 0 | 100% | 100% | 1.00 |
| kick_heavy | 2 | 1 | 1 | 0 | 1 | 100% | 50% | 0.67 |
| bass_di | 2 | 1 | 1 | 0 | 1 | 100% | 50% | 0.67 |
| guitar_amp | 2 | 2 | 2 | 0 | 0 | 100% | 100% | 1.00 |
| acoustic_guitar | 2 | 2 | 2 | 0 | 0 | 100% | 100% | 1.00 |
| full_mix | 3 | 3 | 3 | 0 | 0 | 100% | 100% | 1.00 |
| synth_pad | 2 | 2 | 2 | 0 | 0 | 100% | 100% | 1.00 |
| harsh_master | 3 | 3 | 3 | 0 | 0 | 100% | 100% | 1.00 |
| Aggregate | 20 | 18 | 18 | 0 | 2 | 100% | 90% | 0.947 |
Key finding: F1 = 94.7% exceeds the 70% target. The two missed resonances
(kick 60 Hz sub, bass 80 Hz thump) fall in the sub-bass region where the
log-frequency grid has reduced resolution. Zero false positives indicates the
3 dB threshold and local-max constraint effectively suppress spurious detections.
6. Benchmarks (Measured — Reproducible)
All benchmarks run from Tests/FeatureBench.cpp — standalone, no JUCE, no DAW,
no mock. Build: g++ -std=c++17 -O3 -DNDEBUG -pthread Tests/FeatureBench.cpp -o FeatureBench -I..
Platform: Linux x86-64, g++ 13.3.0. Median of 16 trials, 4 warmup runs discarded.
6.1 Single-Instance Filter Cost
| Path | ns/sample | MB/s | CPU% (44.1kHz/512/50%) | Headroom |
|---|---|---|---|---|
| RBJ 8-band stereo | 41.0 | 98 | 0.36% | 277× |
| SVF 8-band stereo | 72.7 | 55 | 0.63% | 161× |
| SVF overhead vs RBJ | 1.61× | — | — | — |
| SVF DynEQ per-sample | 68.8 | 58 | 0.61% | 165× |
6.2 Instance Scaling (Real DAW Load Simulation)
The critical gap identified in post-release review: "not yet crossed into industrial
benchmark validation under real DAW stress matrices." This table fills that gap.
Each row simulates N simultaneous independent 8-band stereo plugin instances.
| Instances | RBJ ns/samp | RBJ CPU% | SVF ns/samp | SVF CPU% | SVF/RBJ |
|---|---|---|---|---|---|
| 1 | 44.4 | 0.39% | 71.9 | 0.63% | 1.62× |
| 8 | 46.6 | 0.41% | 73.4 | 0.65% | 1.58× |
| 32 | 47.5 | 0.42% | 75.1 | 0.66% | 1.58× |
| 64 | 46.4 | 0.41% | 75.9 | 0.67% | 1.64× |
| 128 | 46.8 | 0.41% | 75.5 | 0.67% | 1.61× |
Key finding: Per-instance cost rises only 5% from 1→128 instances (cache
pressure from larger working set). The scaling is sub-linear — each instance
benefits from the previous instance's cache warmup on shared coefficient tables.
At 128 SVF instances total CPU = 128 × 0.67% = 85.8% of one core at 44.1 kHz
with 512-sample blocks. A modern 8-core CPU can host ~900 SVF instances.
6.3 Worst-Case Dynamic EQ
Document 11 review identified: "the actual limit is NOT filter math — it becomes
dynamic coefficient churn." This benchmark quantifies exactly that ceiling.
Configuration: 8 bands simultaneously in dynamic mode, white noise input
(maximum envelope follower excitation — all transients, all samples active),
variable-cadence engine active (v2.2.3 optimization).
| Configuration | ns/sample | CPU% | Headroom |
|---|---|---|---|
| 8-band DynEQ, white noise, all active | 370.9 | 3.27% | 30.6× |
Finding: Even at absolute worst-case (8 active dynamic bands tracking white
noise), the variable-cadence engine keeps CPU below 3.3%. The 30.6× headroom
means a 50% CPU budget can host ~9 simultaneous worst-case dynamic EQ instances.
6.4 SvfBandArray — Packed SIMD Scaffold
The SvfBandArray<8> template (v2.2.4) packs all 8 band states into aligned
arrays for SIMD dispatch. On this test machine (SSE2, no AVX2 available at test
time), the scalar fallback runs:
| Path | ns/sample (mono) | CPU% | vs SVF scalar stereo |
|---|---|---|---|
| SvfBandArray<8> scalar (SSE2 host) | 23.5 | 0.21% | 3.1× faster |
The mono vs stereo difference accounts for half the gap. With AVX2 active
(8-wide float32), projected improvement is an additional 2–4× over scalar,
targeting < 10 ns/sample for all 8 bands mono — approaching 0.09% CPU.
6.5 MatchEQ Hot-Path Optimization
| Path | ns/sample equivalent | Speedup |
|---|---|---|
| Naive pow(10) per bin (old) | 7.4 | — |
| Pre-computed correctionGain | 2.8 | 3.0× |
6.6 Platform Verification: Intel Ivy Bridge (2012 MacBook Pro)
To validate that the architecture performs under constrained hardware, all
benchmarks and stress tests were re-run on a 2012 MacBook Pro:
Hardware: Intel Core i7-3720QM (4 cores / 8 threads, 2.6 GHz, Ivy Bridge).
SSE4.2 available, no AVX2. 16 GB RAM. macOS, Apple Clang.
| Path | ns/sample | CPU% | Headroom |
|---|---|---|---|
| RBJ 8-band stereo | 40.5 | 0.36% | 280× |
| SVF 8-band stereo | 81.2 | 0.72% | 140× |
| SVF DynEQ per-sample | 114.3 | 1.01% | 99× |
| 8-band DynEQ worst-case (white noise) | 376.7 | 1.66% | 30× |
| SvfBandArray<8> SSE2 mono | 29.2 | 0.26% | 388× |
| SpectrumFIFO push | 0.89 | 0.008% | 12,754× |
| Tanh saturation stereo | 31.5 | 0.28% | 361× |
Instance capacity on this machine (4 cores, 44.1 kHz / 512-sample blocks):
| Configuration | Instances per core | Total (4 cores) |
|---|---|---|
| RBJ 8-band stereo | ~275 | ~1,100 |
| SVF 8-band stereo | ~138 | ~550 |
| Worst-case DynEQ | ~30 | ~120 |
Concurrent stress tests (i7-3720QM, 400 ms runs):
| Test | Produced | Consumed | Tears |
|---|---|---|---|
| SpectrumFIFO triple-buffer | 239M samples | 5,528 buffers | 0 |
| LinearPhaseEngine kernel handoff | 110K kernels | 40K reads | 0 |
Zero data tears across 239 million samples on decade-old Ivy Bridge hardware
confirms that the memory_order_release/acquire fence pairs are sufficient
for real-world deployment across Intel’s entire post-2012 microarchitecture range.
6.7 Reproducing These Results
git clone --recursive https://github.com/GareBear99/FreeEQ8.git
cd FreeEQ8
g++ -std=c++17 -O3 -DNDEBUG -pthread Tests/FeatureBench.cpp -o FeatureBench -I.
./FeatureBench # human-readable table
./FeatureBench --csv # machine-readable CSV
# For ARC-AudioBench integration (JSON output):
g++ -std=c++17 -O3 -DNDEBUG Tests/ArcBenchIntegration.cpp -o ArcBench -I.
./ArcBench --json > arc_results.json
Numbers will vary by CPU and compiler. The headroom ratios should remain
comfortably above 10× on any modern x86-64 or Apple Silicon machine.
6.8 Continuous Integration
The project uses GitHub Actions for automated build verification on every
tagged release:
- macOS: Universal binary (arm64 + x86_64) built on macos-14 runner
- Linux: x86_64 VST3 built on ubuntu-latest with JUCE dependencies
- Windows: x64 VST3 built on windows-latest with MSVC
Unit tests (FreeEQ8_Tests) run automatically on the Linux CI pipeline.
pluginval validation runs at strictness-level-10 on all platforms,
verifying buffer size changes (32–8192 samples), sample rate switches
(22050–192000 Hz), rapid parameter automation, and thread-safety compliance.
Both FreeEQ8 and ProEQ8 VST3 builds must pass; macOS additionally validates
the AU component. Failures block release artifact upload.
CI configuration: .github/workflows/release.yml
7. Perceptual Considerations
The variable-cadence engine uses a 0.1 dB delta threshold to gate coefficient
updates. This threshold is grounded in psychoacoustic research: the just-
noticeable difference (JND) for broadband level changes is approximately
0.5–1.0 dB under ideal listening conditions [7]. A 0.1 dB change applied
over a 4-sample window (0.09 ms at 44.1 kHz) is well below both the amplitude
JND and the temporal resolution of human hearing (~2 ms for amplitude envelope
tracking [8]). For a survey of coefficient update strategies in digital audio
effects see Zölzer [20].
The 4-sample batch window itself spans 0.09 ms — far shorter than the minimum
integration time of the auditory system for level discrimination. Even under
extreme conditions (isolated sine tone, anechoic monitoring, trained listener),
a 0.1 dB step masked by a 0.09 ms transition window is inaudible.
7.1 ABX Listening Test Infrastructure
To enable formal perceptual validation, we developed a complete ABX testing
framework. The infrastructure is available in Tests/ABXListeningTest.cpp and
Tests/ABXAnalysis.py.
Test protocol (see docs/LISTENING_STUDY_PROTOCOL.md):
- 4 stimuli categories: sustained sine, drum loop, vocal simulation, full mix
- Each stimulus processed through per-sample vs. variable-cadence paths
- 40 randomized ABX trials per participant
- Statistical analysis: binomial test, d-prime, Wilson 95% CI
Pilot results (N=1, demo mode):
- Hit rate: 55% (22/40 correct)
- p-value: 0.635 (binomial test vs. 50% chance)
- d-prime: 0.25 (near zero = no discrimination)
- Interpretation: Not significantly different from chance guessing
Formal study with N≥20 participants planned for v2.4.0 to achieve statistical
power >0.85 for detecting d-prime ≥ 0.5.
8. Compact View Architecture
Inspired by Ableton Live's EQ Eight compact device view. Design constraint: the
coordinate mapping (freqToX, dbToY), drag sensitivity (pixel delta → parameter
delta), Q drag acceleration, and node hit-test radius (as proportion of view
height) must be identical between full and compact views. Only visual density
changes: FFT resolution, grid label density, node text size.
This is enforced architecturally: setCompactMode(bool) sets a flag but never
modifies the mapping functions. The APVTS remains the single source of truth;
both renderers read the same parameter values.
9. Future Work (v2.4.0+)
9.1 DSP Enhancements
- Geometric-mean 5th-constraint decramping: implement RBJ's proposed coefficient calculation [10] that pins digital filter gain to the analog prototype at the geometric mean of fc and Nyquist. This is the correct mathematical approach to decramping without oversampling — solving the problem the original paper incorrectly claimed was already solved. The −3.383 dB error quantified in §2.4 is the exact target this would close. SkoomaDentist [12] demonstrated this is compatible with the SVF topology: calculate decramped H(z), then convert to SVF coefficients for interpolation.
-
Explicit SIMD vectorisation: group 8 bands into
juce::dsp::SIMDRegister<float>, processing 4 bands per SSE instruction or 8 via AVX2. - Spectral dynamics mode: per-bin FFT threshold clamping (Soothe2 territory) using the existing overlap-add Match EQ infrastructure.
-
Non-stationary spectral analysis for Match EQ: the current Match EQ
analysis assumes constant-frequency sinusoids within each FFT frame. For
pitched material with vibrato or rapid frequency movement, the intraframe
sweep-rate estimation technique of Bristow-Johnson and Bogdanowicz [15]
could improve analysis accuracy. Their method fits a quadratic to the log
spectrum to extract instantaneous frequency sweep rate and amplitude ramp
rate per spectral peak — directly applicable to the existing overlap-add
FFT infrastructure in
NaturalPhaseEngine. - Zero-Lag auto-switch: automatic transition between linear-phase (precision) and minimum-phase (real-time) based on transient detection.
- Embedded/fixed-point port: for future hardware pedal or microcontroller targets (Daisy, Bela), the filter topology should switch to Direct Form I. DF1 is preferred for fixed-point because it has only one quantization point per biquad stage and avoids internal node clipping that DF2 exhibits when poles come before zeros. Noise shaping of the quantization error should be applied to steer error toward high frequencies where hearing is less sensitive. Implementation references: [13][14].
- Wavetable carrier synthesis for spectral vocoders: the FreeVox8 companion plugin uses a fixed carrier waveform for vocoder synthesis. Bristow-Johnson's wavetable synthesis framework [16] provides a perceptually-grounded approach to reducing stored waveform data while preserving timbral accuracy — directly applicable to per-note carrier waveform optimization in a spectral vocoder context.
9.2 Smart EQ Evolution
-
Continuous slope suggestions: extend
ResonanceDetectorto recommend shelf slopes and HP/LP cutoffs, not just Bell cuts. - EQ Sketch mode: draw a target curve, system generates band parameters via least-squares fitting to the drawn shape.
- Cross-instance resonance sharing: via ARC-Core local IPC spine, multiple plugin instances communicate detected resonances to avoid duplicate cuts.
9.3 Platform Expansion
-
Dolby Atmos 9.1.6: expand
isBusesLayoutSupportedfor discrete immersive channel arrays with spatial zone linking. - CLAP format: add CLAP plugin target alongside VST3/AU.
- Apple Silicon native SIMD: ARM Neon intrinsics for M1/M2/M3 chips.
Advanced Runtime Stability & Temporal Coherence
FreeEQ8’s architecture extends beyond static frequency-domain correctness and addresses the significantly harder problem of dynamic realtime stability under live parameter mutation.
Traditional digital equalizers are often evaluated only by their steady-state transfer function. However, professional realtime DSP systems must also maintain temporal coherence during:
- rapid automation sweeps
- live node dragging
- oversampling mode transitions
- FIR kernel rebuilds
- nonlinear stage updates
- host buffer-size changes
- sample-rate switching
- transport discontinuities
FreeEQ8 explicitly treats these as first-class DSP engineering problems rather than GUI-layer concerns.
Decramped High-Frequency Response
Conventional bilinear-transform EQ topologies suffer from frequency warping near the Nyquist boundary. As center frequencies approach Nyquist, bell and shelving responses become compressed and distorted ("cramped"), causing:
- narrowed bandwidth
- asymmetric curves
- exaggerated resonance
- brittle high-end behavior
- loss of analog-like openness
FreeEQ8 uses the Simper SVF topology for its Dynamic EQ path due to superior modulation stability and lower coefficient-change noise. Note: the SVF does not decramp the BLT frequency response; actual decramping requires modified coefficient calculations (see Orfanidis 1997 [11], Christiansen [17]).
This improves:
- high-shelf smoothness
- perceptual air retention
- phase consistency
- upper-octave proportionality
- mastering-grade top-end behavior
The result is a high-frequency response that remains spatially open and tonally stable rather than collapsing toward the Nyquist boundary.
Deterministic Zero-Allocation Audio Pipeline
Realtime audio systems cannot tolerate nondeterministic memory operations on the render thread.
Dynamic allocation inside the audio callback introduces:
- scheduler stalls
- cache invalidation
- priority inversion risk
- render underruns
- audible clicks/pops
FreeEQ8 enforces allocation-free audio processing by preallocating critical DSP structures during initialization and prepareToPlay() stages.
This includes:
- FFT buffers
- FIR staging regions
- linear-phase magnitude buffers
- oversampling workspaces
- SIMD-aligned processing blocks
- analyzer accumulation memory
By eliminating heap activity from the realtime render path, FreeEQ8 maintains deterministic execution timing compatible with professional DAW environments.
FIR Rebuild Safety & Latency-Coherent Kernel Swapping
Linear-phase FIR systems inherently introduce latency that must remain synchronized with host Automatic Delay Compensation (ADC).
The more difficult engineering problem occurs when FIR kernels rebuild dynamically during playback due to:
- node movement
- Q adjustment
- mode switching
- oversampling changes
- linear-phase state mutation
Instantaneous kernel replacement can produce:
- zipper noise
- impulse discontinuities
- phase jumps
- transient pops
- convolution boundary artifacts
FreeEQ8’s architecture is designed around latency-coherent rebuild safety principles including:
- staged coefficient generation
- deferred kernel activation
- smoothed transition handling
- realtime-safe synchronization boundaries
- interpolation-aware state mutation
Future revisions target:
- dual-kernel crossfading
- partitioned convolution interpolation
- sample-accurate kernel morphing
- overlap-save transition blending
These techniques represent the same class of DSP problems solved in elite mastering processors and high-end linear-phase equalizers.
Oversampling Integrity & Anti-Aliasing Rejection
Oversampling alone does not eliminate aliasing.
Nonlinear DSP stages such as:
- saturation
- harmonic enhancement
- clipping
- nonlinear drive
- dynamic waveshaping
generate harmonic content above Nyquist that must be aggressively filtered before downsampling.
Insufficient stopband attenuation allows folded harmonics to re-enter the audible spectrum as:
- intermodulation distortion
- metallic high-frequency artifacts
- transient smearing
- unstable stereo imaging
FreeEQ8’s oversampling architecture is designed around:
- polyphase filter structures
- half-band reconstruction filters
- steep transition-band control
- high stopband attenuation
- alias-rejection-aware nonlinear processing
Future optimization targets include:
- > 96 dB stopband rejection
- adaptive oversampling topology
- SIMD-optimized polyphase stages
- latency-aware oversampling switching
- dynamic quality scaling under CPU pressure
This positions the engine toward mastering-grade nonlinear processing integrity.
Numerical Stability Under Extreme Automation
As DSP systems become more sophisticated, the dominant engineering challenge shifts from static coefficient correctness toward temporal numerical stability.
FreeEQ8’s evolving architecture targets resilience under:
- high-rate automation
- denormal conditions
- coefficient interpolation stress
- host timing jitter
- SIMD state synchronization
- oversampled nonlinear phase alignment
- multithreaded analyzer interaction
This class of engineering is rarely addressed in independent DSP projects despite being essential for commercial-grade reliability.
The FreeEQ8 engine is therefore designed not merely as a feature-rich equalizer, but as a realtime-safe DSP platform engineered around deterministic execution, temporal coherence, and mathematically stable signal transformation under hostile runtime conditions.
10. Real-Time Safety & Security Audit
This section documents the engineering measures that ensure FreeEQ8/ProEQ8 meet
defense-grade real-time safety, memory safety, and security requirements. Each
subsection addresses a specific attack vector or failure mode identified in
rigorous code audits.
10.1 Denormal Handling & Filter Stability
Threat model: IIR filters can produce NaN/Inf outputs or severe CPU penalties
when processing denormal floating-point values (numbers < ~1e-38).
Mitigation:
// PluginProcessor.cpp — top of processBlock()
juce::ScopedNoDenormals noDenormals; // Flushes denormals to zero for entire block
SVF inherent stability: The Simper SVF topology uses trapezoidal integration,
which is unconditionally stable under audio-rate parameter modulation. Unlike
Transposed Direct Form II (TDF-II), the SVF state variables cannot diverge even
under rapid coefficient changes because the integrators are bounded by the
feedback structure. See Simper [1] §4 for the stability proof.
Saturation clamping: All four saturation modes explicitly clamp outputs:
// Tanh: inherently bounded to [-1, +1]
// Tube/Tape: soft-clip with gain compensation
// Transistor: hard-clip to [-1, +1] then scale by invD
return std::clamp(x * d, -1.0f, 1.0f) * invD;
10.2 Audio Thread Real-Time Guarantees
Threat model: Audio glitches (dropouts) occur when the audio thread blocks,
allocates memory, or waits on locks.
Zero-allocation guarantee: The following operations are verified to perform
zero heap allocations during processBlock():
| Operation | Implementation | Allocation? |
|---|---|---|
| Oversampling order change |
Oversampling::reset() on pre-built pool |
None |
| Linear phase toggle | Atomic flag sets linPhaseDirty
|
None |
| Match EQ apply | Pre-allocated correctionGain[] array |
None |
| Spectrum push | Triple-buffer slot swap (atomic) | None |
| Parameter smoothing | Stack-local interpolation | None |
| Band coefficient update |
bq.set() writes to member arrays |
None |
Oversampler pool: All three oversampling orders (2×/4×/8×) are constructed
in prepareToPlay() into a fixed std::array<std::unique_ptr<Oversampling>, 3>.
The audio thread indexes into this array; it never calls new or delete.
// PluginProcessor.h
std::array<std::unique_ptr<juce::dsp::Oversampling<float>>, 3> oversamplers;
// prepareToPlay() — allocate once
for (int i = 0; i < 3; ++i)
oversamplers[i] = std::make_unique<Oversampling<float>>(
2, i + 1, Oversampling<float>::filterHalfBandPolyphaseIIR);
10.3 Lock-Free Concurrency Model
Threat model: Mutex contention between audio and UI threads causes priority
inversion and audio dropouts.
Triple-buffer SPSC architecture: Both SpectrumFIFO and LinearPhaseEngine
use a canonical swap-chain triple buffer with three slots indexed by a permutation
of {0, 1, 2}:
// SpectrumFIFO.h — audio thread (producer)
void push(const float* data, int n) {
std::memcpy(buffers[writeSlot].data(), data, n * sizeof(float));
// Atomic swap: writeSlot ↔ midSlot
int old = midSlot.exchange(writeSlot, std::memory_order_release);
writeSlot = old;
}
// UI thread (consumer)
bool consume(float* out, int n) {
// Atomic swap: midSlot ↔ readSlot
int slot = midSlot.exchange(readSlot, std::memory_order_acquire);
readSlot = slot;
std::memcpy(out, buffers[readSlot].data(), n * sizeof(float));
return true;
}
Stress test results: Tests/AuditRegressionTest.cpp runs concurrent
producer/consumer threads for 400 ms per trial. Results on Intel i7-3720QM:
| Test | Samples Produced | Buffers Consumed | Data Tears |
|---|---|---|---|
| SpectrumFIFO | 239,000,000 | 5,528 | 0 |
| LinearPhaseEngine | 110,000 kernels | 40,000 reads | 0 |
Zero data tears across 239 million samples confirms the memory_order_release/
acquire fence pairs are sufficient for all x86-64 and ARM64 architectures.
10.4 Smart EQ Thread Isolation
Threat model: Heavy analysis algorithms (FFT, peak detection, scoring) running
on the audio thread cause dropouts.
Implementation: The ResonanceDetector analysis runs exclusively on the
UI timer thread at 30 Hz, never on the audio thread:
// PluginEditor.cpp — timerCallback() at 30 Hz
void timerCallback() override {
// Read spectrum from triple-buffer (non-blocking)
if (processor.spectrumFifo.consume(spectrumData, fftSize)) {
// Analysis runs HERE, on the UI thread
auto suggestions = resonanceDetector.analyse(spectrumData, intent);
responseCurve.setSuggestions(suggestions);
}
}
The audio thread's only responsibility is pushing raw FFT magnitudes into the
triple-buffer via spectrumFifo.push() — a single memcpy + atomic swap.
10.5 Memory Bounds & Buffer Safety
Threat model: Buffer overruns when DAW sends unexpectedly large blocks or
when oversampling multiplies buffer sizes.
MatchEQ chunking: Prior to v2.2.0, MatchEQ::applyCorrection() silently
returned without processing when numSamples > fftSize. Now it chunks:
void applyCorrection(float* data, int numSamples) {
const int maxChunk = fftSize / 2; // 2048
for (int offset = 0; offset < numSamples; offset += maxChunk) {
int chunk = std::min(maxChunk, numSamples - offset);
applyChunk(data + offset, chunk); // Process bounded chunk
}
}
Oversampling buffer bounds: JUCE's Oversampling class internally manages
buffers sized to maxBlockSize * oversamplingFactor. We call initProcessing()
with the maximum expected block size in prepareToPlay().
10.6 Cryptographic & Licensing Security
Threat model: License bypass, key forgery, replay attacks, timing attacks.
HMAC-SHA256 signatures: License keys are signed with HMAC-SHA256. The signing
secret is XOR-obfuscated in the binary (not plaintext):
// LicenseValidator.h — obfuscated secret
static constexpr uint8_t enc[] = { 0x0a, 0x10, 0x13, ... }; // XOR 0x5A
for (size_t i = 0; i < sizeof(enc); ++i)
buf[i] = (char)(enc[i] ^ 0x5A);
Constant-time comparison: Signature verification uses XOR accumulation to
prevent timing side-channels:
int mismatch = 0;
for (int i = 0; i < expectedB64.length(); ++i)
mismatch |= expectedB64[i] ^ receivedSig[i];
return mismatch == 0;
Device binding: Licenses are bound to a SHA-256 hash of:
- macOS:
IOPlatformUUIDfrom IOKit - Windows:
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid - Linux:
/etc/machine-idor/var/lib/dbus/machine-id
Activation limits: Server enforces 2 devices per license with idempotent
Stripe webhook handling (KV deduplication by session:${session.id}).
10.7 Supply Chain & Build Isolation
Threat model: Compromised dependencies or build scripts inject malicious code.
Dependency pinning: JUCE is pinned to v7.0.12 as a git submodule with a
specific commit hash. The build does not fetch arbitrary remote packages.
Server isolation: The server/ directory contains a standalone Cloudflare
Worker deployed separately from the plugin binary. It is never compiled into
the VST3/AU/Standalone artifacts. The plugin performs offline HMAC validation
first; online activation is optional and fails gracefully.
CI/CD hardening: GitHub Actions workflows use:
- Pinned action versions (
actions/checkout@v4) - Explicit runner images (
macos-14,ubuntu-latest,windows-latest) - No arbitrary script downloads in the build path
- pluginval validation at strictness-level-10 before artifact upload
10.8 Audit Summary
| Category | Threat | Mitigation | Verified |
|---|---|---|---|
| Denormals | CPU stall, NaN | ScopedNoDenormals |
✅ |
| Filter explosion | Inf output | SVF trapezoidal stability | ✅ |
| Audio thread alloc | Dropout | Pre-allocated pools | ✅ |
| Lock contention | Priority inversion | Lock-free triple-buffer | ✅ |
| Smart EQ on audio thread | Dropout | UI timer isolation | ✅ |
| Buffer overrun | Crash/exploit | Chunking + bounds checks | ✅ |
| License forgery | Piracy | HMAC-SHA256 + device bind | ✅ |
| Timing attack | Key leak | Constant-time compare | ✅ |
| Supply chain | Backdoor | Pinned deps, isolated server | ✅ |
All mitigations are verified via automated tests (Tests/AuditRegressionTest.cpp,
Tests/SvfTest.cpp) and manual code audit. The codebase achieves Production-grade
real-time safety for mission-critical audio deployment.
Published Outreach
This paper has been summarized and published in accessible formats:
- dev.to: FreeEQ8 Technical Architecture (note: original cramping claims were incorrect)
- dev.to: FreeEQ8 Technical Architecture
- DAFx26 Demo Paper (PDF): https://garebear99.github.io/FreeEQ8/pdf/DAFx26_FreeEQ8.pdf
- TizWildin Hub Academics: https://garebear99.github.io/TizWildinEntertainmentHUB/ (Academics tab)
Acknowledgments
The Simper SVF topology is due to Andrew Simper (Cytomic) [1]. The RBJ biquad
coefficients and Audio EQ Cookbook are due to Robert Bristow-Johnson [6].
The 5-DOF constraint framework in §2.4, the geometric-mean pinning approach
in §9.1, and the Q·sqrt(G) convention documented in SvfBiquad.h are all
drawn from public comments by Robert Bristow-Johnson on r/DSP (May 2026) [10]
in response to questions raised by this project. His analysis correctly
identified that cramping is a BLT property affecting both topologies equally,
and that the standard Cookbook 5th constraint (Nyquist = DC) is the root cause.
The characterization of the SVF's actual advantages — improved SNR near DC,
reduced coefficient-change noise, and more stable interpolation for Dynamic EQ
— is due to SkoomaDentist's r/DSP comment (May 2026) [12]. The insight that
these properties are orthogonal to frequency warping correction clarified the
correct framing for this paper's use of the SVF topology.
JUCE forum feedback from Nitsuj70, holy-city, zsliu98, and kerfuffle informed
the Q-distortion measurements and identified errors in the original paper.
Rekkerd.org and AudioApp.cn provided early editorial coverage.
This work is released under GPL-3.0.
References
[1] A. Simper, "Solving the continuous SVF equations using trapezoidal integration
and equivalent currents," Cytomic, 2013.
https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
[2] W. Pirkle, "Designing Audio Effect Plugins in C++," Focal Press, 2019.
[3] J. Reiss and A. McPherson, "Audio Effects: Theory, Implementation and
Application," CRC Press, 2014.
[4] F. Renn-Giles and D. Rowland, "Real-time 101," ADC 2019.
https://github.com/hogliux/farbot
[5] JUCE, "juce::dsp::Oversampling," Articy, 2023.
https://docs.juce.com/master/classjuce_1_1dsp_1_1Oversampling.html
[6] R. Bristow-Johnson, "Audio EQ Cookbook," musicdsp.org, 1994.
https://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
[7] B. C. J. Moore, "An Introduction to the Psychology of Hearing,"
6th ed., Brill, 2012.
[8] ISO 226:2003, "Acoustics — Normal equal-loudness-level contours."
[9] H. Haas, "The influence of a single echo on the audibility of speech,"
Acustica, vol. 1, pp. 49-58, 1951.
[10] R. Bristow-Johnson, comment on "I wrote a paper on why your EQ plugin lies
to you at high frequencies," r/DSP, Reddit, May 2026.
https://www.reddit.com/r/DSP/comments/1tqynr5/
[11] S. P. Orfanidis, "Digital parametric equalizer design with prescribed
Nyquist-frequency gain," J. Audio Eng. Soc., vol. 45, no. 6, pp. 444-455,
1997. https://www.collinsaudio.com/Prosound_Workshop/orfanidis%20decramping.pdf
[12] SkoomaDentist, comment on "I wrote a paper on why your EQ plugin lies
to you at high frequencies," r/DSP, Reddit, May 2026.
https://www.reddit.com/r/DSP/comments/1tqynr5/
[13] R. Bristow-Johnson, answer to "Why does the Direct Form I become our first
choice for fixed-point implementation?" DSP Stack Exchange, 2022.
https://dsp.stackexchange.com/questions/76826/why-does-the-direct-form-i-become-our-first-choice-for-fixed-point-implementatio/76855#76855
[14] R. Bristow-Johnson, answer to "Best implementation of a real-time,
fixed-point IIR filter with constant coefficients," DSP Stack Exchange.
https://dsp.stackexchange.com/questions/21792/best-implementation-of-a-real-time-fixed-point-iir-filter-with-constant-coeffic/21811#21811
[15] R. Bristow-Johnson and K. Bogdanowicz, "Intraframe Time-Scaling of
Nonstationary Sinusoids Within the Phase Vocoder," in Proc. IEEE Workshop
on Applications of Signal Processing to Audio and Acoustics (WASPAA),
New Paltz, NY, Oct. 2001, pp. W2001-1–W2001-4.
[16] R. Bristow-Johnson, "Wavetable Synthesis 101, A Fundamental Perspective,"
in Proc. AES 101st Convention, Los Angeles, Nov. 1996, Preprint 4486.
(Also: "A Detailed Analysis of a Time-Domain Formant-Corrected Pitch-Shifting
Algorithm," J. Audio Eng. Soc., vol. 43, no. 5, pp. 340–352, May 1995.
https://aes2.org/publications/elibrary-page/?id=6514)
[17] K. B. Christiansen, "Parametric Equalizer Design with Arbitrary Frequency
Response Specification Near Nyquist," [paper pending author confirmation
— referenced by R. Bristow-Johnson as a more complete treatment than
Orfanidis 1997, resolving the slope discontinuity at Nyquist].
[18] V. Zavalishin, "The Art of VA Filter Design," Urs Heckmann Audio, 2018.
https://www.native-instruments.com/fileadmin/ni_media/downloads/pdf/VAFilterDesign_2.1.0.pdf
(Provides the TPT/trapezoidal integration framework underlying the Simper SVF.)
[19] R. Bristow-Johnson, "The Equivalence of Various Methods of Computing
Biquad Coefficients for Audio Parametric Equalizers," in Proc. AES 97th
Convention, San Francisco, Nov. 1994, Preprint 3906.
(Proves that all biquad topologies produce identical transfer functions —
directly supports §2.3 measurement of 0.000 dB difference between RBJ and SVF.)
[20] U. Zölzer (Ed.), "DAFX: Digital Audio Effects," 2nd ed., Wiley, 2011.
(Standard DSP reference for filter structures, oversampling, and audio effect
implementation methodology cited across §2, §3, §4.)
Further Reading
The following works informed the broader FreeEQ8 ecosystem and are recommended
for readers interested in related DSP topics, though they do not directly support
claims made in this paper:
Robert Bristow-Johnson — additional published work
- "Performance of Low-Order Polynomial Interpolators in the Presence of Oversampled Input," AES preprint — relevant to oversampling interpolation quality in future embedded ports.
- "Effect of DAC Deglitching on Frequency Response" — measurement methodology for audio hardware characterization.
- "Comments on A Digital Approach to Time-Delay Spectrometry" — audio measurement systems context.
Julius O. Smith III — digital filter theory
- "Introduction to Digital Filters with Audio Applications," W3K Publishing / CCRMA online. https://ccrma.stanford.edu/~jos/filters/ Comprehensive treatment of the bilinear transform, IIR filter design, and direct-form topologies. Foundational reading for §2.1–§2.4.
- "Audio Signal Processing in MATLAB." https://ccrma.stanford.edu/~jos/sasp/ Complements the Zölzer DAFX reference [20] for implementation methodology.
Knud Bank Christiansen
- Parametric EQ design work on decramped response near Nyquist. RBJ describes this as a more complete treatment than Orfanidis (1997), resolving the slope discontinuity at Nyquist. Full citation pending; reference [17] will be updated when confirmed.
Vadim Zavalishin
- "The Art of VA Filter Design," 2nd ed. — already cited as [18]. The full book (freely available) covers nonlinear extensions, implicit solvers, and topology-preserving discretization beyond what is cited in §2.2.
Top comments (0)