// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). // // XAudio render driver. The game registers one client callback; the audio // system calls it once per 256-sample frame (48 kHz, ~5.33 ms) and the // callback submits the mixed frame with XAudioSubmitRenderDriverFrame // (256 samples x 6 channels, planar big-endian float). // // Frames go to an SDL3 audio stream (5.1 float; SDL downmixes to whatever // the device has). The callback runs like the hardware's: one frame every // 5.33 ms, evenly (the game's voices hold only a couple of frames of // decoded audio). Calling it in // bursts of whatever the device pulls at once (1024+ samples on PipeWire) // left every voice empty for half of each burst: gameplay audio came out // chopped 50% (two frames on, two off), thin and "tinny". SDL's queue is // kept near two device buffers to absorb the device's pulls, and the // cadence gains or skips a frame to follow the device clock, but never stops // (apu/audio_cadence.h): all of the game's sound, its sound commands // included, runs on this callback, so a device that stops taking audio must // not stop it (GitHub issue #20: the sound stopped for good, then the game // hung after the race). What such a device can't take is dropped, and the // log says when it stopped and started taking audio again. NFSMW_MUTE=1 // (or no audio device) keeps the same cadence with no output. The driver // handle and wrapped argument follow Xenia. // // While the game is suspended (iOS: the app isn't the active one) the // cadence stands still with guest time and the main thread pauses the // device (apu::SuspendOutput), which keeps what is queued: on the resume // that plays first and the cadence carries on at its old phase, with no // frames owed. #include #include "audio.h" #include "audio_cadence.h" #include #include #include #include #ifdef __APPLE__ #include #endif #include #include