Skip to content

fix: post-rename findings (reset(), midFilter reset, tail length, chunking coverage)#63

Merged
yves-vogl merged 5 commits into
mainfrom
fix/post-rename-findings
Jul 16, 2026
Merged

fix: post-rename findings (reset(), midFilter reset, tail length, chunking coverage)#63
yves-vogl merged 5 commits into
mainfrom
fix/post-rename-findings

Conversation

@yves-vogl

Copy link
Copy Markdown
Collaborator

Summary

Test plan

🤖 Generated with Claude Code

…stop/loop

CryptaAudioProcessor never overrode juce::AudioProcessor::reset(), so a
host transport-stop/loop/rewind (which calls reset(), not prepareToPlay())
never flushed the LR4 crossover's filter memory, gate/compressor
envelopes, the high-band voicing's oversampling/mid/tone filter state,
the low-band latency-compensation delay line, EQ biquad history, or the
IR convolution engine - all of which already exposed their own
real-time-safe reset() but were never wired into a processor-level
override.

Added tests/ResetTests.cpp ("reset(): clears DSP stage state so silence
right after a loud signal is actually silent"), confirmed red before the
fix (rms 0.46 with the base-class no-op reset() vs a margin of 1e-4),
green after wiring every stage's reset() into the new override.

Fixes #56.
Voicing::setVoicing() recomputed midFilter's coefficients on every
voicing change but, unlike preHighPass's explicit Razor-switch handling,
never reset its state - a stale biquad delay-line combined with a
freshly-swapped coefficient set (e.g. Wool's -6dB scoop jumping to
Razor's +5dB hump) rings a spurious transient that a plain "silence
right after the switch" magnitude check can't isolate: the oversampling
stage's own FIR flush tail and the boost/cut gain difference between
voicings both legitimately leave residual regardless of the fix.

Added tests/VoicingTests.cpp's on-resonance/off-resonance ratio test,
which isolates midFilter's own stale-state contribution from those
confounds by comparing the post-switch residual when the excite signal
sits on Wool's own mid-filter frequency (500Hz, maximises midFilter's
stored state) against off-resonance (20Hz, minimises it, leaving only
the shared FIR/gain confound). Confirmed red before the fix (ratio 2.459
against a 2.35 threshold), green after wiring in midFilter.reset().

Fixes #57.
CryptaAudioProcessor::getTailLengthSeconds() unconditionally returned
0.0, correct only while IRLoader has its single-sample identity IR
installed. loadImpulseResponse() is a live, callable seam into a real
juce::dsp::Convolution with no length cap, so once a real IR is loaded
the plugin's actual output tail grows to that IR's length while
getTailLengthSeconds() kept reporting 0.0 - a host trusting that value
for bounce/freeze/render-tail decisions could truncate the convolution
tail.

IRLoader now tracks the loaded IR's own duration (numSamples /
irSampleRate at load time - invariant under Convolution's internal
resampling to the session rate), exposed via
IRLoader::getTailLengthSeconds() and wired into the processor's
override.

Added tests/TailLengthTests.cpp, confirmed red before the fix (both the
"reflects loaded IR length" and "independent of session sample rate"
cases failed with the hardcoded 0.0), green after wiring the tracked
duration through.

Fixes #58.
processBlock()'s chunking logic (splitting a host block into sub-blocks
of at most prepareToPlay()'s samplesPerBlock, to avoid overrunning
lowBandBuffer/highBandBuffer and Voicing's fixed-size Oversampling/
DryWetMixer buffers) was never exercised by any test: every existing
test only ever calls processBlock() with a buffer sized at or below what
prepareToPlay() promised, so the multi-iteration chunking branch never
ran under CI.

Added tests/ChunkingTests.cpp, comparing a single oversized (and
non-multiple-of-block-size) processBlock() call against the same signal
fed through in properly host-sized sub-blocks. This test passed
immediately (no red state): the chunking loop in PluginProcessor.cpp was
already correct, just untested - this closes that coverage gap rather
than fixing a functional bug, and is a Release-safe path (no
jassert-only branching in the code under test).

Fixes #59.
CLAUDE.md and docs/manual.md still carried the pre-suite-bible
"symphonic-metal" framing; reworded to "heavy music" (the suite bible's
production/mix-context term) to match the rest of the documentation.
@yves-vogl
yves-vogl merged commit 3666558 into main Jul 16, 2026
2 checks passed
@yves-vogl
yves-vogl deleted the fix/post-rename-findings branch July 16, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment