sequence notifs (updated #5265) (#5824)

* Adds ability for Notifiations to not make a noise

This is probably the only feature that will use it, the noise makes sense for most things we want to use notifications for, but it playing on every scene transition was a bit distracting.

* Adds a hook for OnSeqPlayerInit

* Uses new hook and displays notification instead of overlay text

* Changes names to prevent collisions

Will be registering other types of hooks that will need different ShipInitFuncs in this same file later.

* Change Icon

* Change CVarName and remove now-unused duration slider

* Update ConfigMigrator for CVar changes.

* clang-format

* fix

* bring back duration control

* config v4

* fix v4 migration

---------

Co-authored-by: Christopher Leggett <chris@leggett.dev>
Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com>
This commit is contained in:
briaguya
2025-10-04 21:57:01 -04:00
committed by GitHub
parent 6c724382f5
commit dd2628f737
12 changed files with 91 additions and 23 deletions

View File

@@ -842,18 +842,17 @@ void RegisterAudioWidgets() {
"the area for the effect to kick in."));
SohGui::mSohMenu->AddSearchWidget({ leadingMusic, "Enhancements", "Audio Editor", "Audio Options" });
displaySeqName = { .name = "Display Sequence Name on Overlay", .type = WidgetType::WIDGET_CVAR_CHECKBOX };
displaySeqName.CVar(CVAR_AUDIO("SeqNameOverlay"))
displaySeqName = { .name = "Display Sequence Name in Notifications", .type = WidgetType::WIDGET_CVAR_CHECKBOX };
displaySeqName.CVar(CVAR_AUDIO("SeqNameNotification"))
.Options(CheckboxOptions()
.Color(THEME_COLOR)
.Tooltip("Displays the name of the current sequence in the corner of the screen whenever a new "
"sequence "
"is loaded to the main sequence player (does not apply to fanfares or enemy BGM)."));
.Tooltip("Emits a notification with the current song name whenever it changes. "
"(does not apply to fanfares or enemy BGM)."));
SohGui::mSohMenu->AddSearchWidget({ displaySeqName, "Enhancements", "Audio Editor", "Audio Options" });
ovlDuration = { .name = "Overlay Duration: %d seconds", .type = WidgetType::WIDGET_CVAR_SLIDER_INT };
ovlDuration.CVar(CVAR_AUDIO("SeqNameOverlayDuration"))
.Options(IntSliderOptions().Color(THEME_COLOR).Min(1).Max(10).DefaultValue(5).Size(ImVec2(300.0f, 0.0f)));
ovlDuration = { .name = "Sequence Notification Duration: %d seconds", .type = WidgetType::WIDGET_CVAR_SLIDER_INT };
ovlDuration.CVar(CVAR_AUDIO("SeqNameNotificationDuration"))
.Options(IntSliderOptions().Color(THEME_COLOR).Min(1).Max(20).DefaultValue(10).Size(ImVec2(300.0f, 0.0f)));
SohGui::mSohMenu->AddSearchWidget({ ovlDuration, "Enhancements", "Audio Editor", "Audio Options" });
voicePitch = { .name = "Link's Voice Pitch Multiplier", .type = WidgetType::WIDGET_CVAR_SLIDER_FLOAT };

View File

@@ -0,0 +1,43 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
#include "AudioCollection.h"
#include <soh/Notification/Notification.h>
#include <soh/SohGui/ImGuiUtils.h>
extern "C" {
#include "variables.h"
extern PlayState* gPlayState;
}
#define CVAR_SEQOVERLAY_NAME CVAR_AUDIO("SeqNameNotification")
#define CVAR_SEQOVERLAY_DEFAULT 0
#define CVAR_SEQOVERLAY_VALUE CVarGetInteger(CVAR_SEQOVERLAY_NAME, CVAR_SEQOVERLAY_DEFAULT)
void NotifySequenceName(int32_t playerIdx, int32_t seqId) {
// Keep track of the previous sequence/scene so we don't repeat notifications
static uint16_t previousSeqId = UINT16_MAX;
static int16_t previousSceneNum = INT16_MAX;
if (playerIdx == SEQ_PLAYER_BGM_MAIN &&
(seqId != previousSeqId || (gPlayState != NULL && gPlayState->sceneNum != previousSceneNum))) {
previousSeqId = seqId;
if (gPlayState != NULL) {
previousSceneNum = gPlayState->sceneNum;
}
const char* sequenceName = AudioCollection::Instance->GetSequenceName(seqId);
if (sequenceName != NULL) {
Notification::Emit({
.message = ICON_FA_MUSIC " " + std::string(sequenceName),
.remainingTime = static_cast<float>(CVarGetInteger(CVAR_AUDIO("SeqNameNotificationDuration"), 10)),
.mute = true,
});
}
}
}
void RegisterAudioNotificationHooks() {
COND_HOOK(OnSeqPlayerInit, CVAR_SEQOVERLAY_VALUE, NotifySequenceName);
}
static RegisterShipInitFunc notifInitFunc(RegisterAudioNotificationHooks, { CVAR_SEQOVERLAY_NAME });

View File

@@ -74,3 +74,6 @@ DEFINE_HOOK(OnGenerationCompletion, ());
DEFINE_HOOK(OnSetGameLanguage, ());
DEFINE_HOOK(OnAssetAltChange, ());
DEFINE_HOOK(OnKaleidoUpdate, ());
// Audio
DEFINE_HOOK(OnSeqPlayerInit, (int32_t playerIdx, int32_t seqId));

View File

@@ -327,3 +327,8 @@ void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)) {
void GameInteractor_ExecuteOnKaleidoUpdate() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnKaleidoUpdate>();
}
// Mark: Audio
void GameInteractor_ExecuteOnSeqPlayerInit(int32_t playerIdx, int32_t seqId) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSeqPlayerInit>(playerIdx, seqId);
}

View File

@@ -86,6 +86,9 @@ void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void));
// Mark: - Pause Menu
void GameInteractor_ExecuteOnKaleidoUpdate();
// Mark: - Audio
void GameInteractor_ExecuteOnSeqPlayerInit(int32_t playerIdx, int32_t seqId);
#ifdef __cplusplus
}
#endif

View File

@@ -131,8 +131,10 @@ void Emit(Options notification) {
notification.remainingTime = CVarGetFloat(CVAR_SETTING("Notifications.Duration"), 10.0f);
}
notifications.push_back(notification);
Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
if (!notification.mute) {
Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
}
} // namespace Notification

View File

@@ -17,6 +17,7 @@ struct Options {
std::string suffix = "";
ImVec4 suffixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f);
float remainingTime = 0.0f; // Seconds
bool mute = false; // whether notification should make a noise
};
class Window final : public Ship::GuiWindow {

View File

@@ -1237,6 +1237,7 @@ extern "C" void InitOTR() {
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion1Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion2Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion3Updater>());
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion4Updater>());
conf->RunVersionUpdates();
SohGui::SetupGuiElements();

View File

@@ -1520,4 +1520,9 @@ std::vector<Migration> version3Migrations = {
{ MigrationAction::Remove, "gPreset0" },
{ MigrationAction::Remove, "gPreset1" },
};
std::vector<Migration> version4Migrations = {
{ MigrationAction::Rename, "gAudioEditor.SeqNameOverlay", "gAudioEditor.SeqNameNotification" },
{ MigrationAction::Rename, "gAudioEditor.SeqNameOverlayDuration", "gAudioEditor.SeqNameNotificationDuration" },
};
} // namespace SOH

View File

@@ -9,6 +9,8 @@ ConfigVersion2Updater::ConfigVersion2Updater() : ConfigVersionUpdater(2) {
}
ConfigVersion3Updater::ConfigVersion3Updater() : ConfigVersionUpdater(3) {
}
ConfigVersion4Updater::ConfigVersion4Updater() : ConfigVersionUpdater(4) {
}
void ConfigVersion1Updater::Update(Ship::Config* conf) {
if (conf->GetInt("Window.Width", 640) == 640) {
@@ -111,4 +113,13 @@ void ConfigVersion3Updater::Update(Ship::Config* conf) {
CVarClear(migration.from.c_str());
}
}
void ConfigVersion4Updater::Update(Ship::Config* conf) {
for (Migration migration : version4Migrations) {
if (migration.action == MigrationAction::Rename) {
CVarCopy(migration.from.c_str(), migration.to.value().c_str());
}
CVarClear(migration.from.c_str());
}
}
} // namespace SOH

View File

@@ -18,4 +18,10 @@ class ConfigVersion3Updater final : public Ship::ConfigVersionUpdater {
ConfigVersion3Updater();
void Update(Ship::Config* conf);
};
class ConfigVersion4Updater final : public Ship::ConfigVersionUpdater {
public:
ConfigVersion4Updater();
void Update(Ship::Config* conf);
};
} // namespace SOH

View File

@@ -8,6 +8,7 @@
#include "soh/Enhancements/audio/AudioCollection.h"
#include "soh/Enhancements/audio/AudioEditor.h"
#include "soh/ResourceManagerHelpers.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include <stdio.h>
#ifdef _MSC_VER
#define strdup _strdup
@@ -630,19 +631,7 @@ s32 AudioLoad_SyncInitSeqPlayerInternal(s32 playerIdx, s32 seqId, s32 arg2) {
AudioSeq_SkipForwardSequence(seqPlayer);
//! @bug missing return (but the return value is not used so it's not UB)
// Keep track of the previous sequence/scene so we don't repeat notifications
static uint16_t previousSeqId = UINT16_MAX;
static int16_t previousSceneNum = INT16_MAX;
if (CVarGetInteger(CVAR_AUDIO("SeqNameOverlay"), 0) && playerIdx == SEQ_PLAYER_BGM_MAIN &&
(seqId != previousSeqId || (gPlayState != NULL && gPlayState->sceneNum != previousSceneNum))) {
previousSeqId = seqId;
if (gPlayState != NULL) {
previousSceneNum = gPlayState->sceneNum;
}
AudioCollection_EmitSongNameNotification(seqId);
}
GameInteractor_ExecuteOnSeqPlayerInit(playerIdx, seqId);
}
u8* AudioLoad_SyncLoadSeq(s32 seqId) {