Skip to content

fix: apply detune transpose before stretch processing and when detune returns to zero#1100

Merged
mdydek merged 2 commits into
software-mansion:mainfrom
Slavezax:fix/detune-transpose-ordering
Jun 11, 2026
Merged

fix: apply detune transpose before stretch processing and when detune returns to zero#1100
mdydek merged 2 commits into
software-mansion:mainfrom
Slavezax:fix/detune-transpose-ordering

Conversation

@Slavezax

Copy link
Copy Markdown
Contributor

⚠️ Breaking changes ⚠️

None.

Introduced changes

In AudioBufferBaseSourceNode::processWithPitchCorrection, stretch_->setTransposeSemitones(detune) is called after stretch_->process() and only when detune != 0. This causes two audible bugs on pitch-corrected sources:

  1. First render quantum plays untransposed. A freshly created source with detune already set renders its first quantum before the transpose is ever applied to the stretcher. Since every start()/seek goes through a new source node, this produces an audible pitch glitch at the original pitch on every play/seek while detuned. Easy to hear with sustained tonal material (e.g. several stems playing pitch-shifted: each seek briefly "dips" to the original key).

  2. Stale transpose when detune returns to 0. Because the setter is guarded by detune != 0.0f, once the param goes back to zero the previously set transpose is never cleared — the source keeps playing transposed even though detune is 0.

Fix: set the transpose unconditionally and before stretch_->process() so the stretcher always processes with the current detune value. setTransposeSemitones is cheap (it just updates the frequency multiplier), so calling it every quantum is fine.

We've been shipping this change as a patch-package patch in a production audio app (multi-stem playback with real-time pitch shifting) since 0.10.x and it eliminates both glitches.

Checklist

  • Linked relevant issue
  • Updated relevant documentation
  • Added/Conducted relevant tests
  • Performed self-review of the code
  • Updated Web Audio API coverage
  • Added support for web
  • Updated old arch android spec file

🤖 Generated with Claude Code

… returns to zero

In processWithPitchCorrection, setTransposeSemitones was called after
stretch_->process() and only when detune != 0. As a result:

- a freshly created source with detune set played its first render
  quantum untransposed, an audible pitch glitch on every play/seek
  while detuned, since each start() goes through a new source
- when detune went back to 0 the previous transpose was never cleared,
  so the source kept playing transposed

Set the transpose unconditionally before processing instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mdydek

mdydek commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

thank you for the contribution

@mdydek mdydek merged commit 148911c into software-mansion:main Jun 11, 2026
6 checks passed
@mdydek mdydek added the fix label Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants