Start work on making sound effects customizable

This commit is contained in:
Henrik Rydgård 2023-07-16 12:15:44 +02:00
parent 2537f76277
commit bd4f3f8953
9 changed files with 146 additions and 34 deletions

View File

@ -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();
}

View File

@ -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);

View File

@ -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[] = {

View File

@ -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;

View File

@ -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>(sample);
} else {
LoadDefaultSample(sound);
}
}
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;
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;
}
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>(Sample::Load(filename));
}
void SoundEffectMixer::LoadSamples() {
samples_.resize((size_t)UI::UISound::COUNT);
samples_[(size_t)UI::UISound::BACK] = std::unique_ptr<Sample>(LoadSample("sfx_back.wav"));
samples_[(size_t)UI::UISound::SELECT] = std::unique_ptr<Sample>(LoadSample("sfx_select.wav"));
samples_[(size_t)UI::UISound::CONFIRM] = std::unique_ptr<Sample>(LoadSample("sfx_confirm.wav"));
samples_[(size_t)UI::UISound::TOGGLE_ON] = std::unique_ptr<Sample>(LoadSample("sfx_toggle_on.wav"));
samples_[(size_t)UI::UISound::TOGGLE_OFF] = std::unique_ptr<Sample>(LoadSample("sfx_toggle_off.wav"));
samples_[(size_t)UI::UISound::ACHIEVEMENT_UNLOCKED] = std::unique_ptr<Sample>(LoadSample("sfx_achievement_unlocked.wav"));
samples_[(size_t)UI::UISound::LEADERBOARD_SUBMITTED] = std::unique_ptr<Sample>(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);

View File

@ -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<std::unique_ptr<Sample>> samples_;
struct PlayInstance {
@ -39,6 +43,7 @@ public:
bool done;
};
private:
std::mutex mutex_;
std::vector<PlayInstance> queue_;
std::vector<PlayInstance> plays_;

View File

@ -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);

View File

@ -42,6 +42,7 @@ protected:
private:
void CreateAccountTab(UI::ViewGroup *viewGroup);
void CreateCustomizeTab(UI::ViewGroup *viewGroup);
void CreateDeveloperToolsTab(UI::ViewGroup *viewGroup);
std::string username_;

View File

@ -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.