# 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`: ```cpp 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):** ```cpp 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: 1. **DC gain** — typically 0 dB (unity passthrough at DC) 2. **Resonant frequency** fc — where the peak or notch is placed 3. **Peak gain** — the dB boost or cut at fc 4. **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>, 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:** ```cpp // 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:** ```cpp // 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:** ```cpp // 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:** ```cpp // 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:** ```cpp // 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: ```cpp // 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: ```cpp 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: 1. **Intent-aware resonance detection** (instrument-specific frequency biasing) 2. **Log-frequency baseline normalization** (robust to spectral tilt) 3. **Explain-on-hover semantic UX** (actionable frequency descriptions) 4. **One-click suggestion apply** (direct APVTS integration, undo-able) 5. **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[] (v2.2.1) | 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 ```bash 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`, 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 `ResonanceDetector` to 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 `isBusesLayoutSupported` for 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:** ```cpp // 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: ```cpp // 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, 3>`. The audio thread indexes into this array; it never calls `new` or `delete`. ```cpp // PluginProcessor.h std::array>, 3> oversamplers; // prepareToPlay() — allocate once for (int i = 0; i < 3; ++i) oversamplers[i] = std::make_unique>( 2, i + 1, Oversampling::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}: ```cpp // 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: ```cpp // 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: ```cpp 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): ```cpp // 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: ```cpp 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: `IOPlatformUUID` from IOKit - Windows: `HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid` - Linux: `/etc/machine-id` or `/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)](https://dev.to/tizwildin/we-eliminated-eq-frequency-cramping-without-oversampling-heres-how-dafx26-paper-4f7l) - **dev.to**: [FreeEQ8 Technical Architecture](https://dev.to/tizwildin/real-time-state-space-parameterization-and-lock-free-semantic-analysis-in-digital-equalization-38jn) - **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.