Add Enhanced Surround option.

This commit is contained in:
Tortuga Veloz 2026-02-10 10:17:49 +01:00
parent ac123c4cab
commit 177fe6a4e5
8 changed files with 153 additions and 2 deletions

View file

@ -117,11 +117,40 @@
data-checked="audio_mode"
value="Surround"
id="audio_surround"
style="nav-up: #lhb_on"
style="nav-up: #lhb_on; nav-down: #enhanced_surround_on"
/>
<label class="config-option__tab-label" for="audio_surround">Surround</label>
</div>
</div>
<div class="config-option" data-event-mouseover="set_cur_config_index(4)">
<label class="config-option__title">Enhanced Surround</label>
<div class="config-option__list">
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(4)"
name="enhanced_surround"
data-checked="enhanced_surround_enabled"
value="1"
id="enhanced_surround_on"
style="nav-up: #audio_surround"
/>
<label class="config-option__tab-label" for="enhanced_surround_on">On</label>
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(4)"
name="enhanced_surround"
data-checked="enhanced_surround_enabled"
value="0"
id="enhanced_surround_off"
style="nav-up: #audio_surround"
/>
<label class="config-option__tab-label" for="enhanced_surround_off">Off</label>
</div>
</div>
</div>
<!-- Descriptions -->
<div class="config__wrapper">
@ -137,6 +166,9 @@
<p data-if="cur_config_index == 3">
Sets the audio output mode. <b>Stereo</b> outputs standard stereo audio. <b>Mono</b> outputs mono audio. <b>Headphones</b> optimizes audio for headphone listening. <b>Surround</b> enables 5.1 surround sound output using matrix decoding.
</p>
<p data-if="cur_config_index == 4">
When enabled with Surround mode, adds pan-based rear channel separation. Sounds panned left go to the rear-left speaker, sounds panned right go to the rear-right speaker, creating improved spatial separation.
</p>
</div>
</div>
</form>

View file

@ -103,6 +103,10 @@ namespace zelda64 {
{zelda64::AudioMode::Surround, "Surround"}
});
// Enhanced surround - adds pan-based rear channel separation
bool get_enhanced_surround_enabled();
void set_enhanced_surround_enabled(bool enabled);
AudioMode get_audio_mode();
void set_audio_mode(AudioMode mode);

View file

@ -10,5 +10,6 @@ DECLARE_FUNC(u32, recomp_get_low_health_beeps_enabled);
// Audio channel settings: 0 = Stereo, 1 = 5.1 Matrix, 2 = 5.1 Raw
DECLARE_FUNC(void, recomp_set_audio_channels, s32 channels);
DECLARE_FUNC(s32, recomp_get_audio_channels);
DECLARE_FUNC(s32, recomp_get_enhanced_surround_enabled);
#endif

View file

@ -408,3 +408,96 @@ RECOMP_PATCH void Audio_SetFileSelectSettings(s8 audioSetting) {
SEQCMD_SET_SOUND_MODE(soundMode);
}
// ============================================================================
// Enhanced Surround Sound - Pan-based RL/RR steering
// ============================================================================
// DMEM address constants for audio synthesis (from synthesis.c)
#define DMEM_SURROUND_TEMP 0x4B0
#define DMEM_HAAS_TEMP 0x5B0
#define DMEM_LEFT_CH 0x930
#define DMEM_RIGHT_CH 0xAD0
#define DMEM_WET_LEFT_CH 0xC70
#define DMEM_WET_RIGHT_CH 0xE10
// Forward declarations
void AudioSynth_DMemMove(Acmd* cmd, s32 dmemIn, s32 dmemOut, size_t size);
extern f32 gDefaultPanVolume[];
// @recomp Patched to add enhanced surround with pan-based RL/RR channel steering
// When enhanced surround is enabled, sounds panned left go more to Rear Left,
// and sounds panned right go more to Rear Right, creating better spatial separation.
RECOMP_PATCH Acmd* AudioSynth_ApplySurroundEffect(Acmd* cmd, NoteSampleState* sampleState, NoteSynthesisState* synthState,
s32 numSamplesPerUpdate, s32 haasDmem, s32 flags) {
s32 wetGain;
u16 dryGain;
s64 dmem = DMEM_SURROUND_TEMP;
f32 decayGain;
AudioSynth_DMemMove(cmd++, haasDmem, DMEM_HAAS_TEMP, numSamplesPerUpdate * SAMPLE_SIZE);
dryGain = synthState->surroundEffectGain;
if (flags == A_INIT) {
aClearBuffer(cmd++, dmem, sizeof(synthState->synthesisBuffers->surroundEffectState));
synthState->surroundEffectGain = 0;
} else {
aLoadBuffer(cmd++, synthState->synthesisBuffers->surroundEffectState, dmem,
sizeof(synthState->synthesisBuffers->surroundEffectState));
// @recomp Check if enhanced surround is enabled for pan-based RL/RR steering
if (recomp_get_enhanced_surround_enabled()) {
// === Matrix surround encoding: steer surround to RL or RR based on pan ===
// Calculate pan position: 0.0 = full left, 0.5 = center, 1.0 = full right
f32 sumVol = sampleState->targetVolLeft + sampleState->targetVolRight;
f32 panPosition = 0.5f; // default: center (mono surround)
if (sumVol > 0.0f) {
panPosition = (f32)sampleState->targetVolRight / sumVol;
}
// The L/R balance determines RL vs RR steering:
// - L dominant (leftGain > rightGain): surround goes more to Rear Left
// - R dominant (rightGain > leftGain): surround goes more to Rear Right
// - Equal: mono surround to both
s16 leftGain = (s16)(dryGain * (1.0f - panPosition));
s16 rightGain = (s16)(dryGain * panPosition);
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, leftGain, dmem, DMEM_LEFT_CH);
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, (rightGain ^ 0xFFFF), dmem, DMEM_RIGHT_CH);
wetGain = (dryGain * synthState->curReverbVol) >> 7;
s16 wetLeftGain = (s16)(wetGain * (1.0f - panPosition));
s16 wetRightGain = (s16)(wetGain * panPosition);
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, wetLeftGain, dmem, DMEM_WET_LEFT_CH);
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, (wetRightGain ^ 0xFFFF), dmem, DMEM_WET_RIGHT_CH);
// === End matrix surround encoding ===
} else {
// Original behavior: mono surround to both channels
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, dryGain, dmem, DMEM_LEFT_CH);
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, (dryGain ^ 0xFFFF), dmem, DMEM_RIGHT_CH);
wetGain = (dryGain * synthState->curReverbVol) >> 7;
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, wetGain, dmem, DMEM_WET_LEFT_CH);
aMix(cmd++, (numSamplesPerUpdate * (s32)SAMPLE_SIZE) >> 4, (wetGain ^ 0xFFFF), dmem, DMEM_WET_RIGHT_CH);
}
}
aSaveBuffer(cmd++, DMEM_SURROUND_TEMP + (numSamplesPerUpdate * SAMPLE_SIZE),
synthState->synthesisBuffers->surroundEffectState,
sizeof(synthState->synthesisBuffers->surroundEffectState));
decayGain = (sampleState->targetVolLeft + sampleState->targetVolRight) * (1.0f / 0x2000);
if (decayGain > 1.0f) {
decayGain = 1.0f;
}
decayGain = decayGain * gDefaultPanVolume[127 - sampleState->surroundEffectIndex];
synthState->surroundEffectGain = ((decayGain * 0x7FFF) + synthState->surroundEffectGain) / 2;
AudioSynth_DMemMove(cmd++, DMEM_HAAS_TEMP, haasDmem, numSamplesPerUpdate * SAMPLE_SIZE);
return cmd;
}

View file

@ -55,3 +55,4 @@ recomp_get_actor_data = 0x8F0000D0;
recomp_get_actor_spawn_index = 0x8F0000D4;
recomp_set_audio_channels = 0x8F0000D8;
recomp_get_audio_channels = 0x8F0000DC;
recomp_get_enhanced_surround_enabled = 0x8F0000E0;

View file

@ -461,6 +461,7 @@ bool save_sound_config(const std::filesystem::path& path) {
config_json["bgm_volume"] = zelda64::get_bgm_volume();
config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled();
config_json["audio_mode"] = zelda64::get_audio_mode();
config_json["enhanced_surround"] = zelda64::get_enhanced_surround_enabled();
return save_json_with_backups(path, config_json);
}
@ -476,6 +477,7 @@ bool load_sound_config(const std::filesystem::path& path) {
call_if_key_exists(zelda64::set_bgm_volume, config_json, "bgm_volume");
call_if_key_exists(zelda64::set_low_health_beeps_enabled, config_json, "low_health_beeps");
call_if_key_exists(zelda64::set_audio_mode, config_json, "audio_mode");
call_if_key_exists(zelda64::set_enhanced_surround_enabled, config_json, "enhanced_surround");
return true;
}

View file

@ -196,3 +196,7 @@ extern "C" void recomp_set_audio_channels(uint8_t* rdram, recomp_context* ctx) {
extern "C" void recomp_get_audio_channels(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, static_cast<s32>(get_audio_channels()));
}
extern "C" void recomp_get_enhanced_surround_enabled(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, zelda64::get_enhanced_surround_enabled() ? 1 : 0);
}

View file

@ -366,11 +366,13 @@ struct SoundOptionsContext {
std::atomic<int> bgm_volume;
std::atomic<int> low_health_beeps_enabled; // RmlUi doesn't seem to like "true"/"false" strings for setting variants so an int is used here instead.
zelda64::AudioMode audio_mode; // Audio output mode (Stereo, Mono, Headphones, Surround)
std::atomic<int> enhanced_surround_enabled; // Pan-based rear channel separation
void reset() {
bgm_volume = 100;
main_volume = 100;
low_health_beeps_enabled = (int)true;
audio_mode = zelda64::AudioMode::Stereo;
enhanced_surround_enabled = (int)false;
}
SoundOptionsContext() {
reset();
@ -428,7 +430,7 @@ void zelda64::set_audio_mode(zelda64::AudioMode mode) {
if (sound_options_model_handle) {
sound_options_model_handle.DirtyVariable("audio_mode");
}
// Update audio backend - only Surround mode uses 5.1 matrix decoding
// Update audio backend - Surround mode uses 5.1 matrix decoding
set_audio_channels(mode == zelda64::AudioMode::Surround ? audioMatrix51 : audioStereo);
}
@ -436,6 +438,17 @@ zelda64::AudioMode zelda64::get_audio_mode() {
return sound_options_context.audio_mode;
}
void zelda64::set_enhanced_surround_enabled(bool enabled) {
sound_options_context.enhanced_surround_enabled.store((int)enabled);
if (sound_options_model_handle) {
sound_options_model_handle.DirtyVariable("enhanced_surround_enabled");
}
}
bool zelda64::get_enhanced_surround_enabled() {
return (bool)sound_options_context.enhanced_surround_enabled.load();
}
struct DebugContext {
Rml::DataModelHandle model_handle;
std::vector<std::string> area_names;
@ -959,6 +972,7 @@ public:
bind_atomic(constructor, sound_options_model_handle, "main_volume", &sound_options_context.main_volume);
bind_atomic(constructor, sound_options_model_handle, "bgm_volume", &sound_options_context.bgm_volume);
bind_atomic(constructor, sound_options_model_handle, "low_health_beeps_enabled", &sound_options_context.low_health_beeps_enabled);
bind_atomic(constructor, sound_options_model_handle, "enhanced_surround_enabled", &sound_options_context.enhanced_surround_enabled);
// Custom binding for audio mode that calls the setter to update audio channels
constructor.BindFunc("audio_mode",