// // OGG Vorbis decoder. Jellyfin always serves us /universal as OGG, so this // is the single decode path. Returns Sound_Data with type LINEAR_SAMPLE_ARRAY // — same buffer the visualizer reads via sound_data.samples (no parallel // decoder, no cursor desync). // // We do NOT use stb_vorbis_decode_memory: that malloc()s the output buffer // with libc's allocator, and Sound_Player's release_asset path frees it // with Jai's allocator → SEGV at song end. Instead we open the stream, // allocate the PCM buffer ourselves, and decode into it. Same allocator on // both ends. // decode_ogg :: (bytes: string, name: string) -> Sound.Sound_Data, bool { err: s32; v := Stb_Vorbis.stb_vorbis_open_memory(bytes.data, cast(s32) bytes.count, *err, null); if !v { log_error("decode_ogg: open failed for '%' (err=%)", name, err); return .{}, false; } defer Stb_Vorbis.stb_vorbis_close(v); info := Stb_Vorbis.stb_vorbis_get_info(v); nch := cast(s64) info.channels; if nch <= 0 || info.sample_rate == 0 { log_error("decode_ogg: bad info for '%' (ch=%, rate=%)", name, info.channels, info.sample_rate); return .{}, false; } total_frames := cast(s64) Stb_Vorbis.stb_vorbis_stream_length_in_samples(v); if total_frames <= 0 { log_error("decode_ogg: zero-length stream for '%'", name); return .{}, false; } total_samples := total_frames * nch; samples := cast(*s16) alloc(total_samples * size_of(s16)); if !samples { log_error("decode_ogg: alloc failed for '%'", name); return .{}, false; } // stb_vorbis sometimes hands back fewer frames than the header advertised // (chained / corrupt streams), so loop until it returns 0 and use what // we got. decoded_frames: s64 = 0; while decoded_frames < total_frames { remaining_shorts := cast(s32) ((total_frames - decoded_frames) * nch); n := Stb_Vorbis.stb_vorbis_get_samples_short_interleaved( v, cast(s32) nch, samples + decoded_frames * nch, remaining_shorts); if n <= 0 break; decoded_frames += n; } if decoded_frames == 0 { log_error("decode_ogg: no frames decoded for '%'", name); free(samples); return .{}, false; } actual_samples := decoded_frames * nch; sd: Sound.Sound_Data; sd.name = copy_string(name); sd.loaded = true; sd.type = .LINEAR_SAMPLE_ARRAY; sd.nchannels = cast(u16) nch; sd.sampling_rate = cast(u32) info.sample_rate; sd.nsamples_times_nchannels = actual_samples; sd.samples = samples; sd.buffer.count = actual_samples * size_of(s16); sd.buffer.data = cast(*u8) samples; log_info("decode_ogg: '%' ch=%, rate=%, frames=%", name, nch, info.sample_rate, decoded_frames); return sd, true; }