From 2859c082179e19f0076a699174f7fa588234e465 Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 1 Mar 2026 18:05:25 +0100 Subject: feat(audio): add experimental MP3 on-demand range decoder Adds mp3_open/mp3_decode_range/mp3_close API backed by miniaudio ma_decoder for in-memory MP3 assets. Guarded by #if !defined(STRIP_ALL); any use in stripped builds is a compile error. No new dependencies: drmp3 is already compiled via MINIAUDIO_IMPLEMENTATION in audio.cc. handoff(Gemini): mp3_sample.{h,cc} in AUDIO_SOURCES. Usage: Mp3Decoder* d = mp3_open(GetAsset(id, &sz), sz); mp3_decode_range(d, start_frame, num_frames, pcm_out); mp3_close(d); Co-Authored-By: Claude Sonnet 4.6 --- cmake/DemoSourceLists.cmake | 1 + src/audio/mp3_sample.cc | 46 +++++++++++++++++++++++++++++++++++++++++++++ src/audio/mp3_sample.h | 30 +++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/audio/mp3_sample.cc create mode 100644 src/audio/mp3_sample.h diff --git a/cmake/DemoSourceLists.cmake b/cmake/DemoSourceLists.cmake index 3439cd5..b31c482 100644 --- a/cmake/DemoSourceLists.cmake +++ b/cmake/DemoSourceLists.cmake @@ -18,6 +18,7 @@ set(AUDIO_SOURCES src/audio/spectrogram_resource_manager.cc src/audio/audio_engine.cc src/audio/spectral_brush.cc + src/audio/mp3_sample.cc ) # Procedural sources (unconditional) diff --git a/src/audio/mp3_sample.cc b/src/audio/mp3_sample.cc new file mode 100644 index 0000000..2036acd --- /dev/null +++ b/src/audio/mp3_sample.cc @@ -0,0 +1,46 @@ +// This file is part of the 64k demo project. +// Experimental MP3 sample decoding via miniaudio (non-STRIP_ALL only). +// Compiles to nothing in stripped builds. + +#include "mp3_sample.h" + +#if !defined(STRIP_ALL) + +// miniaudio.h is already compiled with MINIAUDIO_IMPLEMENTATION in audio.cc. +// Include here without it to get struct/function declarations only. +#include "miniaudio.h" + +// Concrete definition (opaque to callers via header forward declaration). +struct Mp3Decoder { + ma_decoder dec; +}; + +Mp3Decoder* mp3_open(const uint8_t* data, size_t size) { + Mp3Decoder* d = new Mp3Decoder(); + ma_decoder_config cfg = ma_decoder_config_init(ma_format_f32, 1, 32000); + if (ma_decoder_init_memory(data, size, &cfg, &d->dec) != MA_SUCCESS) { + delete d; + return nullptr; + } + return d; +} + +int mp3_decode_range(Mp3Decoder* dec, int start_frame, int num_frames, + float* out) { + if (ma_decoder_seek_to_pcm_frame(&dec->dec, (ma_uint64)start_frame) != + MA_SUCCESS) { + return 0; + } + ma_uint64 frames_read = 0; + ma_decoder_read_pcm_frames(&dec->dec, out, (ma_uint64)num_frames, + &frames_read); + return (int)frames_read; +} + +void mp3_close(Mp3Decoder* dec) { + if (!dec) return; + ma_decoder_uninit(&dec->dec); + delete dec; +} + +#endif // !STRIP_ALL diff --git a/src/audio/mp3_sample.h b/src/audio/mp3_sample.h new file mode 100644 index 0000000..e8229f2 --- /dev/null +++ b/src/audio/mp3_sample.h @@ -0,0 +1,30 @@ +// This file is part of the 64k demo project. +// Experimental MP3 sample decoding (non-STRIP_ALL only). +// Entire API unavailable when STRIP_ALL is defined (compile error on use). + +#pragma once +#include +#include + +#if !defined(STRIP_ALL) + +// Opaque MP3 decoder. Create with mp3_open(), destroy with mp3_close(). +// Output format: mono f32 PCM at 32 kHz (matches synth sample rate). +// |data| passed to mp3_open() must remain valid for the decoder's lifetime +// (embedded asset byte arrays satisfy this automatically). +struct Mp3Decoder; + +// Open an in-memory MP3 blob for range decoding. +// Returns nullptr on error. +Mp3Decoder* mp3_open(const uint8_t* data, size_t size); + +// Seek to |start_frame| and decode up to |num_frames| f32 mono samples into +// |out|. Returns the number of frames actually decoded (may be < num_frames at +// end of stream). +int mp3_decode_range(Mp3Decoder* dec, int start_frame, int num_frames, + float* out); + +// Release the decoder. +void mp3_close(Mp3Decoder* dec); + +#endif // !STRIP_ALL -- cgit v1.2.3