UnleashedRecomp/UnleashedRecomp/apu/driver/sdl2_driver.cpp

168 lines
5.2 KiB
C++

#include <apu/audio.h>
#include <cpu/guest_thread.h>
#include <kernel/heap.h>
#include <os/logger.h>
#include <user/config.h>
static PPCFunc* g_clientCallback{};
static uint32_t g_clientCallbackParam{}; // pointer in guest memory
static SDL_AudioDeviceID g_audioDevice{};
static bool g_downMixToStereo;
static void CreateAudioDevice()
{
if (g_audioDevice != NULL)
SDL_CloseAudioDevice(g_audioDevice);
bool surround = Config::ChannelConfiguration == EChannelConfiguration::Surround;
int allowedChanges = surround ? SDL_AUDIO_ALLOW_CHANNELS_CHANGE : 0;
SDL_AudioSpec desired{}, obtained{};
desired.freq = XAUDIO_SAMPLES_HZ;
desired.format = AUDIO_F32SYS;
desired.channels = surround ? XAUDIO_NUM_CHANNELS : 2;
desired.samples = XAUDIO_NUM_SAMPLES;
g_audioDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, allowedChanges);
if (obtained.channels != 2 && obtained.channels != XAUDIO_NUM_CHANNELS) // This check may fail only when surround sound is enabled.
{
SDL_CloseAudioDevice(g_audioDevice);
g_audioDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0);
}
if (!g_audioDevice)
LOGFN_ERROR("Failed to open audio device: {}", SDL_GetError());
g_downMixToStereo = (obtained.channels == 2);
}
void XAudioInitializeSystem()
{
#ifdef _WIN32
// Force wasapi on Windows.
SDL_setenv("SDL_AUDIODRIVER", "wasapi", true);
#endif
SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, "playback");
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Unleashed Recompiled");
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
LOGFN_ERROR("Failed to init audio subsystem: {}", SDL_GetError());
return;
}
CreateAudioDevice();
}
static std::unique_ptr<std::thread> g_audioThread;
static volatile bool g_audioThreadShouldExit;
static void AudioThread()
{
using namespace std::chrono_literals;
GuestThreadContext ctx(0);
size_t channels = g_downMixToStereo ? 2 : XAUDIO_NUM_CHANNELS;
while (!g_audioThreadShouldExit)
{
uint32_t queuedAudioSize = SDL_GetQueuedAudioSize(g_audioDevice);
constexpr size_t MAX_LATENCY = 10;
const size_t callbackAudioSize = channels * XAUDIO_NUM_SAMPLES * sizeof(float);
if ((queuedAudioSize / callbackAudioSize) <= MAX_LATENCY)
{
ctx.ppcContext.r3.u32 = g_clientCallbackParam;
g_clientCallback(ctx.ppcContext, g_memory.base);
}
auto now = std::chrono::steady_clock::now();
constexpr auto INTERVAL = 1000000000ns * XAUDIO_NUM_SAMPLES / XAUDIO_SAMPLES_HZ;
auto next = now + (INTERVAL - now.time_since_epoch() % INTERVAL);
std::this_thread::sleep_for(std::chrono::floor<std::chrono::milliseconds>(next - now));
while (std::chrono::steady_clock::now() < next)
std::this_thread::yield();
}
}
static void CreateAudioThread()
{
SDL_PauseAudioDevice(g_audioDevice, 0);
g_audioThreadShouldExit = false;
g_audioThread = std::make_unique<std::thread>(AudioThread);
}
void XAudioRegisterClient(PPCFunc* callback, uint32_t param)
{
auto* pClientParam = static_cast<uint32_t*>(g_userHeap.Alloc(sizeof(param)));
ByteSwapInplace(param);
*pClientParam = param;
g_clientCallbackParam = g_memory.MapVirtual(pClientParam);
g_clientCallback = callback;
CreateAudioThread();
}
void XAudioSubmitFrame(void* samples)
{
auto floatSamples = reinterpret_cast<be<float>*>(samples);
if (g_downMixToStereo)
{
// 0: left 1.0f, right 0.0f
// 1: left 0.0f, right 1.0f
// 2: left 0.75f, right 0.75f
// 3: left 0.0f, right 0.0f
// 4: left 1.0f, right 0.0f
// 5: left 0.0f, right 1.0f
std::array<float, 2 * XAUDIO_NUM_SAMPLES> audioFrames;
for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++)
{
float ch0 = floatSamples[0 * XAUDIO_NUM_SAMPLES + i];
float ch1 = floatSamples[1 * XAUDIO_NUM_SAMPLES + i];
float ch2 = floatSamples[2 * XAUDIO_NUM_SAMPLES + i];
float ch3 = floatSamples[3 * XAUDIO_NUM_SAMPLES + i];
float ch4 = floatSamples[4 * XAUDIO_NUM_SAMPLES + i];
float ch5 = floatSamples[5 * XAUDIO_NUM_SAMPLES + i];
audioFrames[i * 2 + 0] = (ch0 + ch2 * 0.75f + ch4) * Config::MasterVolume;
audioFrames[i * 2 + 1] = (ch1 + ch2 * 0.75f + ch5) * Config::MasterVolume;
}
SDL_QueueAudio(g_audioDevice, &audioFrames, sizeof(audioFrames));
}
else
{
std::array<float, XAUDIO_NUM_CHANNELS * XAUDIO_NUM_SAMPLES> audioFrames;
for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++)
{
for (size_t j = 0; j < XAUDIO_NUM_CHANNELS; j++)
audioFrames[i * XAUDIO_NUM_CHANNELS + j] = floatSamples[j * XAUDIO_NUM_SAMPLES + i] * Config::MasterVolume;
}
SDL_QueueAudio(g_audioDevice, &audioFrames, sizeof(audioFrames));
}
}
void XAudioConfigValueChangedCallback(IConfigDef* configDef)
{
if (configDef == &Config::ChannelConfiguration)
{
if (g_audioThread->joinable())
{
g_audioThreadShouldExit = true;
g_audioThread->join();
}
CreateAudioDevice();
CreateAudioThread();
}
}