mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-02 10:36:03 -04:00
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:
@@ -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,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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user