mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 05:19:56 +00:00
Start work on making sound effects customizable
This commit is contained in:
parent
2537f76277
commit
bd4f3f8953
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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[] = {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -42,6 +42,7 @@ protected:
|
||||
|
||||
private:
|
||||
void CreateAccountTab(UI::ViewGroup *viewGroup);
|
||||
void CreateCustomizeTab(UI::ViewGroup *viewGroup);
|
||||
void CreateDeveloperToolsTab(UI::ViewGroup *viewGroup);
|
||||
|
||||
std::string username_;
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user