Skip to content

Volume control UI changes, part 2 #19971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading