diff --git a/CMakeLists.txt b/CMakeLists.txt index be70036c..f37ce28a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -402,7 +402,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp include/audio/dsp_simd.hpp include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp - include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp + include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp + include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp ) cmrc_add_resource_library( diff --git a/include/audio/audio_device.hpp b/include/audio/audio_device.hpp new file mode 100644 index 00000000..5c1f5fc1 --- /dev/null +++ b/include/audio/audio_device.hpp @@ -0,0 +1,9 @@ +#pragma once + +#ifdef __LIBRETRO__ +#include "audio/libretro_audio_device.hpp" +using AudioDevice = LibretroAudioDevice; +#else +#include "audio/miniaudio_device.hpp" +using AudioDevice = MiniAudioDevice; +#endif \ No newline at end of file diff --git a/include/audio/audio_device_interface.hpp b/include/audio/audio_device_interface.hpp new file mode 100644 index 00000000..de70c77a --- /dev/null +++ b/include/audio/audio_device_interface.hpp @@ -0,0 +1,36 @@ +#pragma once +#include + +#include "config.hpp" +#include "helpers.hpp" +#include "ring_buffer.hpp" + +class AudioDeviceInterface { + protected: + static constexpr usize maxFrameCount = 0x2000; + + using Samples = Common::RingBuffer; + using RenderBatchCallback = usize (*)(const s16*, usize); + + Samples* samples = nullptr; + + const AudioDeviceConfig& audioSettings; + // Store the last stereo sample we output. We play this when underruning to avoid pops. + std::array lastStereoSample{}; + + public: + AudioDeviceInterface(Samples* samples, const AudioDeviceConfig& audioSettings) : samples(samples), audioSettings(audioSettings) {} + + bool running = false; + Samples* getSamples() { return samples; } + + // If safe is on, we create a null audio device + virtual void init(Samples& samples, bool safe = false) = 0; + virtual void close() = 0; + + virtual void start() = 0; + virtual void stop() = 0; + + // Only used for audio devices that render multiple audio frames in one go, eg the libretro audio device. + virtual void renderBatch(RenderBatchCallback callback) {} +}; \ No newline at end of file diff --git a/include/audio/libretro_audio_device.hpp b/include/audio/libretro_audio_device.hpp new file mode 100644 index 00000000..047427ce --- /dev/null +++ b/include/audio/libretro_audio_device.hpp @@ -0,0 +1,60 @@ +#pragma once +#include + +#include "audio/audio_device_interface.hpp" + +class LibretroAudioDevice : public AudioDeviceInterface { + bool initialized = false; + + public: + LibretroAudioDevice(const AudioDeviceConfig& audioSettings) : AudioDeviceInterface(nullptr, audioSettings), initialized(false) { + running = false; + } + + void init(Samples& samples, bool safe = false) override { + this->samples = &samples; + + initialized = true; + running = false; + } + + void close() override { + initialized = false; + running = false; + }; + + void start() override { running = true; } + void stop() override { running = false; }; + + void renderBatch(RenderBatchCallback callback) override { + if (running) { + static constexpr usize frameCount = 774; + static constexpr usize channelCount = 2; + static s16 audioBuffer[frameCount * channelCount]; + + usize samplesWritten = 0; + samplesWritten += samples->pop(audioBuffer, frameCount * channelCount); + + // Get the last sample for underrun handling + if (samplesWritten != 0) { + std::memcpy(&lastStereoSample[0], &audioBuffer[(samplesWritten - 1) * 2], sizeof(lastStereoSample)); + } + + // If underruning, copy the last output sample + { + s16* pointer = &audioBuffer[samplesWritten * 2]; + s16 l = lastStereoSample[0]; + s16 r = lastStereoSample[1]; + + for (usize i = samplesWritten; i < frameCount; i++) { + *pointer++ = l; + *pointer++ = r; + } + } + + callback(audioBuffer, sizeof(audioBuffer) / (channelCount * sizeof(s16))); + } + } + + bool isInitialized() const { return initialized; } +}; \ No newline at end of file diff --git a/include/audio/miniaudio_device.hpp b/include/audio/miniaudio_device.hpp index 0363aa44..deb6e31a 100644 --- a/include/audio/miniaudio_device.hpp +++ b/include/audio/miniaudio_device.hpp @@ -3,39 +3,31 @@ #include #include -#include "config.hpp" -#include "helpers.hpp" +#include "audio/audio_device_interface.hpp" #include "miniaudio.h" -#include "ring_buffer.hpp" -class MiniAudioDevice { - using Samples = Common::RingBuffer; +class MiniAudioDevice : public AudioDeviceInterface { static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate static constexpr ma_uint32 channelCount = 2; // Audio output is stereo + bool initialized = false; + ma_device device; ma_context context; ma_device_config deviceConfig; - Samples* samples = nullptr; - - const AudioDeviceConfig& audioSettings; - - bool initialized = false; - bool running = false; // Store the last stereo sample we output. We play this when underruning to avoid pops. - std::array lastStereoSample; std::vector audioDevices; public: MiniAudioDevice(const AudioDeviceConfig& audioSettings); // If safe is on, we create a null audio device - void init(Samples& samples, bool safe = false); - void close(); + void init(Samples& samples, bool safe = false) override; + void close() override; - void start(); - void stop(); + void start() override; + void stop() override; bool isInitialized() const { return initialized; } }; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index a222a021..326eb232 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -7,8 +7,8 @@ #include #include "PICA/gpu.hpp" +#include "audio/audio_device.hpp" #include "audio/dsp_core.hpp" -#include "audio/miniaudio_device.hpp" #include "cheats.hpp" #include "config.hpp" #include "cpu.hpp" @@ -48,14 +48,14 @@ class Emulator { Scheduler scheduler; Crypto::AESEngine aesEngine; - MiniAudioDevice audioDevice; + AudioDevice audioDevice; Cheats cheats; public: static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens ROMType romType = ROMType::None; - bool running = false; // Is the emulator running a game? + bool running = false; // Is the emulator running a game? private: #ifdef PANDA3DS_ENABLE_HTTP_SERVER @@ -126,6 +126,7 @@ class Emulator { LuaManager& getLua() { return lua; } Scheduler& getScheduler() { return scheduler; } Memory& getMemory() { return memory; } + AudioDeviceInterface& getAudioDevice() { return audioDevice; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index 550fb039..8e2a1e71 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -7,8 +7,9 @@ #include "helpers.hpp" -MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings) - : initialized(false), running(false), samples(nullptr), audioSettings(audioSettings) {} +MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings) : AudioDeviceInterface(nullptr, audioSettings), initialized(false) { + running = false; +} void MiniAudioDevice::init(Samples& samples, bool safe) { this->samples = &samples; @@ -212,4 +213,4 @@ void MiniAudioDevice::close() { ma_device_uninit(&device); ma_context_uninit(&context); } -} +} \ No newline at end of file diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index 727da8d2..a9783320 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -389,6 +389,8 @@ void retro_run() { emulator->runFrame(); videoCallback(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0); + // Call audio batch callback + emulator->getAudioDevice().renderBatch(audioBatchCallback); } void retro_set_controller_port_device(uint port, uint device) {}