From c4888bea71326f7a69e8214af0d9c2a62a60b887 Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 4 Feb 2026 13:08:13 +0100 Subject: feat(audio): Implement audio backend abstraction (Task #51.1) Created interface-based audio backend system to enable testing without hardware. This is the foundation for robust tracker timing verification. Changes: - Created AudioBackend interface with init/start/shutdown methods - Added test-only hooks: on_voice_triggered() and on_frames_rendered() - Moved miniaudio implementation to MiniaudioBackend class - Refactored audio.cc to use backend abstraction with auto-fallback - Added time tracking to synth.cc (elapsed time from rendered frames) - Created test_audio_backend.cc to verify backend injection works - Fixed audio test linking to include util/procedural dependencies All test infrastructure guarded by #if !defined(STRIP_ALL) for zero size impact on final build. Production path unchanged, 100% backward compatible. All 13 tests pass. handoff(Claude): Task #51.1 complete, audio backend abstraction ready Co-Authored-By: Claude Sonnet 4.5 --- src/audio/miniaudio_backend.cc | 70 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/audio/miniaudio_backend.cc (limited to 'src/audio/miniaudio_backend.cc') diff --git a/src/audio/miniaudio_backend.cc b/src/audio/miniaudio_backend.cc new file mode 100644 index 0000000..d2563c5 --- /dev/null +++ b/src/audio/miniaudio_backend.cc @@ -0,0 +1,70 @@ +// This file is part of the 64k demo project. +// It implements the production audio backend using miniaudio. +// Moved from audio.cc to enable backend abstraction for testing. + +#include "miniaudio_backend.h" +#include "synth.h" +#include + +// Static callback for miniaudio (C API requirement) +void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput, + const void* pInput, + ma_uint32 frameCount) { + (void)pDevice; + (void)pInput; + float* fOutput = (float*)pOutput; + synth_render(fOutput, (int)frameCount); +} + +MiniaudioBackend::MiniaudioBackend() : initialized_(false) { +} + +MiniaudioBackend::~MiniaudioBackend() { + if (initialized_) { + shutdown(); + } +} + +void MiniaudioBackend::init() { + if (initialized_) { + return; + } + + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.format = ma_format_f32; + config.playback.channels = 2; + config.sampleRate = 32000; + config.dataCallback = MiniaudioBackend::audio_callback; + config.pUserData = this; + + if (ma_device_init(NULL, &config, &device_) != MA_SUCCESS) { + printf("Failed to open playback device.\n"); + return; + } + + initialized_ = true; +} + +void MiniaudioBackend::start() { + if (!initialized_) { + printf("Cannot start: backend not initialized.\n"); + return; + } + + if (ma_device_start(&device_) != MA_SUCCESS) { + printf("Failed to start playback device.\n"); + ma_device_uninit(&device_); + initialized_ = false; + return; + } +} + +void MiniaudioBackend::shutdown() { + if (!initialized_) { + return; + } + + ma_device_stop(&device_); + ma_device_uninit(&device_); + initialized_ = false; +} -- cgit v1.2.3