fix: properly clear all internal buffers to prevent audio artifacts after seeking#34
Merged
cutterbl merged 3 commits intocutterbl:masterfrom Feb 4, 2026
Merged
Conversation
Previously, calling clear() on SoundTouch instances would not fully reset internal state, causing buzz/crackling sounds when reusing instances after seeking. This was because: 1. FifoSampleBuffer.clear() only reset pointers, not the actual data 2. RateTransposer had no clear() method at all 3. Stretch.clearMidBuffer() only nullified midBuffer when dirty flag was set, and never cleared refMidBuffer (correlation reference buffer) Changes: - FifoSampleBuffer: Zero the actual Float32Array data - RateTransposer: Add clear() method that resets interpolation state - Stretch: Always zero midBuffer and refMidBuffer, reset skipFract This eliminates the need to recreate SoundTouch instances on every seek, reducing GC pressure in real-time audio applications. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When midBuffer is zeroed but not null, the initialization block in process() is skipped because the check `this.midBuffer === null` returns false. This causes the algorithm to use zeroed samples for overlap calculations, resulting in audio pops. By setting midBuffer to null, we trigger proper reinitialization where process() creates a fresh Float32Array and fills it with actual audio samples from the input buffer. This ensures overlap calculations use real audio data, eliminating the pops when seeking. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Owner
|
@rosostolato Thanks for your PR. And thank you for digging in to work it out. Great explanation of actual vs expected behavior, and cause and solution. I will review everything over the weekend, and we'll see if we can't get this in play. (no pun intended ;) ) |
Contributor
Author
|
This was all claude's help but worked to me |
Contributor
Author
|
There's another bug still going on that makes the audio pop when seeking and playing but I can't find what it is. Idk if it's in the lib or my implementation |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When reusing SoundTouch instances after seeking (calling
clear()), audio artifacts (buzz, pops, crackling) would persist for 1-2 seconds. This made it impractical to reuse instances, forcing developers to recreate SoundTouch objects on every seek, which causes GC pressure in real-time audio applications.Root Cause
The
clear()method had three issues:FifoSampleBuffer.clear() only reset pointers, not the actual Float32Array data. Stale audio samples remained in memory and bled into subsequent processing.
RateTransposer had no
clear()method. The interpolation state (prevSampleL,prevSampleR,slopeCount) persisted, causing discontinuities when processing resumed.Stretch.clearMidBuffer() had two problems:
midBufferwhenmidBufferDirtywas truerefMidBuffer(correlation reference buffer), causing the WSOLA algorithm to find incorrect overlap positionsSolution
FifoSampleBuffer.js
RateTransposer.js
Stretch.js
Why midBuffer = null instead of fill(0)?
Setting midBuffer = null triggers the initialization block in process():
This ensures midBuffer is populated with actual audio samples from the new position, rather than zeros. When midBuffer contains zeros, the cross-correlation calculations produce arbitrary results, leading to incorrect overlap positions and audio artifacts.
Testing
Tested in a multi-track audio player with pitch shifting:
Breaking Changes
None. This is a bug fix that makes clear() work as expected.