Skip to content

Commit

Permalink
Merge pull request #19971 from hrydgard/volume-control-convert-settings
Browse files Browse the repository at this point in the history
Volume control UI changes, part 2
  • Loading branch information
hrydgard authored Feb 12, 2025
2 parents 1158ddd + fcaf3da commit 29a0835
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 44 deletions.
66 changes: 62 additions & 4 deletions Core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,16 +745,42 @@ static const ConfigSetting graphicsSettings[] = {
ConfigSetting("DisplayRefreshRate", &g_Config.iDisplayRefreshRate, g_Config.iDisplayRefreshRate, CfgFlag::PER_GAME),
};

static int LegacyVolumeToNewVolume(int legacy, int max) {
float multiplier = Volume10ToMultiplier(legacy);
return std::clamp(MultiplierToVolume100(multiplier), 0, max);
}

static int DefaultGameVolume() {
return LegacyVolumeToNewVolume(g_Config.iLegacyGameVolume, 100);
}

static int DefaultReverbVolume() {
return LegacyVolumeToNewVolume(g_Config.iLegacyReverbVolume, 200);
}

static int DefaultAchievementVolume() {
// NOTE: The old achievemnt volume was a straight percentage so it doesn't convert
// the same as the others.
return MultiplierToVolume100((float)g_Config.iLegacyAchievementVolume / 10.0f);
}

static const ConfigSetting soundSettings[] = {
ConfigSetting("Enable", &g_Config.bEnableSound, true, CfgFlag::PER_GAME),
ConfigSetting("AudioBackend", &g_Config.iAudioBackend, 0, CfgFlag::PER_GAME),
ConfigSetting("ExtraAudioBuffering", &g_Config.bExtraAudioBuffering, false, CfgFlag::DEFAULT),

ConfigSetting("GlobalVolume", &g_Config.iGameVolume, VOLUME_FULL, CfgFlag::PER_GAME),
ConfigSetting("ReverbVolume", &g_Config.iReverbVolume, VOLUME_FULL, CfgFlag::PER_GAME),
// Legacy volume settings, these get auto upgraded through default handlers on the new settings. NOTE: Must be before the new ones in the order here.
// The default settings here are still relevant, they will get propagated into the new ones.
ConfigSetting("GlobalVolume", &g_Config.iLegacyGameVolume, VOLUME_FULL, CfgFlag::PER_GAME | CfgFlag::DONT_SAVE),
ConfigSetting("ReverbVolume", &g_Config.iLegacyReverbVolume, VOLUME_FULL, CfgFlag::PER_GAME | CfgFlag::DONT_SAVE),
ConfigSetting("AchievementSoundVolume", &g_Config.iLegacyAchievementVolume, 6, CfgFlag::PER_GAME | CfgFlag::DONT_SAVE),

// Current volume settings.
ConfigSetting("GameVolume", &g_Config.iGameVolume, &DefaultGameVolume, CfgFlag::PER_GAME),
ConfigSetting("ReverbRelativeVolume", &g_Config.iReverbVolume, &DefaultReverbVolume, CfgFlag::PER_GAME),
ConfigSetting("AltSpeedRelativeVolume", &g_Config.iAltSpeedVolume, VOLUMEHI_FULL, CfgFlag::PER_GAME),
ConfigSetting("AchievementSoundVolume", &g_Config.iAchievementSoundVolume, 6, CfgFlag::PER_GAME),
ConfigSetting("UIVolume", &g_Config.iUIVolume, 70, CfgFlag::DEFAULT),
ConfigSetting("AchievementVolume", &g_Config.iAchievementVolume, &DefaultAchievementVolume, CfgFlag::PER_GAME),
ConfigSetting("UIVolume", &g_Config.iUIVolume, 75, CfgFlag::DEFAULT),

ConfigSetting("AudioDevice", &g_Config.sAudioDevice, "", CfgFlag::DEFAULT),
ConfigSetting("AutoAudioDevice", &g_Config.bAutoAudioDevice, true, CfgFlag::DEFAULT),
Expand Down Expand Up @@ -1468,6 +1494,7 @@ bool Config::Save(const char *saveReason) {
return true;
}

// A lot more cleanup tasks should be moved into here, and some of these are severely outdated.
void Config::PostLoadCleanup(bool gameSpecific) {
// Override ppsspp.ini JIT value to prevent crashing
jitForcedOff = DefaultCpuCore() != (int)CPUCore::JIT && (g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR);
Expand Down Expand Up @@ -1502,6 +1529,9 @@ void Config::PostLoadCleanup(bool gameSpecific) {
if (g_Config.sCustomDriver == "Default") {
g_Config.sCustomDriver = "";
}

// Convert old volume settings.

}

void Config::PreSaveCleanup(bool gameSpecific) {
Expand Down Expand Up @@ -2092,3 +2122,31 @@ bool PlayTimeTracker::GetPlayedTimeString(const std::string &gameId, std::string
*str = ApplySafeSubstitutions(ga->T("Time Played: %1h %2m %3s"), hours, minutes, seconds);
return true;
}

// This matches exactly the old shift-based curve.
float Volume10ToMultiplier(int volume) {
// Allow muting entirely.
if (volume <= 0) {
return 0.0f;
}
return powf(2.0f, (float)(volume - 10));
}

// NOTE: This is used for new volume parameters.
// It uses a more intuitive-feeling curve.
float Volume100ToMultiplier(int volume) {
// Switch to linear above the 1.0f point.
if (volume > 100) {
return volume / 100.0f;
}
return powf(volume * 0.01f, 1.75f);
}

// Used for migration from the old settings.
int MultiplierToVolume100(float multiplier) {
// Switch to linear above the 1.0f point.
if (multiplier > 1.0f) {
return multiplier * 100;
}
return (int)(powf(multiplier, 1.0f / 1.75f) * 100.f + 0.5f);
}
13 changes: 8 additions & 5 deletions Core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,14 +280,17 @@ struct Config {
bool bEnableSound;
int iAudioBackend;

// Volume settings, 0-10
int iGameVolume;
int iReverbVolume;
int iAltSpeedVolume;
int iAchievementSoundVolume;
// Legacy volume settings, 0-10. These get auto-upgraded and should not be used.
int iLegacyGameVolume;
int iLegacyReverbVolume;
int iLegacyAchievementVolume;

// Newer volume settings, 0-100
int iGameVolume;
int iReverbVolume;
int iUIVolume;
int iAchievementVolume;
int iAltSpeedVolume;

bool bExtraAudioBuffering; // For bluetooth
std::string sAudioDevice;
Expand Down
15 changes: 5 additions & 10 deletions Core/ConfigValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,14 @@ constexpr int VOLUME_FULL = 10;
constexpr int VOLUMEHI_FULL = 100; // for newer volume params. will convert them all later

// This matches exactly the old shift-based curve.
inline float Volume10ToMultiplier(int volume) {
// Allow muting entirely.
if (volume <= 0) {
return 0.0f;
}
return powf(2.0f, (float)(volume - 10));
}
float Volume10ToMultiplier(int volume);

// NOTE: This is used for new volume parameters.
// It uses a more intuitive-feeling curve.
inline float Volume100ToMultiplier(int volume) {
return powf(volume * 0.01f, 1.75f);
}
float Volume100ToMultiplier(int volume);

// Used for migration from the old settings.
int MultiplierToVolume100(float multiplier);

struct ConfigTouchPos {
float x;
Expand Down
2 changes: 1 addition & 1 deletion Core/HLE/__sceAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ void __AudioUpdate(bool resetRecording) {
}

if (g_Config.bEnableSound) {
float multiplier = Volume10ToMultiplier(std::clamp(g_Config.iGameVolume, 0, VOLUME_FULL));
float multiplier = Volume100ToMultiplier(std::clamp(g_Config.iGameVolume, 0, VOLUMEHI_FULL));
if (PSP_CoreParameter().fpsLimit != FPSLimit::NORMAL || PSP_CoreParameter().fastForward) {
if (g_Config.iAltSpeedVolume != -1) {
// Multiply in the alt speed volume instead of replacing like before.
Expand Down
2 changes: 1 addition & 1 deletion Core/HW/SasAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ void SasInstance::ApplyWaveformEffect() {
}

// Volume max is 0x1000, while our factor is up to 0x8000. Shifting left by 3 fixes that.
reverb_.ProcessReverb(sendBufferProcessed, sendBufferDownsampled, grainSize / 2, waveformEffect.leftVol << 3, waveformEffect.rightVol << 3);
reverb_.ProcessReverb(sendBufferProcessed, sendBufferDownsampled, grainSize / 2, (uint16_t)(waveformEffect.leftVol << 3), (uint16_t)(waveformEffect.rightVol << 3));
}

void SasInstance::DoState(PointerWrap &p) {
Expand Down
14 changes: 8 additions & 6 deletions Core/HW/SasReverb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class BufferWrapper {
int size_;
};

void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, uint16_t volLeft, uint16_t volRight) {
void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, int volLeft, int volRight) {
// This means replicate the input signal in the processed buffer.
// Can also be used to verify that the error is in here...
if (preset_ == -1) {
Expand All @@ -221,13 +221,15 @@ void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inpu
return;
}

const uint8_t reverbVolume = Clamp(g_Config.iReverbVolume, 0, 25);
const float reverbVolumeMultiplier = Volume100ToMultiplier(g_Config.iReverbVolume);
// Standard volume is 10, which pairs with a normal shift of 15.
const uint8_t finalShift = 25 - reverbVolume;
if (reverbVolume == 0) {
if (reverbVolumeMultiplier <= 0.0f) {
// Force to zero output, which is not the same as "Off."
memset(output, 0, inputSize * 4);
return;
} else {
volLeft *= reverbVolumeMultiplier;
volRight *= reverbVolumeMultiplier;
}

const SasReverbData &d = presets[preset_];
Expand Down Expand Up @@ -266,8 +268,8 @@ void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inpu
b[d.mRAPF2] = clamp_s16(Rout - (d.vAPF2*b[(d.mRAPF2 - d.dAPF2)] >> 15));
Rout = b[(d.mRAPF2 - d.dAPF2)] + (b[d.mRAPF2] * d.vAPF2 >> 15);
// ___Output to Mixer(Output volume multiplied with input from APF2)___________
output[i * 4 + 0] = clamp_s16((Lout * volLeft) >> finalShift);
output[i * 4 + 1] = clamp_s16((Rout * volRight) >> finalShift);
output[i * 4 + 0] = clamp_s16((Lout * volLeft) >> 15);
output[i * 4 + 1] = clamp_s16((Rout * volRight) >> 15);
output[i * 4 + 2] = 0;
output[i * 4 + 3] = 0;

Expand Down
4 changes: 2 additions & 2 deletions Core/HW/SasReverb.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SasReverb {

// Input should be a mixdown of all the channels that have reverb enabled, at 22khz.
// Output is written back at 44khz.
void ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, uint16_t volLeft, uint16_t volRight);
void ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, int volLeft, int volRight);

private:
enum {
Expand All @@ -41,4 +41,4 @@ class SasReverb {
int16_t *workspace_;
int preset_;
int pos_;
};
};
4 changes: 2 additions & 2 deletions UI/BackgroundAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,9 @@ void SoundEffectMixer::Mix(int16_t *buffer, int sz, int sampleRateHz) {
}
}

void SoundEffectMixer::Play(UI::UISound sfx, float volume) {
void SoundEffectMixer::Play(UI::UISound sfx, float multiplier) {
std::lock_guard<std::mutex> guard(mutex_);
queue_.push_back(PlayInstance{ sfx, 0, (int)(255.0f * volume), false });
queue_.push_back(PlayInstance{ sfx, 0, (int)(255.0f * multiplier), false });
}

void SoundEffectMixer::UpdateSample(UI::UISound sound, Sample *sample) {
Expand Down
2 changes: 1 addition & 1 deletion UI/EmuScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ void EmuScreen::sendMessage(UIMessage message, const char *value) {
}
} else if (message == UIMessage::REQUEST_PLAY_SOUND) {
if (g_Config.bAchievementsSoundEffects && g_Config.bEnableSound) {
float achievementVolume = Volume10ToMultiplier(g_Config.iAchievementSoundVolume) * Volume100ToMultiplier(g_Config.iGameVolume);
float achievementVolume = Volume100ToMultiplier(g_Config.iAchievementVolume);
// TODO: Handle this some nicer way.
if (!strcmp(value, "achievement_unlocked")) {
g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume);
Expand Down
21 changes: 15 additions & 6 deletions UI/GameSettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include "UI/RetroAchievementScreens.h"
#include "UI/OnScreenDisplay.h"
#include "UI/DiscordIntegration.h"
#include "UI/BackgroundAudio.h"

#include "Common/File/FileUtil.h"
#include "Common/File/AndroidContentURI.h"
Expand Down Expand Up @@ -656,11 +657,13 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) {
// This is here because it now only applies to in-game. Muting the menu sounds is separate.
CheckBox *enableSound = audioSettings->Add(new CheckBox(&g_Config.bEnableSound, a->T("Enable Sound")));

PopupSliderChoice *volume = audioSettings->Add(new PopupSliderChoice(&g_Config.iGameVolume, VOLUME_OFF, VOLUME_FULL, VOLUME_FULL, a->T("Game volume"), screenManager()));
PopupSliderChoice *volume = audioSettings->Add(new PopupSliderChoice(&g_Config.iGameVolume, VOLUME_OFF, VOLUMEHI_FULL, VOLUMEHI_FULL, a->T("Game volume"), screenManager()));
volume->SetFormat("%d%%");
volume->SetEnabledPtr(&g_Config.bEnableSound);
volume->SetZeroLabel(a->T("Mute"));

PopupSliderChoice *reverbVolume = audioSettings->Add(new PopupSliderChoice(&g_Config.iReverbVolume, VOLUME_OFF, 2 * VOLUME_FULL, VOLUME_FULL, a->T("Reverb volume"), screenManager()));
PopupSliderChoice *reverbVolume = audioSettings->Add(new PopupSliderChoice(&g_Config.iReverbVolume, VOLUME_OFF, 2 * VOLUMEHI_FULL, VOLUMEHI_FULL, a->T("Reverb volume"), screenManager()));
reverbVolume->SetFormat("%d%%");
reverbVolume->SetEnabledPtr(&g_Config.bEnableSound);
reverbVolume->SetZeroLabel(a->T("Disabled"));

Expand All @@ -669,9 +672,17 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) {
altVolume->SetEnabledPtr(&g_Config.bEnableSound);
altVolume->SetZeroLabel(a->T("Mute"));

PopupSliderChoice *achievementVolume = audioSettings->Add(new PopupSliderChoice(&g_Config.iAchievementSoundVolume, VOLUME_OFF, VOLUME_FULL, VOLUME_FULL, ac->T("Achievement sound volume"), screenManager()));
PopupSliderChoice *achievementVolume = audioSettings->Add(new PopupSliderChoice(&g_Config.iAchievementVolume, VOLUME_OFF, VOLUMEHI_FULL, MultiplierToVolume100(0.6f), ac->T("Achievement sound volume"), screenManager()));
achievementVolume->SetFormat("%d%%");
achievementVolume->SetEnabledPtr(&g_Config.bEnableSound);
achievementVolume->SetZeroLabel(a->T("Mute"));
achievementVolume->SetLiveUpdate(true);
achievementVolume->OnChange.Add([](UI::EventParams &e) {
// Audio preview
float achievementVolume = Volume100ToMultiplier(g_Config.iAchievementVolume);
g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume);
return UI::EVENT_DONE;
});

audioSettings->Add(new ItemHeader(a->T("UI sound")));

Expand All @@ -691,8 +702,7 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) {
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
if (IsVistaOrHigher()) {
static const char *backend[] = { "Auto", "DSound (compatible)", "WASAPI (fast)" };
PopupMultiChoice *audioBackend = audioSettings->Add(new PopupMultiChoice(&g_Config.iAudioBackend, a->T("Audio backend", "Audio backend (restart req.)"), backend, 0, ARRAY_SIZE(backend), I18NCat::AUDIO, screenManager()));
audioBackend->SetEnabledPtr(&g_Config.bEnableSound);
audioSettings->Add(new PopupMultiChoice(&g_Config.iAudioBackend, a->T("Audio backend", "Audio backend (restart req.)"), backend, 0, ARRAY_SIZE(backend), I18NCat::AUDIO, screenManager()));
}
#endif

Expand All @@ -712,7 +722,6 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) {

#if PPSSPP_PLATFORM(ANDROID)
CheckBox *extraAudio = audioSettings->Add(new CheckBox(&g_Config.bExtraAudioBuffering, a->T("AudioBufferingForBluetooth", "Bluetooth-friendly buffer (slower)")));
extraAudio->SetEnabledPtr(&g_Config.bEnableSound);

// Show OpenSL debug info
const std::string audioErrorStr = AndroidAudio_GetErrorString(g_audioState);
Expand Down
9 changes: 5 additions & 4 deletions UI/RetroAchievementScreens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ AudioFileChooser::AudioFileChooser(RequesterToken token, std::string *value, std
layoutParams_->height = ITEM_HEIGHT;
}
Add(new Choice(ImageID("I_PLAY"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) {
float achievementVolume = g_Config.iAchievementSoundVolume * 0.1f;
float achievementVolume = Volume100ToMultiplier(g_Config.iAchievementVolume);
g_BackgroundAudio.SFX().Play(sound_, achievementVolume);
return UI::EVENT_DONE;
});
Expand Down Expand Up @@ -363,9 +363,10 @@ void RetroAchievementsSettingsScreen::CreateCustomizeTab(UI::ViewGroup *viewGrou
viewGroup->Add(new AudioFileChooser(GetRequesterToken(), &g_Config.sAchievementsUnlockAudioFile, ac->T("Achievement unlocked"), UISound::ACHIEVEMENT_UNLOCKED));
viewGroup->Add(new AudioFileChooser(GetRequesterToken(), &g_Config.sAchievementsLeaderboardSubmitAudioFile, ac->T("Leaderboard score submission"), UISound::LEADERBOARD_SUBMITTED));
}
PopupSliderChoice *volume = viewGroup->Add(new PopupSliderChoice(&g_Config.iAchievementSoundVolume, VOLUME_OFF, VOLUME_FULL, VOLUME_FULL, ac->T("Achievement sound volume"), screenManager()));
volume->SetEnabledPtr(&g_Config.bEnableSound);
volume->SetZeroLabel(a->T("Mute"));
PopupSliderChoice *achievementVolume = viewGroup->Add(new PopupSliderChoice(&g_Config.iAchievementVolume, VOLUME_OFF, VOLUMEHI_FULL, MultiplierToVolume100(0.6f), ac->T("Achievement sound volume"), screenManager()));
achievementVolume->SetFormat("%d%%");
achievementVolume->SetEnabledPtr(&g_Config.bEnableSound);
achievementVolume->SetZeroLabel(a->T("Mute"));

static const char *positions[] = { "None", "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right" };

Expand Down
4 changes: 2 additions & 2 deletions headless/Headless.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ int main(int argc, const char* argv[])
g_Config.sMACAddress = "12:34:56:78:9A:BC";
g_Config.iFirmwareVersion = PSP_DEFAULT_FIRMWARE;
g_Config.iPSPModel = PSP_MODEL_SLIM;
g_Config.iGameVolume = VOLUME_FULL;
g_Config.iReverbVolume = VOLUME_FULL;
g_Config.iGameVolume = VOLUMEHI_FULL;
g_Config.iReverbVolume = VOLUMEHI_FULL;
g_Config.internalDataDirectory.clear();
g_Config.bUseExperimentalAtrac = newAtrac;

Expand Down
15 changes: 15 additions & 0 deletions unittest/UnitTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,20 @@ bool TestCrossSIMD() {
return true;
}

bool TestVolumeFunc() {
for (int i = 0; i <= 20; i++) {
float mul = Volume10ToMultiplier(i);

int vol100 = MultiplierToVolume100(mul);
float mul2 = Volume100ToMultiplier(vol100);

bool smaller = (fabsf(mul2 - mul) < 0.02f);
EXPECT_TRUE(smaller);
// printf("%d -> %f -> %d -> %f\n", i, mul, vol100, mul2);
}
return true;
}

typedef bool (*TestFunc)();
struct TestItem {
const char *name;
Expand Down Expand Up @@ -1282,6 +1296,7 @@ TestItem availableTests[] = {
TEST_ITEM(Buffer),
TEST_ITEM(SIMD),
TEST_ITEM(CrossSIMD),
TEST_ITEM(VolumeFunc),
};

int main(int argc, const char *argv[]) {
Expand Down

0 comments on commit 29a0835

Please sign in to comment.