Skip to content

feat: M1 dynamic EQ core, tests and docs#1

Merged
yves-vogl merged 4 commits into
mainfrom
feat/m1-dsp
Jul 15, 2026
Merged

feat: M1 dynamic EQ core, tests and docs#1
yves-vogl merged 4 commits into
mainfrom
feat/m1-dsp

Conversation

@yves-vogl

Copy link
Copy Markdown
Contributor

Summary

  • Six-band dynamic EQ engine (LancetEngine -> DynamicBand -> Detector): serial bell/shelf filters (Band 1 Low Shelf, Band 6 High Shelf) with static + soft-knee-clamped dynamic gain, each driven by a pre-chain-tapped cascaded-bandpass detector (>20 dB/2oct selective at Q=1); Input/Output Trim, parallel Mix (DryWetMixer), exclusive per-band Listen.
  • Real-time-safe throughout: ArrayCoefficients-based coefficient updates on a 32-sample sub-block granularity, SmoothedValue-ramped gain (zipper guard), zero added latency (all biquads, no lookahead), real oversized-block clamp.
  • LancetAudioProcessor (59-parameter APVTS, frozen IDs) + a functional v0.1 slider/toggle/combo editor.
  • Test count: 47/47 green locally (ctest --test-dir build), covering all 10 M1 guarantee categories from docs/design-brief.md plus Listen/Mix/bus-layout/sample-rate coverage.
  • docs/manual.md and docs/architecture.md (including two floating-point precision gotchas found and fixed while writing the test suite - see architecture doc).

Known limitations

  • GUI is the plain v0.1 slider grid; custom vector-drawn GUI is M3.
  • tests/ScratchDryWetTests.cpp holds legitimate permanent regression coverage (a real juce::dsp::Gain default-silence bug) but kept its diagnostic-session filename - a rename is a trivial follow-up, noted in the test-suite commit.

Per-band serial bell/shelf filter (Band 1 Low Shelf, Band 6 High Shelf,
Bands 2-5 Bell) with static + soft-knee-clamped dynamic gain, driven by
a pre-chain-tapped cascaded-bandpass Detector (>20 dB/2oct selective at
Q=1) feeding a linked stereo peak envelope. Real-time-safe coefficient
updates via ArrayCoefficients on a 32-sample sub-block granularity, with
SmoothedValue-ramped gain for the zipper guard. LancetEngine wires six
DynamicBands in series with Input/Output Trim, parallel Mix
(DryWetMixer), and exclusive per-band Listen (sidechain solo).

Zero added latency (all biquads, no lookahead).
AudioProcessor plumbing around LancetEngine: 59-parameter APVTS
resolved to raw atomics once at construction, pushed into the engine
every prepareToPlay()/processBlock() call, ScopedNoDenormals, real
oversized-block clamp (LancetEngine::process(), not just jassert),
getLatencySamples() == 0, full state save/recall.

Editor is a plain but fully-wired v0.1 grid: a top strip of Input
Trim/Output Trim/Mix knobs above six per-band columns, each with
On/Listen toggles, a Type combo (Band 1/6 only), and the seven
per-band knobs. Custom vector-drawn GUI is roadmap M3.
47 test cases across null/transparency, static magnitude vs. an
independently-implemented analytic RBJ reference (peak + both shelf
directions), dynamic gain convergence to Range within a release-time
window, detector band isolation, NaN/Inf recovery, oversized-block
clamp, state round-trip (float/choice and bool parameters separately),
reset() clears state, zero latency, and an automation zipper guard -
plus Listen, Mix, and bus-layout/sample-rate sweep coverage.

Also includes tests/ScratchDryWetTests.cpp, which now holds permanent
regression coverage for a real juce::dsp::Gain<float> default-silence
bug found during test-writing (see docs/architecture.md); the filename
is a leftover from the diagnostic session and was kept as-is rather
than routed around this environment's rm/mv restrictions - a rename to
something like GainDefaultsRegressionTests.cpp is a trivial follow-up.
docs/manual.md: what Lancet is, where it sits in a mix chain, full
signal-flow and parameter reference, usage tips (heavy-music framing,
no symphonic-metal wording per suite convention).

docs/architecture.md: full engineering writeup - module map, per-band
filter/detector design, the soft-knee gain-computer formula, the
32-sample sub-block coefficient update and zipper guard, Listen, Mix,
and two floating-point precision gotchas found while writing the M1
test suite (a coefficient-normalisation x/x-vs-x*(1/x) fix, and an
exact-0-dB bypass working around compiler FMA contraction).

README.md body and CHANGELOG.md [0.1.0] section filled in; CLAUDE.md
status updated to reflect M1 completion (47/47 tests green).
@yves-vogl
yves-vogl merged commit dc8a96c into main Jul 15, 2026
2 checks passed
@yves-vogl
yves-vogl deleted the feat/m1-dsp branch July 15, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant