From d7413601a664ea571baa5389e8de5a6f6a7e5620 Mon Sep 17 00:00:00 2001 From: tortugaveloz Date: Fri, 6 Feb 2026 07:28:54 +0100 Subject: [PATCH] Surround support (#642) * Added surround option. * Added surround effect. * Remove printf. * fix. --------- Co-authored-by: Lywx --- include/defines.h | 4 +-- include/sounds.h | 1 + libultraship | 2 +- src/audio/external.c | 44 +++++++++++++++++++++++++++ src/audio/external.h | 4 ++- src/audio/internal.h | 3 ++ src/audio/playback.c | 10 +++++- src/audio/synthesis.c | 71 +++++++++++++++++++++++++++++++++++++++++++ src/audio/synthesis.h | 1 + src/menu_items.c | 4 +-- src/menus.c | 24 ++++++++------- 11 files changed, 149 insertions(+), 19 deletions(-) diff --git a/include/defines.h b/include/defines.h index 5a784aa3c..0d823a4b5 100644 --- a/include/defines.h +++ b/include/defines.h @@ -287,12 +287,10 @@ enum COLOR_ID { /** * @brief Sound mode options - * Option 2 appears to be unused, as such its probably not - * a valid option */ #define SOUND_STEREO 0 #define SOUND_HEADPHONES 1 -#define SOUND_UNUSED 2 +#define SOUND_SURROUND 2 #define SOUND_MONO 3 #define NUM_SOUND_MODES 4 diff --git a/include/sounds.h b/include/sounds.h index 703359a9a..625003eb3 100644 --- a/include/sounds.h +++ b/include/sounds.h @@ -64,6 +64,7 @@ #define SOUND_MENU_STEREO SOUND_ARG_LOAD(0x49, 0x00, 0x80, 0x24) #define SOUND_MENU_HEADPHONES SOUND_ARG_LOAD(0x49, 0x00, 0x80, 0x25) +#define SOUND_MENU_SURROUND SOUND_ARG_LOAD(0x49, 0x00, 0x80, 0x25) #define SOUND_MENU_MONO SOUND_ARG_LOAD(0x49, 0x00, 0x80, 0x29) /* Staging */ diff --git a/libultraship b/libultraship index 41eeeb247..f40cfd33b 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 41eeeb247ed3f83baaf1ea73b364a57719c14d99 +Subproject commit f40cfd33b8bc6237d635d4ed82838a7e3f785386 diff --git a/src/audio/external.c b/src/audio/external.c index bb1f3de1f..6ba8ee2b0 100644 --- a/src/audio/external.c +++ b/src/audio/external.c @@ -291,6 +291,42 @@ s8 func_800C16E8(f32 arg0, f32 arg1, u8 cameraId) { GLOBAL_ASM("asm/non_matchings/audio/external/func_800C16E8.s") #endif +/** + * Calculate surround effect index from Z position (depth relative to camera). + * Positive Z is behind the camera, negative Z is in front. + * + * Returns: + * 0x00 - 0x3F: Sound is in front of camera (0 = far front, 0x3F = at camera) + * 0x40 - 0x7F: Sound is behind camera (0x40 = at camera, 0x7F = far behind) + */ +u8 get_sound_surround_effect_index(f32 z) { + f32 absZ; + s32 surroundEffectIndex; + f32 maxZ = 100.0f; + + absZ = (z < 0) ? -z : z; + if (absZ > maxZ) { + absZ = maxZ; + } + + // Positive z means behind the camera + if (z > 0.0f) { + // Behind camera - surround effect index 0x3F (at camera) to 0x7F (far behind) + surroundEffectIndex = (s32)((absZ / maxZ) * 64.0f) + 0x3F; + if (surroundEffectIndex > 0x7F) { + surroundEffectIndex = 0x7F; + } + } else { + // In front of camera - surround effect index 0x3F (at camera) to 0x00 (far front) + surroundEffectIndex = 0x3F - (s32)((absZ / maxZ) * 63.0f); + if (surroundEffectIndex < 0) { + surroundEffectIndex = 0; + } + } + + return (u8)surroundEffectIndex; +} + f32 func_800C1934(u8 bank, u8 soundId) { f32 phi_f2; @@ -325,6 +361,14 @@ void func_800C19D0(u8 arg0, u8 arg1, u8 arg2) { sp3B = func_800C15D0(arg0, arg1, arg2); sp34 = func_800C1934(arg0, arg1) * *temp_s0->unk10; sp33 = func_800C16E8(*temp_s0->unk00[0], *temp_s0->unk08, temp_s0->cameraId); + + // Set surround effect index when in surround mode + if (gAudioLibSoundMode == SOUND_MODE_SURROUND) { + struct SequenceChannel* channel = gSequencePlayers[2].channels[arg2]; + if (IS_SEQUENCE_CHANNEL_VALID(channel)) { + channel->surroundEffectIndex = get_sound_surround_effect_index(*temp_s0->unk08); + } + } break; } temp_s0_2 = &D_8018EF18[arg2]; diff --git a/src/audio/external.h b/src/audio/external.h index eddbdaecc..69c0ea724 100644 --- a/src/audio/external.h +++ b/src/audio/external.h @@ -31,8 +31,9 @@ #define SEQUENCE_ARGS(priority, seqId) ((priority << 8) | seqId) #define SOUND_MODE_STEREO 0 -#define SOUND_MODE_MONO 3 #define SOUND_MODE_HEADSET 1 +#define SOUND_MODE_SURROUND 2 +#define SOUND_MODE_MONO 3 #define SEQ_PLAYER_LEVEL 0 // Level background music #define SEQ_PLAYER_ENV 1 // Misc music like the puzzle jingle @@ -204,6 +205,7 @@ void audio_reset_session_eu(OSMesg); f32 func_800C1480(u8, u8); s8 func_800C15D0(u8, u8, u8); s8 func_800C16E8(f32, f32, u8); +u8 get_sound_surround_effect_index(f32 z); f32 func_800C1934(u8, u8); void func_800C19D0(u8, u8, u8); struct Unk_8018EFD8* func_800C1C88(u8, Vec3f, Vec3f, f32*, u8, u32); diff --git a/src/audio/internal.h b/src/audio/internal.h index 9b7fb37b4..1f5f2b490 100644 --- a/src/audio/internal.h +++ b/src/audio/internal.h @@ -338,6 +338,7 @@ struct SequenceChannel { /*0x5C, 0x60*/ struct M64ScriptState scriptState; /*0x78, 0x7C*/ struct AdsrSettings adsr; /*0x80, 0x84*/ struct NotePool notePool; + /* */ u8 surroundEffectIndex; // Surround depth: 0 = front, 0x7F = behind }; // size = 0xC0, 0xC4 in EU, 0xD0 in SH // Also known as a Track, according to debug strings. @@ -460,6 +461,8 @@ struct Note { /*0x84, 0x8C*/ struct VibratoState vibratoState; u8 pad3[8]; /* , 0xB0, 0xB4*/ struct NoteSubEu noteSubEu; + /* */ u8 surroundEffectIndex; // Surround depth: 0 = front, 0x7F = behind + /* */ u8 notePan; // Pan position for surround effect (0=left, 128=center, 255=right) }; // size = 0xC0, known to be 0xC8 on SH // While this struct needs to be size 0xA0, its not clear diff --git a/src/audio/playback.c b/src/audio/playback.c index e4dddff88..08034d1f4 100644 --- a/src/audio/playback.c +++ b/src/audio/playback.c @@ -20,6 +20,9 @@ void note_set_vel_pan_reverb(struct Note* note, f32 velocity, u8 pan, u8 reverbV u16 unkMask = ~0x80; pan &= unkMask; + + // Store pan for surround effect + note->notePan = pan; if (note->noteSubEu.stereoHeadsetEffects && gAudioLibSoundMode == SOUND_MODE_HEADSET) { smallPanIndex = pan >> 3; @@ -35,7 +38,7 @@ void note_set_vel_pan_reverb(struct Note* note, f32 velocity, u8 pan, u8 reverbV volLeft = gHeadsetPanVolume[pan]; volRight = gHeadsetPanVolume[127 - pan]; - } else if (sub->stereoHeadsetEffects && gAudioLibSoundMode == SOUND_MODE_STEREO) { + } else if (sub->stereoHeadsetEffects && (gAudioLibSoundMode == SOUND_MODE_STEREO || gAudioLibSoundMode == SOUND_MODE_SURROUND)) { strongRight = false; strongLeft = false; sub->headsetPanRight = 0; @@ -316,6 +319,11 @@ void process_notes(void) { pan = playbackState->parentLayer->notePan; reverbVol = playbackState->parentLayer->seqChannel->reverbVol; bookOffset = playbackState->parentLayer->seqChannel->bookOffset & 0x7; + + // Transfer surround effect index from channel to note + if (gAudioLibSoundMode == SOUND_MODE_SURROUND) { + note->surroundEffectIndex = playbackState->parentLayer->seqChannel->surroundEffectIndex; + } } frequency *= playbackState->vibratoFreqScale * playbackState->portamentoFreqScale; diff --git a/src/audio/synthesis.c b/src/audio/synthesis.c index bb1f0884d..6f39be9e7 100644 --- a/src/audio/synthesis.c +++ b/src/audio/synthesis.c @@ -7,6 +7,7 @@ #include "audio/load.h" #include "audio/seqplayer.h" #include "audio/internal.h" +#include "audio/external.h" #include "port/Engine.h" #include @@ -617,6 +618,12 @@ Acmd* synthesis_process_note(s32 noteIndex, struct NoteSubEu* noteSubEu, struct // synthesisState->restart = 1; cmd = note_apply_headset_pan_effects(cmd, noteSubEu, synthesisState, inBuf * 2, flags, leftRight); } + + // Apply surround effect for notes with spatial effects enabled when in surround mode + if (noteSubEu->stereoHeadsetEffects && gAudioLibSoundMode == SOUND_MODE_SURROUND) { + cmd = note_apply_surround_effect(cmd, note, inBuf); + } + return cmd; } #else @@ -789,3 +796,67 @@ Acmd* note_apply_headset_pan_effects(Acmd* acmd, struct NoteSubEu* noteSubEu, st return acmd; } + +/** + * Apply surround sound effect using matrix encoding based on depth position. + * Uses surroundEffectIndex (0x00-0x7F) calculated from Z position: + * 0x00-0x3F: Sound in front (0 = far front, 0x3F = at camera) - less rear effect + * 0x40-0x7F: Sound behind (0x40 = at camera, 0x7F = far behind) - more rear effect + * + * This creates a rear channel effect by phase-inverting and mixing based on pan and depth. + */ +Acmd* note_apply_surround_effect(Acmd* cmd, struct Note* note, s32 bufLen) { + s16 dryGain; + s32 wetGain; + f32 depthFactor; + u8 surroundIdx = note->surroundEffectIndex; + struct NoteSubEu* sub = ¬e->noteSubEu; + + // Calculate depth factor: how much rear channel to add + // surroundEffectIndex: 0 = front, 0x3F = at camera, 0x7F = far behind + // We want sounds behind the camera to have stronger rear channel effect + depthFactor = (f32)surroundIdx / 127.0f; + + // Convert u8 pan (0=left, 64=center, 127=right) to float (0.0-1.0) + f32 panPosition = (f32)note->notePan / 127.0f; + + // Calculate base gain from current volume and depth + dryGain = sub->targetVolLeft > sub->targetVolRight ? sub->targetVolLeft : sub->targetVolRight; + dryGain = (s16)(dryGain * depthFactor); // Scale by depth + dryGain = dryGain >> 2; // Scale down for subtle effect + if (dryGain > 0x1800) { + dryGain = 0x1800; // Limit surround intensity + } + + // Skip if gain is too low + if (dryGain < 0x100) { + return cmd; + } + + // Matrix surround encoding: steer surround based on pan + // The idea: add out-of-phase content to create width/depth + // Left-panned sounds get positive left, negative right (spreads to rear left) + // Right-panned sounds get negative left, positive right (spreads to rear right) + s16 leftGain = (s16)(dryGain * (1.0f - panPosition)); + s16 rightGain = (s16)(dryGain * panPosition); + + // Mix surround contribution into channels + // Left channel gets positive surround from left-panned content + aSetBuffer(cmd++, 0, 0, 0, bufLen * 2); + aMix(cmd++, 0, leftGain, DMEM_ADDR_LEFT_CH, DMEM_ADDR_LEFT_CH); + + // Right channel gets phase-inverted surround contribution for matrix encoding + aMix(cmd++, 0, (s16)(rightGain ^ 0xFFFF), DMEM_ADDR_RIGHT_CH, DMEM_ADDR_RIGHT_CH); + + // Apply to wet (reverb) channels for consistent spatialization + wetGain = (dryGain * sub->reverbVol) >> 7; + if (wetGain > 0) { + s16 wetLeftGain = (s16)(wetGain * (1.0f - panPosition)); + s16 wetRightGain = (s16)(wetGain * panPosition); + + aMix(cmd++, 0, wetLeftGain, DMEM_ADDR_WET_LEFT_CH, DMEM_ADDR_WET_LEFT_CH); + aMix(cmd++, 0, (s16)(wetRightGain ^ 0xFFFF), DMEM_ADDR_WET_RIGHT_CH, DMEM_ADDR_WET_RIGHT_CH); + } + + return cmd; +} diff --git a/src/audio/synthesis.h b/src/audio/synthesis.h index 870c6bfe0..35dd4218d 100644 --- a/src/audio/synthesis.h +++ b/src/audio/synthesis.h @@ -114,6 +114,7 @@ Acmd* func_800B86A0(Acmd* cmd, struct NoteSubEu* note, struct NoteSynthesisState u16 inBuf, s32 headsetPanSettings, u32 flags); Acmd* note_apply_headset_pan_effects(Acmd* acmd, struct NoteSubEu* noteSubEu, struct NoteSynthesisState* note, s32 bufLen, s32 flags, s32 leftRight); +Acmd* note_apply_surround_effect(Acmd* cmd, struct Note* note, s32 bufLen); extern struct SynthesisReverb gSynthesisReverbs[4]; diff --git a/src/menu_items.c b/src/menu_items.c index 9838fd31e..6689142d6 100644 --- a/src/menu_items.c +++ b/src/menu_items.c @@ -384,11 +384,11 @@ char* gDebugScreenModeNames[] = { char* gDebugSoundModeNames[] = { "stereo", "head phone", - "xxx", + "surround", "monaural", }; -char* gSoundModeNames[NUM_SOUND_MODES] = { "STEREO", "HEADPHONE", "", "MONO" }; +char* gSoundModeNames[NUM_SOUND_MODES] = { "STEREO", "HEADPHONE", "SURROUND", "MONO" }; char* gWinLoseText[] = { "WINNER!", diff --git a/src/menus.c b/src/menus.c index ab24e419b..d9b84c0a9 100644 --- a/src/menus.c +++ b/src/menus.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -152,7 +153,7 @@ const s8 unref_800F2BDC[4] = { 1, 0, 0, 0 }; // from sScreenModePlayerTable, note the 2 is not set since that's for vertical 2p screen const s8 sScreenModeIdxFromPlayerMode[4] = { 0, 1, 3, 4 }; -const union GameModePack sSoundMenuPack = { { SOUND_STEREO, SOUND_HEADPHONES, SOUND_UNUSED, SOUND_MONO } }; +const union GameModePack sSoundMenuPack = { { SOUND_STEREO, SOUND_HEADPHONES, SOUND_SURROUND, SOUND_MONO } }; /**************************/ @@ -298,9 +299,6 @@ void options_menu_act(struct Controller* controller, u16 controllerIdx) { } else { gSoundMode = SOUND_STEREO; } - if (gSoundMode == SOUND_UNUSED) { - gSoundMode = SOUND_MONO; - } set_sound_mode(); switch (gSoundMode) { case SOUND_STEREO: @@ -309,6 +307,9 @@ void options_menu_act(struct Controller* controller, u16 controllerIdx) { case SOUND_HEADPHONES: play_sound2(SOUND_MENU_HEADPHONES); return; + case SOUND_SURROUND: + play_sound2(SOUND_MENU_SURROUND); + return; case SOUND_MONO: play_sound2(SOUND_MENU_MONO); return; @@ -1146,9 +1147,6 @@ void splash_menu_act(struct Controller* controller, u16 controllerIdx) { case DEBUG_MENU_SOUND_MODE: { if ((btnAndStick & R_JPAD) && (gSoundMode < 3)) { gSoundMode += 1; - if (gSoundMode == SOUND_UNUSED) { - gSoundMode = SOUND_MONO; - } play_sound2(SOUND_MENU_CURSOR_MOVE); set_sound_mode(); gSaveData.main.saveInfo.soundMode = gSoundMode; @@ -1157,9 +1155,6 @@ void splash_menu_act(struct Controller* controller, u16 controllerIdx) { } if ((btnAndStick & L_JPAD) && (gSoundMode > 0)) { gSoundMode -= 1; - if (gSoundMode == SOUND_UNUSED) { - gSoundMode = SOUND_HEADPHONES; - } play_sound2(SOUND_MENU_CURSOR_MOVE); set_sound_mode(); gSaveData.main.saveInfo.soundMode = gSoundMode; @@ -2078,8 +2073,15 @@ void set_sound_mode(void) { union GameModePack pack; pack = sSoundMenuPack; - if ((gSoundMode == SOUND_STEREO) || (gSoundMode == SOUND_HEADPHONES) || (gSoundMode == SOUND_MONO)) { + if ((gSoundMode == SOUND_STEREO) || (gSoundMode == SOUND_HEADPHONES) || + (gSoundMode == SOUND_SURROUND) || (gSoundMode == SOUND_MONO)) { func_800C3448(pack.modes[gSoundMode] | 0xE0000000); + + if (gSoundMode == SOUND_SURROUND) { + SetAudioChannels(audioMatrix51); + } else { + SetAudioChannels(audioStereo); + } } }