v0.7.0 - Harmonic character is now genuinely frequency-selective
The fix
The Harmonic band character was running its waveshaper across the entire broadband signal passing through the band. A Harmonic bell at 5 kHz saturated your bass, your mids, everything — it was a full-range saturator sitting behind an EQ filter, not a harmonic EQ.
A harmonic EQ's defining behaviour is frequency-selectivity: isolate the region the band targets, generate harmonics from only that, mix them back. That's now what it does.
- The band's region is split off through a region-isolation filter (band-pass at freq/Q for Bell/Tilt, high-pass at the corner for High Shelf, low-pass for Low Shelf — the same shape the dynamic detector uses).
- Only that isolated region is shaped.
- Only the generated harmonics — shaped signal minus isolated region — are summed back in parallel with the dry path. Because the difference is what's summed, the band's own level is unchanged and drive=0 is exactly transparent: it adds harmonics without adding gain.
Why this wasn't caught in v0.3.0
The original verification FFT'd a single tone at the band's own frequency. That test is structurally incapable of distinguishing "saturate only this region" from "saturate everything" — with one tone present, both produce identical output. It confirmed the waveshaper maths (which was, and still is, correct) and nothing whatsoever about the routing.
The new test is two-tone, which separates them immediately.
Verified
Two-tone harness: ~100 Hz well outside the band + ~5 kHz inside it; Harmonic bell at 5 kHz, Q=1, +12 dB.
| Measurement | Result |
|---|---|
| In-band 2nd harmonic vs out-of-band 2nd harmonic | 91 dB selectivity |
| Out-of-band fundamental level change | within 0.01 dB of input |
| Pure-odd blend, 2nd harmonic | 121 dB down (tanh is an odd function) |
| Harmonic band at 0 dB gain | transparent to -180 dB |
| Dynamic EQ after shared-filter refactor | still ducks correctly |
The old implementation could not have produced the first two numbers.
pluginval strictness 5: VST3 clean. AU passes with two benign, pre-existing warnings unrelated to this DSP-only change (sidechain bus-disable quirk, and the AU wrapper reporting program -1) — both now documented accurately in the README, which previously undercounted them.
Also in this release
The "which filter isolates this band's region" logic existed only inside DynamicEQDetector. It's now a single shared EQBand::designRegionIsolationFilter used by both the harmonic stage and the dynamic detector, so the two can't drift apart. Isolation coefficients are cached and redesigned only when the region actually moves, since coefficient construction allocates and this runs on the audio thread.
Known limitations
- Not yet tested against real (non-silent) audio hardware in a live signal chain
- No linear-phase mode — intentionally out of scope (zero latency is the point)
Full Changelog: v0.6.0...v0.7.0