#include #include #include #include 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); } g_downMixToStereo = (obtained.channels == 2); } void XAudioInitializeSystem() { SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, "playback"); SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Unleashed Recompiled"); SDL_InitSubSystem(SDL_INIT_AUDIO); CreateAudioDevice(); } static std::unique_ptr 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(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(AudioThread); } void XAudioRegisterClient(PPCFunc* callback, uint32_t param) { auto* pClientParam = static_cast(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*>(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 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 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(); } }