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.
5. Allocation-Free Semantic Analysis (ResonanceDetector)
Traditional "smart EQ" products apply machine-learning inference models (Soothe2,
iZotope Neutron) — opaque, CPU-heavy, non-deterministic. We introduce a
deterministic alternative that adds zero latency and allocates no memory:
- Take the 2048-bin log-magnitude spectrum from
SpectrumFIFO (UI thread rate).
- Re-sample into a 96-bin log-frequency grid (20 Hz → Nyquist).
- Estimate local baseline via ±0.5-octave moving average.
- Flag peaks where
(magnitude − baseline) ≥ 3 dB and peak is local max in
±3-bin neighbourhood.
- Score each peak:
score = deviation ×ばつ intentWeight(hz, mode) where
intentWeight is a log-frequency Gaussian bump per IntentMode profile.
- Return top-4 suggestions:
{freqHz, gainDb, q, confidence, label}.
The intentWeight function encodes instrument-specific problem zones:
| Mode |
Primary Bump |
Zone |
| VocalClean |
×ばつ1.6 at 300 Hz |
mud |
| DrumPunch |
×ばつ1.5 at 300 Hz |
boxiness |
| GuitarSpace |
×ばつ1.5 at 250 Hz |
mud |
| MasterPolish |
×ばつ1.3 at 250 Hz |
low-end buildup |
Semantic labels from FrequencyExplainer.h map frequency ranges to strings
("mud", "harshness", "sibilance", "air") enabling explain-on-hover UX.
7. 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 -ISource.
Platform: Linux x86-64, g++ 13.3.0. Median of 16 trials, 4 warmup runs discarded.
7.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% |
×ばつ |
| SVF 8-band stereo |
72.7 |
55 |
0.63% |
×ばつ |
| SVF overhead vs RBJ |
×ばつ |
— |
— |
— |
| SVF DynEQ per-sample |
68.8 |
58 |
0.61% |
×ばつ |
7.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% |
×ばつ |
| 8 |
46.6 |
0.41% |
73.4 |
0.65% |
×ばつ |
| 32 |
47.5 |
0.42% |
75.1 |
0.66% |
×ばつ |
| 64 |
46.4 |
0.41% |
75.9 |
0.67% |
×ばつ |
| 128 |
46.8 |
0.41% |
75.5 |
0.67% |
×ばつ |
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.
7.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% |
×ばつ |
Finding: Even at absolute worst-case (8 active dynamic bands tracking white
noise), the variable-cadence engine keeps CPU below 3.3%. The ×ばつ headroom
means a 50% CPU budget can host ~9 simultaneous worst-case dynamic EQ instances.
7.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% |
×ばつ faster |
The mono vs stereo difference accounts for half the gap. With AVX2 active
(8-wide float32), projected improvement is an additional ×ばつ over scalar,
targeting < 10 ns/sample for all 8 bands mono — approaching 0.09% CPU.
7.5 MatchEQ Hot-Path Optimization
| Path |
ns/sample equivalent |
Speedup |
| Naive pow(10) per bin (old) |
7.4 |
— |
| Pre-computed correctionGain
|
2.8 |
×ばつ |
7.6 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 -ISource
./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 -ISource
./ArcBench --json > arc_results.json
Numbers will vary by CPU and compiler. The headroom ratios should remain
comfortably above ×ばつ on any modern x86-64 or Apple Silicon machine.
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.
7. Future Work (v2.2.5+)
-
Explicit SIMD vectorisation: group 8 bands into
juce::dsp::SIMDRegister<float>,
processing 4 bands per SSE instruction or 8 via AVX2.
-
Cross-instance masking negotiation: via ARC-Core local IPC spine, multiple
plugin instances communicate energy peaks and negotiate inverse dynamic notches.
-
Spectral dynamics mode: per-bin FFT threshold clamping (Soothe2 territory)
using the existing overlap-add Match EQ infrastructure.
-
Dolby Atmos 9.1.6: expand
isBusesLayoutSupported for discrete immersive
channel arrays with spatial zone linking.
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