Symptom
When seeking within a playing track (e.g. dragging the position bar from 1:00 to 1:30), the user hears ~500 ms of the pre-seek audio drain before the new position kicks in, producing an audible glitch / brief overlap. Reproduced on Windows, cpal shared mode, FLAC and AAC sources.
Root cause
The seek path in src-tauri/crates/app/src/audio/decoder.rs calls stream.seek_ms(ms) to reposition the symphonia reader, but the rtrb::Producer → cpal callback ring buffer keeps every sample already pushed before the seek. With the typical ring sized for several hundred ms of headroom, the user hears:
- ~200-500 ms of samples from the pre-seek position drain from the ring
- A discontinuity at the boundary (audible pop/click)
- Playback continues at the new position
A glance at the seek handlers confirms no ring drain happens:
ControlFlow::Seek(ms) => {
// …
stream.seek_ms(ms); // only the symphonia reader moves
// …
}
The resampler (rubato::FftFixedIn) also keeps internal history; without a reset, ~10-30 ms of interpolated samples are computed from the old position and bleed into the new one.
Fix sketch
In every Seek branch of the decoder loop (ControlFlow::Seek, PushOutcome::Seek, A-B loop wrap):
- Pause the cpal callback consumption (e.g. set a
seek_in_progress atomic the callback checks and zeroes its output during)
- Drain the rtrb ring — either pop until empty client-side, or rebuild the producer/consumer pair and swap the producer through
AudioCmd::SwapProducer
- Reset the resampler state (
Resampler::reset() if exposed, else rebuild it with the same target rate)
- Apply a 5-10 ms fade-in on the first post-seek block to hide any residual discontinuity (cheap insurance even after the drain)
- Resume the callback
Acceptance criteria
Notes
Phase 1.a waveflow-core extraction did not touch the audio engine — this bug is pre-existing on main. Surfaced via a user report on the v1.4.0 candidate.
Related callsites in decoder.rs: ControlFlow::Seek(ms), PushOutcome::Seek(ms), A-B loop wrap branch around loop_a_ms.
Symptom
When seeking within a playing track (e.g. dragging the position bar from 1:00 to 1:30), the user hears ~500 ms of the pre-seek audio drain before the new position kicks in, producing an audible glitch / brief overlap. Reproduced on Windows, cpal shared mode, FLAC and AAC sources.
Root cause
The seek path in
src-tauri/crates/app/src/audio/decoder.rscallsstream.seek_ms(ms)to reposition the symphonia reader, but thertrb::Producer → cpal callbackring buffer keeps every sample already pushed before the seek. With the typical ring sized for several hundred ms of headroom, the user hears:A glance at the seek handlers confirms no ring drain happens:
The resampler (
rubato::FftFixedIn) also keeps internal history; without a reset, ~10-30 ms of interpolated samples are computed from the old position and bleed into the new one.Fix sketch
In every
Seekbranch of the decoder loop (ControlFlow::Seek,PushOutcome::Seek, A-B loop wrap):seek_in_progressatomic the callback checks and zeroes its output during)AudioCmd::SwapProducerResampler::reset()if exposed, else rebuild it with the same target rate)Acceptance criteria
seek_mshelper)Notes
Phase 1.a
waveflow-coreextraction did not touch the audio engine — this bug is pre-existing onmain. Surfaced via a user report on the v1.4.0 candidate.Related callsites in
decoder.rs:ControlFlow::Seek(ms),PushOutcome::Seek(ms), A-B loop wrap branch aroundloop_a_ms.