Merge pull request #19971 from hrydgard/volume-control-convert-settings

Volume control UI changes, part 2
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2025-02-12 10:48:17 -06:00
commit 29a0835c6d
13 files changed
+127 -44

No files matched your search

+62 -4
View File
@@ -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),
@@ -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);
@@ -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) {
@@ -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);
}
+8 -5
View File
@@ -280,14 +280,17 @@ public:
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;
+5 -10
View File
@@ -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;
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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) {
+8 -6
View File
@@ -207,7 +207,7 @@ private:
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) {
@@ -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_];
@@ -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;
+2 -2
View File
@@ -31,7 +31,7 @@ public:
// 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 {
@@ -41,4 +41,4 @@ private:
int16_t *workspace_;
int preset_;
int pos_;
};
};
+2 -2
View File
@@ -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) {
+1 -1
View File
@@ -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);
+15 -6
View File
@@ -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"
@@ -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"));
@@ -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")));
@@ -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
@@ -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);
+5 -4
View File
@@ -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;
});
@@ -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" };
+2 -2
View File
@@ -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;
+15
View File
@@ -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;
@@ -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[]) {