From c8866854c3d5ce28bdd116588e6fc2c7a206a978 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Thu, 19 Mar 2026 17:47:11 +0100 Subject: [PATCH] Stereo audio! --- src/dusk/audio/DuskAudioSystem.cpp | 26 ++++-- src/dusk/audio/DuskDsp.cpp | 126 ++++++++++++++++++++++++----- src/dusk/audio/DuskDsp.hpp | 22 ++++- 3 files changed, 147 insertions(+), 27 deletions(-) diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 1d625a8101..3becb76cfa 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASChannel.h" @@ -16,7 +18,8 @@ using namespace dusk::audio; -static DspSubframe AllSubframeBuffers[DSP_OUTPUT_CHANNELS]; +static OutputSubframe OutBuffer; +static std::array OutInterleaveBuffer; static SDL_AudioStream* PlaybackStream; @@ -34,7 +37,7 @@ static void InitSDL3Output() { constexpr SDL_AudioSpec spec = { SDL_AUDIO_F32, - 1, + 2, SampleRate, }; PlaybackStream = SDL_OpenAudioDeviceStream( @@ -90,15 +93,28 @@ int RenderNewAudioFrame() { 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() { - DspSubframe& subFrame = AllSubframeBuffers[0]; + OutBuffer = {}; JASDriver::updateDSP(); - DspRender(subFrame); + DspRender(OutBuffer); #if 0 outRaw.write((const char*)subFrame.data(), sizeof(subFrame)); #endif - SDL_PutAudioStreamData(PlaybackStream, subFrame.data(), sizeof(subFrame)); + InterleaveOutputData(OutBuffer, OutInterleaveBuffer); + + SDL_PutAudioStreamData(PlaybackStream, &OutInterleaveBuffer, sizeof(OutInterleaveBuffer)); } diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index a901cc0cdc..a042b0aa88 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "Adpcm.hpp" #include "JSystem/JAudio2/JASDriverIF.h" @@ -58,7 +59,7 @@ static u32 ConvertSamplesToDataLength(const JASDsp::TChannel& channel, u32 sampl static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - DspSubframe& subframe); + OutputSubframe& subframe); static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; @@ -84,9 +85,7 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } } -void dusk::audio::DspRender(DspSubframe& subframe) { - subframe.fill(0); - +void dusk::audio::DspRender(OutputSubframe& subframe) { // This cast half exists because my debugger sucks and this is an easy way to look at the data. auto& channels = *reinterpret_cast*>(JASDsp::CH_BUF); @@ -112,9 +111,12 @@ void dusk::audio::DspRender(DspSubframe& subframe) { ValidateChannel(channel); - DspSubframe channelSubframe = {}; + OutputSubframe channelSubframe = {}; RenderChannel(channel, channelAux, channelSubframe); - MixSubframe(subframe, channelSubframe); + + for (int o = 0; o < subframe.channels.size(); o++) { + MixSubframe(subframe.channels[o], channelSubframe.channels[o]); + } } } @@ -189,34 +191,116 @@ static void SDLCALL ReadChannelSamples( SDL_PutAudioStreamData(stream, requested, requestedSize); } +constexpr u16 GetBusConnect(const OutputChannel channel) { + switch (channel) { + // TODO: This is a guess for now. + case OutputChannel::LEFT: + return 0x0D00; + case OutputChannel::RIGHT: + return 0x0D60; + default: + CRASH("Invalid output channel!"); + } +} + +static const JASDsp::OutputChannelConfig* GetOutputConfig( + const JASDsp::TChannel& sourceChannel, + OutputChannel channel) { + + auto busConnect = GetBusConnect(channel); + for (const auto& mOutputChannel : sourceChannel.mOutputChannels) { + auto config = &mOutputChannel; + if (config->mBusConnect == busConnect) { + return config; + } + } + + return nullptr; +} + +static f32 GetVolumeForOutputChannel( + const JASDsp::TChannel& sourceChannel, + OutputChannel outputChannel) { + + u16 volume; + f32 panValue = 1; + if (sourceChannel.mAutoMixerBeenSet) { + volume = sourceChannel.mAutoMixerVolume; + + auto autoMixerPan = static_cast(sourceChannel.mAutoMixerPanDolby >> 8) / 127; + + switch (outputChannel) { + case OutputChannel::LEFT: + panValue = 1 - autoMixerPan; + break; + case OutputChannel::RIGHT: + panValue = autoMixerPan; + break; + default: + CRASH("Unhandled output channel: OutputChannel"); + } + + } else { + auto config = GetOutputConfig(sourceChannel, outputChannel); + if (config == nullptr) { + return 0; + } + + volume = config->mTargetVolume; + } + + // TODO: interpolate to avoid popping. + f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); + ratio *= panValue; + + return ratio; +} + +static void RenderOutputChannel( + const JASDsp::TChannel& sourceChannel, + OutputChannel outputChannel, + const std::span inputSamples, + OutputSubframe& fullOutputSubframe) { + + auto& outputSubframe = fullOutputSubframe[outputChannel]; + assert(inputSamples.size() <= outputSubframe.size()); + + auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel); + if (volume == 0) { + return; + } + + for (int i = 0; i < inputSamples.size(); i++) { + outputSubframe[i] = inputSamples[i] * volume; + } +} + static void RenderChannel( JASDsp::TChannel& channel, ChannelAuxData& channelAux, - DspSubframe& subframe) { + OutputSubframe& subframe) { if (channel.mResetFlag) { ResetChannel(channel, channelAux); } - int wantRead = static_cast(subframe.size() * sizeof(subframe[0])); + DspSubframe audioLoadBuffer = {}; + + int wantRead = sizeof(audioLoadBuffer); auto read = SDL_GetAudioStreamData( channelAux.resampleStream, - subframe.data(), + &audioLoadBuffer, wantRead); if (read < wantRead) { channel.mIsFinished = true; } - u16 volume; - if (channel.mAutoMixerBeenSet) { - volume = channel.mAutoMixerVolume; - } else { - volume = channel.mOutputChannels[0].mTargetVolume; - } - f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); - for (auto& sample : subframe) { - sample *= ratio; - } + auto hasReadSamples = std::span(audioLoadBuffer).subspan(0, wantRead / sizeof(f32)); + + static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); + + RenderOutputChannel(channel, OutputChannel::LEFT, hasReadSamples, subframe); + RenderOutputChannel(channel, OutputChannel::RIGHT, hasReadSamples, subframe); } void dusk::audio::DspInit() { @@ -231,13 +315,13 @@ void dusk::audio::DspInit() { SampleRate }; - for (int i = 0; i < DSP_CHANNELS; i++) { + for (u32 i = 0; i < DSP_CHANNELS; i++) { auto& aux = ChannelAux[i]; aux.resampleStream = SDL_CreateAudioStream(&srcSpec, &dstSpec); SDL_SetAudioStreamGetCallback( aux.resampleStream, ReadChannelSamples, - reinterpret_cast(i)); + reinterpret_cast(static_cast(i))); } } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 76a4ef2e14..0b47cf2570 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -3,12 +3,21 @@ #include "JSystem/JAudio2/JASDSPInterface.h" #include +#include #include "SDL3/SDL_audio.h" namespace dusk::audio { constexpr int SampleRate = 32000; + enum class OutputChannel : u8 { + LEFT, + RIGHT, + OutputChannel_MAX + }; + + constexpr + struct ChannelAuxData { s16 hist1; s16 hist0; @@ -19,6 +28,17 @@ namespace dusk::audio { using DspSubframe = std::array; + struct OutputSubframe { + static constexpr int NUM_CHANNELS = static_cast(OutputChannel::OutputChannel_MAX); + + std::array channels; + + DspSubframe& operator[](OutputChannel channel) { + assert(channel < OutputChannel::OutputChannel_MAX); + return channels[static_cast(channel)]; + } + }; + void DspInit(); - void DspRender(DspSubframe& subframe); + void DspRender(OutputSubframe& subframe); }