ivanpaulovich
`WavStreamInstance::seek()` selects the decoder path by testing the codec **union** pointer instead of the parsed filetype: ```cpp // src/audiosource/wav/soloud_wavstream.cpp result WavStreamInstance::seek(double aSeconds, float* mScratch, unsigned int mScratchSize) { if (mCodec.mOgg) // <-- mCodec is a UNION: non-null for drwav*/drflac*/drmp3* too { int pos = (int)floor(mBaseSamplerate * aSeconds); stb_vorbis_seek(mCodec.mOgg, pos); ... ``` `mCodec` is a union of `stb_vorbis*` / `drwav*` / `drflac*` / `drmp3*`, so for any **non-ogg** WavStream with a live decoder this condition is true and `stb_vorbis_seek()` runs on a foreign decoder object. `getAudio()` and `rewind()` correctly `switch (mParent->mFiletype)`; `seek()` is the one branch that doesn't. **Impact (observed in production, macOS):** a looping WAV-typed `WavStream` reaches end-of-stream, `mixBus_internal`'s loop path calls `voice->seek(...)` on the audio thread, and `stb_vorbis_seek` walks the `drwav` as if it were a vorbis handle. In our crashes it ended in `stb_vorbis_stream_length_in_samples()`'s error path writing `f->total_samples = 0xffffffff` into the foreign allocation — corrupting whatever object the heap had placed at that offset (we chased a "gesture recognizer" crash for days before tracing it back here). Any looping `.wav`/`.flac`/`.mp3` stream triggers it on the first loop; sanitizers don't flag it because both objects are live. **Repro sketch:** ```cpp SoLoud::Soloud soloud; soloud.init(); SoLoud::WavStream ws; ws.load("anything.wav"); // filetype = WAVSTREAM_WAV int h = soloud.play(ws); soloud.setLooping(h, true); // ...let it play to the end of the file; the loop-seek runs // stb_vorbis_seek() on the live drwav* -> heap corruption / UB. ``` **Fix (matches getAudio/rewind):** ```diff result WavStreamInstance::seek(double aSeconds, float* mScratch, unsigned int mScratchSize) { - if (mCodec.mOgg) + if (mParent->mFiletype == WAVSTREAM_OGG && mCodec.mOgg) { ``` Non-ogg streams then fall through to the generic `AudioSourceInstance::seek()` (rewind + skip), which also makes looping WAV streams actually loop — with the current code the vorbis mis-seek fails, `while (seek == SO_NO_ERROR)` breaks, and the looping voice silently dies at its first loop point. Happy to send this as a PR if useful.