From the 2026-07-03 comprehensive review. Three compounding bugs produce audible clicks at every loop boundary; fix as one unit.
- Redundant wrap re-seek (AudioSystem.cpp:382-401):
expectedSample is computed from the wrapped mediaLocalFrame (:349-371), so at each loop boundary it jumps ~sourceLength→0 — far past the oneTickSamples*3 threshold — firing seekPending on every wrap. The decode worker already loops seamlessly on its own (AudioDecodeWorker.cpp:74-81) and has ~1 s of looped audio buffered; the controller's redundant seek clears that ring and re-seeks. Any Loop/PingPong clip whose source is shorter than its authored window clicks at every wrap.
- swresample not flushed on seek (AudioDecoder.cpp:146-147):
av_seek_frame + avcodec_flush_buffers leave m_swr internal delay/buffered samples intact → stale resampled audio emitted first after every seek, and swr_get_delay accounting is off. Drain (swr_convert null input) or reinit swr on seek.
- ring.clear() races the RT mixer (AudioRingBuffer.hpp:67-70): clear()'s own doc says "only call when the consumer is known idle," but the decode worker calls
w->ring.clear() on seek (AudioDecodeWorker.cpp:47) while mixSource.active is still true at a loop wrap (AudioSystem.cpp:300-301) → relaxed-store race with read(). Stays in-bounds (indices are % capacity) so glitch-not-crash, but it is the audible artifact path.
From the 2026-07-03 comprehensive review. Three compounding bugs produce audible clicks at every loop boundary; fix as one unit.
expectedSampleis computed from the wrappedmediaLocalFrame(:349-371), so at each loop boundary it jumps ~sourceLength→0 — far past theoneTickSamples*3threshold — firingseekPendingon every wrap. The decode worker already loops seamlessly on its own (AudioDecodeWorker.cpp:74-81) and has ~1 s of looped audio buffered; the controller's redundant seek clears that ring and re-seeks. Any Loop/PingPong clip whose source is shorter than its authored window clicks at every wrap.av_seek_frame+avcodec_flush_buffersleavem_swrinternal delay/buffered samples intact → stale resampled audio emitted first after every seek, andswr_get_delayaccounting is off. Drain (swr_convertnull input) or reinit swr on seek.w->ring.clear()on seek (AudioDecodeWorker.cpp:47) whilemixSource.activeis still true at a loop wrap (AudioSystem.cpp:300-301) → relaxed-store race withread(). Stays in-bounds (indices are % capacity) so glitch-not-crash, but it is the audible artifact path.