diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index c41762e146..1b31db6e7c 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -1,8 +1,16 @@ #pragma once +#include + namespace dusk::audio { /** * Initialize the audio system and start playing audio. */ void Initialize(); + + void SetMasterVolume(f32 value); + + u32 GetResetCount(int channelIdx); + + f32 VolumeFromU16(u16 value); } diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 7c2572dd53..0ed2101dcf 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -15,6 +15,7 @@ #include "DuskDsp.hpp" #include "JSystem/JAudio2/JASAudioThread.h" +#include "JSystem/JAudio2/JASDriverIF.h" // #define DUSK_DUMP_AUDIO @@ -73,6 +74,12 @@ void dusk::audio::Initialize() { SDL_ResumeAudioStreamDevice(PlaybackStream); } +void dusk::audio::SetMasterVolume(const f32 value) { + JASCriticalSection section; + + MasterVolume = value; +} + void SDLCALL GetNewAudio( void*, SDL_AudioStream*, @@ -132,3 +139,11 @@ void RenderAudioSubframe() { 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()); +} diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index 76e6154f80..2b26ea478c 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -9,6 +9,7 @@ #include "Adpcm.hpp" #include "JSystem/JAudio2/JASDriverIF.h" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/endian.h" #include "global.h" @@ -16,6 +17,9 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +f32 dusk::audio::MasterVolume = 1.0f; +f32 dusk::audio::PrevMasterVolume = 1.0f; + /** * Validate that a DSP channel's format is actually something we know how to play. */ @@ -87,6 +91,8 @@ static void UpdateSampleRate(const JASDsp::TChannel& channel, ChannelAuxData& au * Reset state for a DSP channel between independent playbacks. */ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { + aux.resetCount += 1; + channel.mSamplesLeft = channel.mEndSample - channel.mSamplePosition; aux.hist0 = 0; @@ -95,6 +101,10 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { SDL_ClearAudioStream(aux.resampleStream); UpdateSampleRate(channel, aux); + for (auto& volume : aux.prevVolume) { + volume = NAN; + } + channel.mResetFlag = false; } @@ -145,6 +155,11 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { MixSubframe(subframe.channels[o], channelSubframe.channels[o]); } } + + for (auto& channel : subframe.channels) { + ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); + } + PrevMasterVolume = MasterVolume; } /** @@ -325,17 +340,24 @@ static const JASDsp::OutputChannelConfig* GetOutputConfig( return nullptr; } +struct VolumeValue { + f32 Target; + f32 Init; +}; + /** * Get the volume that the given DSP channel should render to the given output channel at. */ -static f32 GetVolumeForOutputChannel( +static VolumeValue GetVolumeForOutputChannel( const JASDsp::TChannel& sourceChannel, OutputChannel outputChannel) { u16 volume; + u16 initVolume; f32 panValue = 1; if (sourceChannel.mAutoMixerBeenSet) { volume = sourceChannel.mAutoMixerVolume; + initVolume = sourceChannel.mAutoMixerInitVolume; auto autoMixerPan = static_cast(sourceChannel.mAutoMixerPanDolby >> 8) / 127; @@ -353,17 +375,21 @@ static f32 GetVolumeForOutputChannel( } else { auto config = GetOutputConfig(sourceChannel, outputChannel); if (config == nullptr) { - return 0; + return {0, 0}; } volume = config->mTargetVolume; + initVolume = config->mCurrentVolume; } // TODO: interpolate to avoid popping. - f32 ratio = static_cast(volume) / static_cast(JASDriver::getChannelLevel_dsp()); - ratio *= panValue; + f32 targetRatio = VolumeFromU16(volume); + targetRatio *= panValue; - return ratio; + f32 initRatio = VolumeFromU16(initVolume); + initRatio *= panValue; + + return {targetRatio, initRatio}; } /** @@ -371,6 +397,7 @@ static f32 GetVolumeForOutputChannel( */ static void RenderOutputChannel( const JASDsp::TChannel& sourceChannel, + ChannelAuxData& aux, OutputChannel outputChannel, const std::span inputSamples, OutputSubframe& fullOutputSubframe) { @@ -379,13 +406,20 @@ static void RenderOutputChannel( assert(inputSamples.size() <= outputSubframe.size()); auto volume = GetVolumeForOutputChannel(sourceChannel, outputChannel); - if (volume == 0) { + + f32 targetVolume = volume.Target; + auto& prevVolume = aux.PrevVolume(outputChannel); + if (std::isnan(prevVolume)) { + // Initialize previous volume to new volume on first render. + prevVolume = volume.Init; + } + + if (prevVolume == 0 && targetVolume == 0) { return; } - for (int i = 0; i < inputSamples.size(); i++) { - outputSubframe[i] = inputSamples[i] * volume; - } + ApplyVolume(outputSubframe, inputSamples, prevVolume, targetVolume); + prevVolume = targetVolume; } static void RenderChannel( @@ -414,8 +448,8 @@ static void RenderChannel( static_assert(OutputSubframe::NUM_CHANNELS == 2, "Keep RenderChannel in sync!"); - RenderOutputChannel(channel, OutputChannel::LEFT, hasReadSamples, subframe); - RenderOutputChannel(channel, OutputChannel::RIGHT, hasReadSamples, subframe); + RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, hasReadSamples, subframe); + RenderOutputChannel(channel, channelAux,OutputChannel::RIGHT, hasReadSamples, subframe); } void dusk::audio::DspInit() { @@ -440,3 +474,24 @@ void dusk::audio::DspInit() { reinterpret_cast(static_cast(i))); } } + +void dusk::audio::ApplyVolume( + std::span dst, + const std::span src, + const f32 startVolume, + const f32 endVolume) { + assert(dst.size() >= src.size()); + + if (startVolume == endVolume) { + for (int i = 0; i < src.size(); i++) { + dst[i] = src[i] * startVolume; + } + } else { + const f32 step = (endVolume - startVolume) / static_cast(src.size()); + auto curVolume = startVolume; + for (int i = 0; i < src.size(); i++) { + dst[i] = src[i] * curVolume; + curVolume += step; + } + } +} diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 9168320e0f..c84bb338db 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -6,6 +6,8 @@ #include #include "SDL3/SDL_audio.h" +#include + // ReSharper disable once CppUnusedIncludeDirective #include "global.h" @@ -26,6 +28,21 @@ namespace dusk::audio { s16 hist0; SDL_AudioStream* resampleStream; u16 prevPitch; + + // Used for debugging tools. + u32 resetCount; + + /** + * Previous volume values, per output channel. + * Used to avoid clicking when volumes change. + * Set to NaN after channel reset, indicating that initial volume value is previous. + */ + f32 prevVolume[static_cast(OutputChannel::OutputChannel_MAX)]; + + f32& PrevVolume(OutputChannel channel) { + assert(channel < OutputChannel::OutputChannel_MAX); + return prevVolume[static_cast(channel)]; + } }; extern ChannelAuxData ChannelAux[DSP_CHANNELS]; @@ -83,4 +100,13 @@ namespace dusk::audio { return channel.mBytesPerBlock; } + + /** + * Apply a volume level to audio data. + * Interpolates across the two provided volume levels to avoid clicking. + */ + void ApplyVolume(std::span dst, std::span src, f32 startVolume, f32 endVolume); + + extern f32 MasterVolume; + extern f32 PrevMasterVolume; } diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 07caf9f0c0..b348a5c3d9 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -6,14 +6,16 @@ #include "JSystem/JAudio2/JASCriticalSection.h" #include "JSystem/JAudio2/JASDSPChannel.h" #include "JSystem/JAudio2/JASDSPInterface.h" -#include "JSystem/JAudio2/JASDriverIF.h" #include "JSystem/JAudio2/JASTrack.h" +#include "dusk/audio/DuskAudioSystem.h" static std::array channelSortIndices = {}; static bool sortUpdateCount = true; static void DisplayDspChannel(int i) { + using namespace dusk::audio; + auto& channel = JASDsp::CH_BUF[i]; auto& jasChannel = JASDSPChannel::sDspChannels[i]; if (!channel.mIsActive) { @@ -39,19 +41,25 @@ static void DisplayDspChannel(int i) { auto pan = (channel.mAutoMixerPanDolby >> 8) / 127.5f; auto dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.5f; auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f; - auto volume = (channel.mAutoMixerVolume) / (f32) JASDriver::getChannelLevel_dsp(); + auto volume = VolumeFromU16(channel.mAutoMixerVolume); ImGui::Text( "Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f)", pan, dolby, fxMix, volume); } else { ImGui::Text( - "Bus connect: %04X,%04X,%04X,%04X,%04X,%04X", + "Bus connect: %04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f),%04X(%.2f)", channel.mOutputChannels[0].mBusConnect, + VolumeFromU16(channel.mOutputChannels[0].mTargetVolume), channel.mOutputChannels[1].mBusConnect, + VolumeFromU16(channel.mOutputChannels[1].mTargetVolume), channel.mOutputChannels[2].mBusConnect, + VolumeFromU16(channel.mOutputChannels[2].mTargetVolume), channel.mOutputChannels[3].mBusConnect, + VolumeFromU16(channel.mOutputChannels[3].mTargetVolume), channel.mOutputChannels[4].mBusConnect, - channel.mOutputChannels[5].mBusConnect); + VolumeFromU16(channel.mOutputChannels[4].mTargetVolume), + channel.mOutputChannels[5].mBusConnect, + VolumeFromU16(channel.mOutputChannels[5].mTargetVolume)); } } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index a0b79579eb..416b7d062c 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -7,8 +7,9 @@ #include #include "JSystem/JUtility/JUTGamePad.h" -#include "m_Do/m_Do_controller_pad.h" +#include "dusk/audio/DuskAudioSystem.h" #include "m_Do/m_Do_audio.h" +#include "m_Do/m_Do_controller_pad.h" namespace dusk { ImGuiMenuGame::ImGuiMenuGame() {} @@ -30,6 +31,8 @@ namespace dusk { ImGui::Text("Master Volume"); ImGui::SliderFloat("##m_masterVolume", &m_audioSettings.m_masterVolume, 0.0f, 1.0f, ""); + /* + // TODO: implement additional settings ImGui::Text("Main Music Volume"); ImGui::SliderFloat("##m_mainMusicVolume", &m_audioSettings.m_mainMusicVolume, 0.0f, 1.0f, ""); @@ -44,8 +47,10 @@ namespace dusk { Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); if (audioMgr != nullptr) { - // TODO: actually apply volume settings } + */ + + audio::SetMasterVolume(m_audioSettings.m_masterVolume); ImGui::EndMenu(); }