Skip to content

M1: IR Blend, Distance emulation, inter-IR phase alignment, broadened tests, docs#8

Merged
yves-vogl merged 6 commits into
mainfrom
feat/m1-dsp-docs
Jul 14, 2026
Merged

M1: IR Blend, Distance emulation, inter-IR phase alignment, broadened tests, docs#8
yves-vogl merged 6 commits into
mainfrom
feat/m1-dsp-docs

Conversation

@yves-vogl

Copy link
Copy Markdown
Contributor

Summary

Implements milestone M1 - DSP completion & test coverage for Nave and prepares the v0.1.0 release.

DSP (issue #1, partially - see below)

  • IR Blend: a second, independently loadable impulse-response slot (IR B), crossfaded against the original slot (IR A) via a new IR Blend parameter. Defaults to 0% (IR A only) - bit-identical to the pre-M1 single-IR signal path, so no existing preset's sound changes.
  • Inter-IR phase alignment: loading IR B automatically time-shifts it (src/dsp/IrAlignment.{h,cpp}) so its transient onset matches IR A's most recently loaded onset, before the two are ever blended - prevents comb-filtering from a timing mismatch between independently captured IRs. Alignment is computed in time, not raw samples, so it stays correct across differing IR sample rates.
  • Distance: a simulated mic-to-cab distance control (proximity-effect low-shelf + air-absorption high-shelf, both scaling with the parameter), applied post-convolution/pre-LoCut. Defaults to 0% ("off"), using the same explicit-bypass-at-the-extreme pattern as LoCut/HiCut, so the default state is still a bit-exact passthrough.
  • Updated signal flow: Convolution (IR A/IR B blend) -> Distance -> LoCut -> HiCut -> Mix -> Level.
  • Both IR slots' file paths and the two new parameters round-trip through getStateInformation/setStateInformation.
  • Editor updated with IR B load/default controls and IR Blend/Distance knobs.

Deferred: the "IR browser + bundled IR library" part of issue #1 is not implemented in this pass - see the issue comment for the full rationale (asset-sourcing/licensing concern, not a DSP task; better scoped into M2/M3). Issue #1 is left open.

Test coverage (issue #2 - closes)

  • New tests/IrAlignmentTests.cpp: onset detection, sample shifting, cross-sample-rate alignment.
  • New engine-level tests for IR Blend (0%/50%/100%) and Distance (passthrough at 0%, measurable attenuation at 100%).
  • New tests/CoverageTests.cpp: sample-rate sweep (44.1-192 kHz) null + finite-output tests, mono/stereo/unsupported bus-layout tests, and long-run (2000-block, and 300-block-with-loaded-IRs) NaN/Inf stability soak tests.
  • Existing parameter/state/latency tests extended for the two new parameters and the IR B file path.
  • The original null/reference and latency tests are untouched in behaviour and stay green throughout.

50 Catch2 test cases green (up from 22), each well under a second locally.

Documentation

  • New docs/manual.md: full user manual (what Nave is, signal flow, complete parameter reference, usage tips).
  • README.md: updated feature list, signal-flow diagram, parameter table, and roadmap (now matching the actual GitHub milestones M1-M4).
  • docs/architecture.md: updated signal-flow diagram, new sections on IR Blend/phase alignment and Distance emulation, updated latency/smoothing/real-time-safety sections for the two-slot convolution engine.
  • CLAUDE.md: Status/DSP sections updated.

Versioning

  • CMake project version was already 0.1.0 (no change needed).
  • CHANGELOG.md: moved Unreleased content into a [0.1.0] - 2026-07-14 section documenting everything above, including the deferral.

Test evidence

export CPM_SOURCE_CACHE="$HOME/.cache/CPM"
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build --target Tests Nave_Standalone --parallel 3
ctest --test-dir build --output-on-failure
  • Tests and Nave_Standalone build cleanly (no warnings).
  • ctest: 100% tests passed, 50/50, ~11s total wall time.

Closes

Closes #2

Pure, off-audio-thread onset-detection and time-shift helpers used to
align a secondary impulse response's transient onset to a primary
one's before the two are blended together, preventing comb-filtering
from timing mismatches between independently captured IRs.
- CabConvolutionEngine now owns a second convolution slot (IR B) and
  crossfades it against IR A via a new IR Blend parameter (default 0%,
  bit-identical to the previous single-IR signal path). Loading IR B
  phase-aligns it against IR A's onset first.
- Adds a Distance parameter emulating mic-to-cab distance via a
  proximity-effect low-shelf and an air-absorption high-shelf, applied
  post-convolution/pre-LoCut. Defaults to 0% ("off"), using the same
  bypass-at-the-extreme pattern as LoCut/HiCut so the default state
  stays a bit-exact passthrough.
- PluginProcessor/PluginEditor wire both new parameters end-to-end,
  including IR B file load/default controls, state save/load, and
  latency reporting across both convolution slots.
- Unit tests for IrAlignment (onset detection, sample shifting,
  cross-sample-rate alignment).
- Engine-level tests for IR Blend (0%/50%/100%) and Distance
  (passthrough at 0%, measurable attenuation at 100%).
- Parameter/state/latency test updates for the two new parameters and
  the IR B file path.
- New CoverageTests.cpp: sample-rate sweep (44.1-192 kHz) null and
  finite-output tests, mono/stereo/unsupported bus-layout tests, and
  long-run (2000-block, and 300-block-with-loaded-IRs) NaN/Inf
  stability soak tests.

50 Catch2 test cases green (up from 22).
- New docs/manual.md: full user manual (what Nave is, signal flow,
  complete parameter reference, usage tips).
- README.md: updated feature list, signal-flow diagram, parameter
  table, and roadmap to match the actual GitHub milestones (M1-M4).
- docs/architecture.md: updated signal-flow diagram and added
  sections on IR Blend/inter-IR phase alignment and Distance
  emulation; latency/parameter-smoothing/real-time-safety sections
  updated for the two-slot convolution engine.
- CLAUDE.md: Status/DSP sections updated to reflect the M1 DSP work
  and the deferred "bundled IR library" scope.
- CHANGELOG.md: moved Unreleased content into a 0.1.0 section and
  documented everything added in this pass, including what was
  deferred and why.
CabConvolutionEngine::process() copied the scratch buffer for IR B's
convolution *after* convolution.process() (IR A) had already mutated
`block` in place, so the "B" side of the crossfade convolved IR A's
already-processed output instead of the original dry input -
IR_B(IR_A(input)) instead of the intended IR_B(input). This only
stayed hidden while IR A was the untouched identity/delta IR, exactly
the setup every prior IR Blend test used.

Copy the pre-convolution samples into scratchBuffer before
convolution.process() runs, so both convolution branches process the
same original input. Add a regression test that loads two distinct,
non-identity IRs into both slots (the plugin's headline use case) and
verifies the blended output against references rendered independently
of the blend code path - a naive reference derived from the buggy
blend branch itself would stay linear in Blend and mask the defect.
Add an explicit note that both convolution branches process the same
original dry input rather than being chained, and reference the
regression test coverage - the docs already described this (correct)
intended behaviour, but didn't call out the ordering that makes it
true, which is exactly what the CabConvolutionEngine.cpp fix in the
previous commit restores.
@yves-vogl
yves-vogl merged commit 29dc77b into main Jul 14, 2026
2 checks passed
@yves-vogl
yves-vogl deleted the feat/m1-dsp-docs branch July 14, 2026 16:20
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.

Broaden test coverage

1 participant