Skip to content

Make renderer gain slew callback-partition invariant - #320

Draft
dissonance-git wants to merge 6 commits into
mgth:mainfrom
dissonance-git:upstream/sample-time-gain-slew
Draft

Make renderer gain slew callback-partition invariant#320
dissonance-git wants to merge 6 commits into
mgth:mainfrom
dissonance-git:upstream/sample-time-gain-slew

Conversation

@dissonance-git

Copy link
Copy Markdown

What changed

  • keep ChannelState gain slew at one constant sample-time rate across callback boundaries
  • clamp only after the target is actually reached instead of dilating the last gain fraction to the callback edge
  • carry the same start/rate/end segment into binaural rendering, so the headphone path no longer applies block-end gain as a callback-rate step
  • add an adversarial regression that renders one continuous fade under two radically different callback partitions and requires sample-equivalent output

Why

Host callback boundaries are transport artifacts. The current slew computes the final partial step as remaining_delta / sample_length, so a callback that straddles the target changes the audible gain trajectory. In binaural mode, the block-end gain is also applied to the whole block, making the trajectory even more directly callback-size dependent.

The regression uses the direct LFE headphone route to avoid HRTF/reverb differences and compares the same 1200-sample fade under 40-sample blocks versus an irregular partition that crosses the 960-sample slew endpoint inside a 273-sample callback.

Validation

The branch is based directly on upstream b1b78a89bb83417d7ff6ae73a617983269bd12a8. This PR is opened as draft so upstream CI can run the repository's format/build/test gates before it is marked ready.

@mgth

mgth commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Thanks — the fix is sound and the adversarial partition test is exactly the kind of regression we want pinned. I ran the branch locally on top of b1b78a8: the new test passes, but two golden null tests fail, which is the expected consequence of the intended trajectory change:

  • null_binaural_kemar — peak residual −24.8 dBFS, RMS −44.6 dBFS (the binaural path moving from block-end constant gain to per-sample interpolation)
  • null_crossover_bands — the speaker path's end-of-ramp clamp behaviour

Two requests before marking this ready:

  1. Re-bless the goldens with OMNIPHONY_BLESS_GOLDENS=1 cargo test -p renderer and quote the printed residuals in the PR description, as the harness instructs. CI runs the full suite, so it will stay red until then.

  2. Make GainSlew::at() branchless. It sits in the per-sample hot loops of the speaker stage, and this repo's convention is to keep per-sample work branch-free. The sign test on step is loop-invariant, so cheap in practice, but it can block auto-vectorisation. Since the ramp is monotonic from start toward end, hoisting the bounds once per block does it:

    let lo = self.start.min(self.end);
    let hi = self.start.max(self.end);
    // per sample:
    (self.start + self.step * sample_idx as f32).clamp(lo, hi)

    which compiles to fma + minss/maxss. Same pattern applies to the two inlined gain computations in binaural/mod.rs (hoist lo/hi out of the for s in 0..span loops).

One nit, no change needed: with ramp_samples <= 0.0 the new code jumps to the target at the first sample instead of ramping over one block. Unreachable today (GAIN_SLEW_SECS is a positive constant), just noting it's a deliberate semantic.

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.

2 participants