Merge pull request #642 from wheremyfoodat/more-dsp

Better audio playback code
This commit is contained in:
wheremyfoodat 2024-11-22 02:52:43 +02:00 committed by GitHub
commit 842634e64e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 175 additions and 157 deletions

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "PICA/float_types.hpp"
#include <array> #include <array>
#include "PICA/float_types.hpp"
namespace PICA { namespace PICA {
// A representation of the output vertex as it comes out of the vertex shader, with padding and all // A representation of the output vertex as it comes out of the vertex shader, with padding and all
struct Vertex { struct Vertex {

View file

@ -8,8 +8,8 @@
#include "helpers.hpp" #include "helpers.hpp"
#include "logger.hpp" #include "logger.hpp"
#include "scheduler.hpp"
#include "ring_buffer.hpp" #include "ring_buffer.hpp"
#include "scheduler.hpp"
// The DSP core must have access to the DSP service to be able to trigger interrupts properly // The DSP core must have access to the DSP service to be able to trigger interrupts properly
class DSPService; class DSPService;
@ -24,7 +24,8 @@ namespace Audio {
static constexpr u64 lleSlice = 16384; static constexpr u64 lleSlice = 16384;
class DSPCore { class DSPCore {
using Samples = Common::RingBuffer<s16, 1024>; // 0x2000 stereo (= 2 channel) samples
using Samples = Common::RingBuffer<s16, 0x2000 * 2>;
protected: protected:
Memory& mem; Memory& mem;
@ -38,8 +39,7 @@ namespace Audio {
public: public:
enum class Type { Null, Teakra, HLE }; enum class Type { Null, Teakra, HLE };
DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {}
: mem(mem), scheduler(scheduler), dspService(dspService) {}
virtual ~DSPCore() {} virtual ~DSPCore() {}
virtual void reset() = 0; virtual void reset() = 0;

View file

@ -3,11 +3,12 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "helpers.hpp"
#include "miniaudio.h" #include "miniaudio.h"
#include "ring_buffer.hpp" #include "ring_buffer.hpp"
class MiniAudioDevice { class MiniAudioDevice {
using Samples = Common::RingBuffer<ma_int16, 1024>; using Samples = Common::RingBuffer<ma_int16, 0x2000 * 2>;
static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate
static constexpr ma_uint32 channelCount = 2; // Audio output is stereo static constexpr ma_uint32 channelCount = 2; // Audio output is stereo
@ -20,6 +21,8 @@ class MiniAudioDevice {
bool initialized = false; bool initialized = false;
bool running = false; bool running = false;
// Store the last stereo sample we output. We play this when underruning to avoid pops.
std::array<s16, 2> lastStereoSample;
std::vector<std::string> audioDevices; std::vector<std::string> audioDevices;
public: public:

View file

@ -9,7 +9,7 @@ enum class Regions : u32 {
Australia = 3, Australia = 3,
China = 4, China = 4,
Korea = 5, Korea = 5,
Taiwan = 6 Taiwan = 6,
}; };
// Used for the language field in the NAND user data // Used for the language field in the NAND user data

View file

@ -1,5 +1,7 @@
#include "audio/miniaudio_device.hpp" #include "audio/miniaudio_device.hpp"
#include <cstring>
#include "helpers.hpp" #include "helpers.hpp"
MiniAudioDevice::MiniAudioDevice() : initialized(false), running(false), samples(nullptr) {} MiniAudioDevice::MiniAudioDevice() : initialized(false), running(false), samples(nullptr) {}
@ -87,20 +89,34 @@ void MiniAudioDevice::init(Samples& samples, bool safe) {
deviceConfig.aaudio.usage = ma_aaudio_usage_game; deviceConfig.aaudio.usage = ma_aaudio_usage_game;
deviceConfig.wasapi.noAutoConvertSRC = true; deviceConfig.wasapi.noAutoConvertSRC = true;
lastStereoSample = {0, 0};
deviceConfig.dataCallback = [](ma_device* device, void* out, const void* input, ma_uint32 frameCount) { deviceConfig.dataCallback = [](ma_device* device, void* out, const void* input, ma_uint32 frameCount) {
auto self = reinterpret_cast<MiniAudioDevice*>(device->pUserData); auto self = reinterpret_cast<MiniAudioDevice*>(device->pUserData);
s16* output = reinterpret_cast<ma_int16*>(out);
const usize maxSamples = std::min(self->samples->Capacity(), usize(frameCount * channelCount));
// Wait until there's enough samples to pop
while (self->samples->size() < maxSamples) {
// If audio output is disabled from the emulator thread, make sure that this callback will return and not hang
if (!self->running) { if (!self->running) {
return; return;
} }
s16* output = reinterpret_cast<ma_int16*>(out);
usize samplesWritten = 0;
samplesWritten += self->samples->pop(output, frameCount * channelCount);
// Get the last sample for underrun handling
if (samplesWritten != 0) {
std::memcpy(&self->lastStereoSample[0], &output[(samplesWritten - 1) * 2], sizeof(lastStereoSample));
} }
self->samples->pop(output, maxSamples); // If underruning, copy the last output sample
{
s16* pointer = &output[samplesWritten * 2];
s16 l = self->lastStereoSample[0];
s16 r = self->lastStereoSample[1];
for (usize i = samplesWritten; i < frameCount; i++) {
*pointer++ = l;
*pointer++ = r;
}
}
}; };
if (ma_device_init(&context, &deviceConfig, &device) != MA_SUCCESS) { if (ma_device_init(&context, &deviceConfig, &device) != MA_SUCCESS) {

View file

@ -120,9 +120,7 @@ namespace Audio {
dspService.triggerPipeEvent(DSPPipeType::Audio); dspService.triggerPipeEvent(DSPPipeType::Audio);
break; break;
case StateChange::Shutdown: case StateChange::Shutdown: dspState = DSPState::Off; break;
dspState = DSPState::Off;
break;
default: Helpers::panic("Unimplemented DSP audio pipe state change %d", state); default: Helpers::panic("Unimplemented DSP audio pipe state change %d", state);
} }