#include "dusk/audio/DuskAudioSystem.h" #include #include #include #include #include #include #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASChannel.h" #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASHeapCtrl.h" #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" #include "JSystem/JAudio2/JASDriverIF.h" // #define DUSK_DUMP_AUDIO using namespace dusk::audio; static OutputSubframe OutBuffer; static std::array OutInterleaveBuffer; static SDL_AudioStream* PlaybackStream; /** * SDL audiostream callback to trigger rendering of new audio data. */ static void SDLCALL GetNewAudio( void*, SDL_AudioStream*, int needed, int); /** * Render an entire new frame of audio and output it to SDL3. * Note: "audio frames" are unrelated to video frames. * @return Amount of audio samples rendered. */ static int RenderNewAudioFrame(); /** * Render an audio subframe and output it to SDL3. */ static void RenderAudioSubframe(); static void InitSDL3Output() { SDL_Init(SDL_INIT_AUDIO); constexpr SDL_AudioSpec spec = { SDL_AUDIO_F32, 2, SampleRate, }; PlaybackStream = SDL_OpenAudioDeviceStream( SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, &GetNewAudio, nullptr); } void dusk::audio::Initialize() { InitSDL3Output(); DspInit(); JASDsp::initBuffer(); JASDSPChannel::initAll(); JASPoolAllocObject_MultiThreaded::newMemPool(0x48); SDL_ResumeAudioStreamDevice(PlaybackStream); } void dusk::audio::SetMasterVolume(const f32 value) { JASCriticalSection section; MasterVolume = value; } void SDLCALL GetNewAudio( void*, SDL_AudioStream*, int needed, int) { while (needed > 0) { const int rendered = RenderNewAudioFrame(); needed -= rendered; } } #if defined(DUSK_DUMP_AUDIO) static std::ofstream outRaw("guh.raw", std::ios_base::out | std::ios_base::binary); #endif int RenderNewAudioFrame() { JASCriticalSection section; const u32 countSubframes = JASDriver::getSubFrames(); JASAudioThread::setDSPSyncCount(countSubframes); for (u32 i = 0; i < countSubframes; i++) { RenderAudioSubframe(); JASAudioThread::snIntCount -= 1; } #if defined(DUSK_DUMP_AUDIO) outRaw.flush(); #endif return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } static void InterleaveOutputData(const OutputSubframe& data, std::span target) { assert(target.size() >= data.channels[0].size() * OutputSubframe::NUM_CHANNELS); size_t outPos = 0; for (size_t inPos = 0; inPos < data.channels[0].size(); inPos++) { for (size_t channelIdx = 0; channelIdx < OutputSubframe::NUM_CHANNELS; channelIdx++) { target[outPos++] = data.channels[channelIdx][inPos]; } } } void RenderAudioSubframe() { OutBuffer = {}; JASDriver::updateDSP(); DspRender(OutBuffer); InterleaveOutputData(OutBuffer, OutInterleaveBuffer); if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) { static_assert(OutputSubframe::NUM_CHANNELS == 2); // This code only works with Stereo so far. // NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe. // That's probably more efficient, but I didn't wanna change the code to calculate the // entire audio buffers at once. // This is only used for the movie player, and it seems to work fine with the smaller calls. const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE); if (mixData) { for (int i = 0; i < OutInterleaveBuffer.size(); i++) { OutInterleaveBuffer[i] += static_cast(mixData[i]) / static_cast(0x7FFF); } } } #if defined(DUSK_DUMP_AUDIO) outRaw.write((const char*)OutInterleaveBuffer.data(), sizeof(OutInterleaveBuffer)); #endif SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); } u32 dusk::audio::GetResetCount(int channelIdx) { return ChannelAux[channelIdx].resetCount; } f32 dusk::audio::VolumeFromU16(u16 value) { return static_cast(value) / static_cast(JASDriver::getChannelLevel_dsp()); }