Implement master volume & volume interpolation

Does not fix clicking :(

Fixes https://github.com/TakaRikka/dusk/issues/132
Fixes https://github.com/TakaRikka/dusk/issues/128
This commit is contained in:
PJB3005
2026-03-27 14:54:03 +01:00
parent 21ce0d35b0
commit 50303bba1b
6 changed files with 134 additions and 17 deletions
+8
View File
@@ -1,8 +1,16 @@
#pragma once
#include <dolphin/types.h>
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);
}
+15
View File
@@ -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<f32>(value) / static_cast<f32>(JASDriver::getChannelLevel_dsp());
}
+66 -11
View File
@@ -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<f32>(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<f32>(volume) / static_cast<f32>(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<f32> 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<void*>(static_cast<uintptr_t>(i)));
}
}
void dusk::audio::ApplyVolume(
std::span<f32> dst,
const std::span<f32> 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<f32>(src.size());
auto curVolume = startVolume;
for (int i = 0; i < src.size(); i++) {
dst[i] = src[i] * curVolume;
curVolume += step;
}
}
}
+26
View File
@@ -6,6 +6,8 @@
#include <cassert>
#include "SDL3/SDL_audio.h"
#include <span>
// 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<int>(OutputChannel::OutputChannel_MAX)];
f32& PrevVolume(OutputChannel channel) {
assert(channel < OutputChannel::OutputChannel_MAX);
return prevVolume[static_cast<int>(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<f32> dst, std::span<f32> src, f32 startVolume, f32 endVolume);
extern f32 MasterVolume;
extern f32 PrevMasterVolume;
}
+12 -4
View File
@@ -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<u8, DSP_CHANNELS> 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));
}
}
+7 -2
View File
@@ -7,8 +7,9 @@
#include <imgui_internal.h>
#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();
}