diff --git a/Common/UI/Root.cpp b/Common/UI/Root.cpp index 3c4311160c..aa4c08d2c9 100644 --- a/Common/UI/Root.cpp +++ b/Common/UI/Root.cpp @@ -21,7 +21,7 @@ static bool focusMovementEnabled; bool focusForced; static std::mutex eventMutex_; -static std::function soundCallback; +static std::function soundCallback; static bool soundEnabled = true; struct DispatchQueueItem { @@ -158,13 +158,13 @@ void SetSoundEnabled(bool enabled) { soundEnabled = enabled; } -void SetSoundCallback(std::function func) { +void SetSoundCallback(std::function func) { soundCallback = func; } -void PlayUISound(UISound sound) { +void PlayUISound(UISound sound, float volume) { if (soundEnabled && soundCallback) { - soundCallback(sound); + soundCallback(sound, volume); } } diff --git a/Common/UI/Root.h b/Common/UI/Root.h index 5a51eeb8e8..0e8d4e7d63 100644 --- a/Common/UI/Root.h +++ b/Common/UI/Root.h @@ -43,12 +43,14 @@ enum class UISound { CONFIRM, TOGGLE_ON, TOGGLE_OFF, + ACHIEVEMENT_UNLOCKED, + LEADERBOARD_SUBMITTED, COUNT, }; void SetSoundEnabled(bool enabled); -void SetSoundCallback(std::function func); +void SetSoundCallback(std::function func); -void PlayUISound(UISound sound); +void PlayUISound(UISound sound, float volume = 0.25f); } // namespace UI diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 756ec82eb8..9a52842afb 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -261,10 +261,14 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED: // An achievement was earned by the player. The handler should notify the player that the achievement was earned. g_OSD.ShowAchievementUnlocked(event->achievement->id); + System_PostUIMessage("play_sound", "achievement_unlocked"); INFO_LOG(ACHIEVEMENTS, "Achievement unlocked: '%s' (%d)", event->achievement->title, event->achievement->id); break; + case RC_CLIENT_EVENT_GAME_COMPLETED: { + // TODO: Do some zany fireworks! + // All achievements for the game have been earned. The handler should notify the player that the game was completed or mastered, depending on challenge mode. auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); @@ -279,6 +283,8 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * g_OSD.Show(OSDType::MESSAGE_INFO, title, message, DeNull(gameInfo->badge_name), 10.0f); + System_PostUIMessage("play_sound", "achievement_unlocked"); + INFO_LOG(ACHIEVEMENTS, "%s", message.c_str()); break; } @@ -295,7 +301,7 @@ static void event_handler_callback(const rc_client_event_t *event, rc_client_t * case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: NOTICE_LOG(ACHIEVEMENTS, "Leaderboard result submitted: %s", event->leaderboard->title); g_OSD.Show(OSDType::MESSAGE_SUCCESS, ReplaceAll(ReplaceAll(ac->T("%1: Submitting leaderboard score: %2!"), "%1", DeNull(event->leaderboard->title)), "%2", DeNull(event->leaderboard->tracker_value)), DeNull(event->leaderboard->description), 3.0f); - // A leaderboard attempt was completed.The handler may show a message with the leaderboard title and /or description indicating the final value being submitted to the server. + System_PostUIMessage("play_sound", "leaderboard_submitted"); break; case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW: NOTICE_LOG(ACHIEVEMENTS, "Challenge indicator show: %s", event->achievement->title); diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index 1decb572c9..bb71e3e60b 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -381,12 +381,17 @@ void SoundEffectMixer::Mix(int16_t *buffer, int sz, int sampleRateHz) { for (std::vector::iterator iter = plays_.begin(); iter != plays_.end(); ) { auto sample = samples_[(int)iter->sound].get(); + if (!sample) { + // Remove playback instance if sample invalid. + iter = plays_.erase(iter); + continue; + } int64_t rateOfSample = sample->rateInHz_; int64_t stride = (rateOfSample << 32) / sampleRateHz; for (int i = 0; i < sz * 2; i += 2) { - if (!sample || (iter->offset >> 32) >= sample->length_ - 2) { + if ((iter->offset >> 32) >= sample->length_ - 2) { iter->done = true; break; } @@ -415,15 +420,16 @@ void SoundEffectMixer::Mix(int16_t *buffer, int sz, int sampleRateHz) { } } -void SoundEffectMixer::Play(UI::UISound sfx) { +void SoundEffectMixer::Play(UI::UISound sfx, float volume) { std::lock_guard guard(mutex_); - plays_.push_back(PlayInstance{ sfx, 0, 64, false }); + plays_.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; } @@ -451,8 +457,10 @@ void SoundEffectMixer::LoadSamples() { 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")); - UI::SetSoundCallback([](UI::UISound sound) { - g_BackgroundAudio.SFX().Play(sound); + UI::SetSoundCallback([](UI::UISound sound, float volume) { + g_BackgroundAudio.SFX().Play(sound, volume); }); } diff --git a/UI/BackgroundAudio.h b/UI/BackgroundAudio.h index c03d4ea318..d2aff93b5e 100644 --- a/UI/BackgroundAudio.h +++ b/UI/BackgroundAudio.h @@ -28,7 +28,7 @@ public: void LoadSamples(); void Mix(int16_t *buffer, int sz, int sampleRateHz); - void Play(UI::UISound sfx); + void Play(UI::UISound sfx, float volume); std::vector> samples_; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index fe6d65cd18..f0be5ed6bd 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -544,6 +544,16 @@ void EmuScreen::sendMessage(const char *message, const char *value) { screenManager()->push(new GamePauseScreen(gamePath_)); } } + } else if (!strcmp(message, "play_sound")) { + if (g_Config.bAchievementsSoundEffects) { + // TODO: Handle this some nicer way. + if (!strcmp(value, "achievement_unlocked")) { + UI::PlayUISound(UI::UISound::ACHIEVEMENT_UNLOCKED, 0.6f); + } + if (!strcmp(value, "leaderboard_submitted")) { + UI::PlayUISound(UI::UISound::LEADERBOARD_SUBMITTED, 0.6f); + } + } } } diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index 4aaaf8c72d..d6c1f29dba 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -265,7 +265,7 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup) }); viewGroup->Add(new CheckBox(&g_Config.bAchievementsChallengeMode, ac->T("Challenge Mode (no savestates)")))->SetEnabledPtr(&g_Config.bAchievementsEnable); viewGroup->Add(new CheckBox(&g_Config.bAchievementsEncoreMode, ac->T("Encore Mode")))->SetEnabledPtr(&g_Config.bAchievementsEnable); - // viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects")))->SetEnabledPtr(&g_Config.bAchievementsEnable); // not yet implemented + viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects")))->SetEnabledPtr(&g_Config.bAchievementsEnable); // not yet implemented viewGroup->Add(new ItemHeader(di->T("Links"))); viewGroup->Add(new Choice(ac->T("RetroAchievements website")))->OnClick.Add([&](UI::EventParams &) -> UI::EventReturn { diff --git a/assets/sfx_achievement_unlocked.wav b/assets/sfx_achievement_unlocked.wav new file mode 100644 index 0000000000..e98228e11d Binary files /dev/null and b/assets/sfx_achievement_unlocked.wav differ diff --git a/assets/sfx_leaderbord_submitted.wav b/assets/sfx_leaderbord_submitted.wav new file mode 100644 index 0000000000..5544fef324 Binary files /dev/null and b/assets/sfx_leaderbord_submitted.wav differ