From bd4f3f89539b4ed415360d685ac5f01a7e198e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 16 Jul 2023 12:15:44 +0200 Subject: [PATCH] Start work on making sound effects customizable --- Common/UI/PopupScreens.cpp | 4 ++ Common/UI/Root.cpp | 3 +- Core/Config.cpp | 6 ++- Core/Config.h | 4 ++ UI/BackgroundAudio.cpp | 94 +++++++++++++++++++++++----------- UI/BackgroundAudio.h | 7 ++- UI/RetroAchievementScreens.cpp | 57 ++++++++++++++++++++- UI/RetroAchievementScreens.h | 1 + assets/lang/sv_SE.ini | 4 ++ 9 files changed, 146 insertions(+), 34 deletions(-) diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index 53b07f15f9..61a07895a7 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -670,6 +670,10 @@ FileChooserChoice::FileChooserChoice(std::string *value, const std::string &text } std::string FileChooserChoice::ValueText() const { + if (value_->empty()) { + auto di = GetI18NCategory(I18NCat::DIALOG); + return di->T("Default"); + } Path path(*value_); return path.GetFilename(); } diff --git a/Common/UI/Root.cpp b/Common/UI/Root.cpp index 82b22de0a9..d024d5ec62 100644 --- a/Common/UI/Root.cpp +++ b/Common/UI/Root.cpp @@ -141,8 +141,7 @@ static void MoveFocus(ViewGroup *root, FocusDirection direction) { return; } - NeighborResult neigh = root->FindNeighbor(focusedView, direction, neigh); - + NeighborResult neigh = root->FindNeighbor(focusedView, direction, NeighborResult()); if (neigh.view) { neigh.view->SetFocus(); root->SubviewFocused(neigh.view); diff --git a/Core/Config.cpp b/Core/Config.cpp index 1c2cd2cace..6786412161 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -265,11 +265,11 @@ static bool DefaultSasThread() { } static const ConfigSetting achievementSettings[] = { + // Core settings ConfigSetting("AchievementsEnable", &g_Config.bAchievementsEnable, true, CfgFlag::DEFAULT), ConfigSetting("AchievementsChallengeMode", &g_Config.bAchievementsChallengeMode, false, CfgFlag::DEFAULT), ConfigSetting("AchievementsEncoreMode", &g_Config.bAchievementsEncoreMode, false, CfgFlag::DEFAULT), ConfigSetting("AchievementsUnofficial", &g_Config.bAchievementsUnofficial, false, CfgFlag::DEFAULT), - ConfigSetting("AchievementsSoundEffects", &g_Config.bAchievementsSoundEffects, true, CfgFlag::DEFAULT), ConfigSetting("AchievementsLogBadMemReads", &g_Config.bAchievementsLogBadMemReads, false, CfgFlag::DEFAULT), // Achievements login info. Note that password is NOT stored, only a login token. @@ -277,6 +277,10 @@ static const ConfigSetting achievementSettings[] = { // from the ini if manually entered (useful when testing various builds on Android). ConfigSetting("AchievementsToken", &g_Config.sAchievementsToken, "", CfgFlag::DONT_SAVE), ConfigSetting("AchievementsUserName", &g_Config.sAchievementsUserName, "", CfgFlag::DEFAULT), + + // Customizations + ConfigSetting("AchievementsUnlockAudioFile", &g_Config.sAchievementsUnlockAudioFile, "", CfgFlag::DEFAULT), + ConfigSetting("AchievementsLeaderboardSubmitAudioFile", &g_Config.sAchievementsLeaderboardSubmitAudioFile, "", CfgFlag::DEFAULT), }; static const ConfigSetting cpuSettings[] = { diff --git a/Core/Config.h b/Core/Config.h index c4af84b497..bd1b4223a1 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -495,6 +495,10 @@ public: bool bAchievementsSoundEffects; bool bAchievementsLogBadMemReads; + // Customizations + std::string sAchievementsUnlockAudioFile; + std::string sAchievementsLeaderboardSubmitAudioFile; + // Achivements login info. Note that password is NOT stored, only a login token. // Still, we may wanna store it more securely than in PPSSPP.ini, especially on Android. std::string sAchievementsUserName; diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index fad55d0d07..9384326b40 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -4,18 +4,20 @@ #include "Common/File/VFS/VFS.h" #include "Common/UI/Root.h" +#include "Common/Data/Text/I18n.h" #include "Common/CommonTypes.h" #include "Common/Data/Format/RIFF.h" #include "Common/Log.h" #include "Common/System/System.h" +#include "Common/System/OSD.h" #include "Common/Serialize/SerializeFuncs.h" #include "Common/TimeUtil.h" #include "Common/Data/Collections/FixedSizeQueue.h" #include "Core/HW/SimpleAudioDec.h" #include "Core/HLE/__sceAudio.h" #include "Core/System.h" -#include "GameInfoCache.h" #include "Core/Config.h" +#include "UI/GameInfoCache.h" #include "UI/BackgroundAudio.h" struct WavData { @@ -367,6 +369,31 @@ void BackgroundAudio::Update() { } } +Sample *Sample::Load(const std::string &path) { + size_t bytes; + uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes); + if (!data) { + WARN_LOG(AUDIO, "Failed to load sample '%s'", path.c_str()); + return nullptr; + } + + RIFFReader reader(data, (int)bytes); + + WavData wave; + wave.Read(reader); + + delete[] data; + + if (wave.num_channels != 2 || wave.raw_bytes_per_frame != 4) { + ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 16-bit raw stereo. '%s'", path.c_str()); + return nullptr; + } + + int16_t *samples = new int16_t[2 * wave.numFrames]; + memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame); + return new Sample(samples, wave.numFrames, wave.sample_rate); +} + static inline int16_t Clamp16(int32_t sample) { if (sample < -32767) return -32767; if (sample > 32767) return 32767; @@ -433,40 +460,49 @@ void SoundEffectMixer::Play(UI::UISound sfx, float volume) { queue_.push_back(PlayInstance{ sfx, 0, (int)(255.0f * volume), false }); } -Sample *SoundEffectMixer::LoadSample(const std::string &path) { - size_t bytes; - uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes); - if (!data) { - WARN_LOG(AUDIO, "Failed to load sample '%s'", path.c_str()); - return nullptr; +void SoundEffectMixer::UpdateSample(UI::UISound sound, Sample *sample) { + if (sample) { + samples_[(size_t)sound] = std::unique_ptr(sample); + } else { + LoadDefaultSample(sound); + } +} + +void SoundEffectMixer::LoadDefaultSample(UI::UISound sound) { + const char *filename = nullptr; + switch (sound) { + case UI::UISound::BACK: filename = "sfx_back.wav"; break; + case UI::UISound::SELECT: filename = "sfx_select.wav"; break; + case UI::UISound::CONFIRM: filename = "sfx_confirm.wav"; break; + case UI::UISound::TOGGLE_ON: filename = "sfx_toggle_on.wav"; break; + case UI::UISound::TOGGLE_OFF: filename = "sfx_toggle_off.wav"; break; + case UI::UISound::ACHIEVEMENT_UNLOCKED: filename = "sfx_achievement_unlocked.wav"; break; + case UI::UISound::LEADERBOARD_SUBMITTED: filename = "sfx_leaderbord_submitted.wav"; break; + default: + return; } - RIFFReader reader(data, (int)bytes); - - WavData wave; - wave.Read(reader); - - delete[] data; - - if (wave.num_channels != 2 || wave.raw_bytes_per_frame != 4) { - ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 16-bit raw stereo. '%s'", path.c_str()); - return nullptr; - } - - int16_t *samples = new int16_t[2 * wave.numFrames]; - memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame); - return new Sample(samples, wave.numFrames, wave.sample_rate); + samples_[(size_t)sound] = std::unique_ptr(Sample::Load(filename)); } void SoundEffectMixer::LoadSamples() { samples_.resize((size_t)UI::UISound::COUNT); - samples_[(size_t)UI::UISound::BACK] = std::unique_ptr(LoadSample("sfx_back.wav")); - samples_[(size_t)UI::UISound::SELECT] = std::unique_ptr(LoadSample("sfx_select.wav")); - samples_[(size_t)UI::UISound::CONFIRM] = std::unique_ptr(LoadSample("sfx_confirm.wav")); - samples_[(size_t)UI::UISound::TOGGLE_ON] = std::unique_ptr(LoadSample("sfx_toggle_on.wav")); - samples_[(size_t)UI::UISound::TOGGLE_OFF] = std::unique_ptr(LoadSample("sfx_toggle_off.wav")); - samples_[(size_t)UI::UISound::ACHIEVEMENT_UNLOCKED] = std::unique_ptr(LoadSample("sfx_achievement_unlocked.wav")); - samples_[(size_t)UI::UISound::LEADERBOARD_SUBMITTED] = std::unique_ptr(LoadSample("sfx_leaderbord_submitted.wav")); + LoadDefaultSample(UI::UISound::BACK); + LoadDefaultSample(UI::UISound::SELECT); + LoadDefaultSample(UI::UISound::CONFIRM); + LoadDefaultSample(UI::UISound::TOGGLE_ON); + LoadDefaultSample(UI::UISound::TOGGLE_OFF); + + if (!g_Config.sAchievementsUnlockAudioFile.empty()) { + UpdateSample(UI::UISound::ACHIEVEMENT_UNLOCKED, Sample::Load(g_Config.sAchievementsUnlockAudioFile)); + } else { + LoadDefaultSample(UI::UISound::ACHIEVEMENT_UNLOCKED); + } + if (!g_Config.sAchievementsLeaderboardSubmitAudioFile.empty()) { + UpdateSample(UI::UISound::LEADERBOARD_SUBMITTED, Sample::Load(g_Config.sAchievementsLeaderboardSubmitAudioFile)); + } else { + LoadDefaultSample(UI::UISound::LEADERBOARD_SUBMITTED); + } UI::SetSoundCallback([](UI::UISound sound, float volume) { g_BackgroundAudio.SFX().Play(sound, volume); diff --git a/UI/BackgroundAudio.h b/UI/BackgroundAudio.h index 1b33caf387..322e0b3f28 100644 --- a/UI/BackgroundAudio.h +++ b/UI/BackgroundAudio.h @@ -19,17 +19,21 @@ struct Sample { int16_t *data_; int length_; // stereo samples. int rateInHz_; // sampleRate + + static Sample *Load(const std::string &path); }; // Mixer for things played on top of everything. class SoundEffectMixer { public: - static Sample *LoadSample(const std::string &path); void LoadSamples(); void Mix(int16_t *buffer, int sz, int sampleRateHz); void Play(UI::UISound sfx, float volume); + void UpdateSample(UI::UISound sound, Sample *sample); + void LoadDefaultSample(UI::UISound sound); + std::vector> samples_; struct PlayInstance { @@ -39,6 +43,7 @@ public: bool done; }; +private: std::mutex mutex_; std::vector queue_; std::vector plays_; diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index a0c7329d2f..67e2920fe8 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -1,4 +1,3 @@ -#include "UI/RetroAchievementScreens.h" #include "Common/System/OSD.h" #include "Common/System/Request.h" #include "Common/UI/View.h" @@ -10,10 +9,56 @@ #include "Core/Config.h" #include "Core/RetroAchievements.h" +#include "UI/RetroAchievementScreens.h" +#include "UI/BackgroundAudio.h" + static inline const char *DeNull(const char *ptr) { return ptr ? ptr : ""; } +// Compound view, creating a FileChooserChoice inside. +class AudioFileChooser : public UI::LinearLayout { +public: + AudioFileChooser(std::string *value, const std::string &title, UI::UISound sound, UI::LayoutParams *layoutParams = nullptr); + + UI::UISound sound_; +}; + +static constexpr UI::Size ITEM_HEIGHT = 64.f; + +AudioFileChooser::AudioFileChooser(std::string *value, const std::string &title, UI::UISound sound, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), sound_(sound) { + using namespace UI; + SetSpacing(2.0f); + if (!layoutParams) { + layoutParams_->width = FILL_PARENT; + layoutParams_->height = ITEM_HEIGHT; + } + Add(new FileChooserChoice(value, title, BrowseFileType::SOUND_EFFECT, new LinearLayoutParams(1.0f)))->OnChange.Add([=](UI::EventParams &e) { + // TODO: Check the file format here. + // Need to forward the event out. + std::string path = e.s; + Sample *sample = Sample::Load(path); + if (sample) { + g_BackgroundAudio.SFX().UpdateSample(sound, sample); + } else { + if (!sample) { + auto au = GetI18NCategory(I18NCat::AUDIO); + g_OSD.Show(OSDType::MESSAGE_WARNING, au->T("Audio file format not supported. Must be 16-bit WAV.")); + } + value->clear(); + } + return UI::EVENT_DONE; + }); + Add(new Choice(ImageID("I_ARROW_RIGHT"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { + g_BackgroundAudio.SFX().Play(sound_, 0.6f); + return UI::EVENT_DONE; + }); + Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) { + value->clear(); + return UI::EVENT_DONE; + }); +} + void RetroAchievementsListScreen::CreateTabs() { auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); @@ -194,6 +239,7 @@ void RetroAchievementsSettingsScreen::CreateTabs() { using namespace UI; CreateAccountTab(AddTab("AchievementsAccount", ac->T("Account"))); + CreateCustomizeTab(AddTab("AchievementsCustomize", ac->T("Customize"))); CreateDeveloperToolsTab(AddTab("AchievementsDeveloperTools", sy->T("Developer Tools"))); } @@ -283,6 +329,15 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup) }); } +void RetroAchievementsSettingsScreen::CreateCustomizeTab(UI::ViewGroup *viewGroup) { + auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); + + using namespace UI; + viewGroup->Add(new ItemHeader(ac->T("Sound effects"))); + viewGroup->Add(new AudioFileChooser(&g_Config.sAchievementsUnlockAudioFile, "Achievement unlocked", UISound::ACHIEVEMENT_UNLOCKED)); + viewGroup->Add(new AudioFileChooser(&g_Config.sAchievementsLeaderboardSubmitAudioFile, "Leaderboard score submission", UISound::LEADERBOARD_SUBMITTED)); +} + void RetroAchievementsSettingsScreen::CreateDeveloperToolsTab(UI::ViewGroup *viewGroup) { auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); diff --git a/UI/RetroAchievementScreens.h b/UI/RetroAchievementScreens.h index 9c29198153..284c530f1a 100644 --- a/UI/RetroAchievementScreens.h +++ b/UI/RetroAchievementScreens.h @@ -42,6 +42,7 @@ protected: private: void CreateAccountTab(UI::ViewGroup *viewGroup); + void CreateCustomizeTab(UI::ViewGroup *viewGroup); void CreateDeveloperToolsTab(UI::ViewGroup *viewGroup); std::string username_; diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 6c86083670..7a73de9a78 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -4,12 +4,15 @@ %1: Leaderboard attempt failed = %1: Topplisteförsök misslyckades %1: Submitting leaderboard score: %2! = %1: Skickar in toppliste-poäng: %2! Account = Inloggning +Achievement Unlocked = Achievement Unlocked Achievements = Achievements Challenge Mode = Utmanings-läge Challenge Mode (no savestates) = Utmanings-läge (inga sparade state) +Customize = Customize Earned = Du har tjänat %d av %d achievements, och %d of %d poäng Failed to log in, check your username and password. = Misslyckades att logga in, kontrollera ditt användarnamn och lösenord. How to use RetroAchievements = Hur man använder RetroAchievements +Leaderboard score submission = Leaderboard score submission Leaderboard submission is enabled = Skickar in poäng till ledartabeller Leaderboards = Leaderboards Links = Links @@ -317,6 +320,7 @@ ConnectingAP = Kopplar upp till acceesspunkten.\nVänta... ConnectingPleaseWait = Kopplar upp.\nVänta... ConnectionName = Uppkopplingsnamn Corrupted Data = Korrupt data +Default = Default Delete = Radera Delete all = Radera allt Delete completed = Borttaget.