From c08118509b84feba002bd9e208f49162b4218556 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:34:03 -0400 Subject: [PATCH] [jak3] Add BRANCH grain, make handler IDs unique (#3950) Two changes that fix some Jak 3 audio bugs. In some areas, sound effects would seem to cut off randomly. This was caused by the sound code reusing IDs, but overlord didn't realize. It would try to kill a sound effect by ID that had already died, and the ID was reassigned to a new sound. To fix this, I made the handle IDs always increment. I also tested starting the ID at large numbers and confirmed that worked. I also added support for the BRANCH grain. This makes the `wastelander-shot` and possibly other sound effects work. It doesn't support all the features, but this is probably enough. Fun fact: the wastelander shot is supposed to have 7 variations, but you always get the same one because they set the sound mask wrong. I added a line you can uncomment if you want to experience the other 6 variations. --------- Co-authored-by: water111 --- game/sound/989snd/blocksound_handler.cpp | 36 ++++++------ game/sound/989snd/blocksound_handler.h | 6 +- game/sound/989snd/handle_allocator.h | 20 ++----- game/sound/989snd/lfo.cpp | 2 +- game/sound/989snd/player.cpp | 2 +- game/sound/989snd/sfxgrain.cpp | 62 ++++++++++++++++---- game/sound/989snd/sound_handler.h | 3 +- goal_src/jak3/levels/wascity/wlander-male.gc | 2 + 8 files changed, 83 insertions(+), 50 deletions(-) diff --git a/game/sound/989snd/blocksound_handler.cpp b/game/sound/989snd/blocksound_handler.cpp index 375bb5e1ed..87234babc6 100644 --- a/game/sound/989snd/blocksound_handler.cpp +++ b/game/sound/989snd/blocksound_handler.cpp @@ -19,7 +19,8 @@ BlockSoundHandler::BlockSoundHandler(SoundBank& bank, u32 sound_id, s32 start_tick) : m_group(sfx.VolGroup), - m_sfx(sfx), + m_sfx(&sfx), + m_orig_sfx(&sfx), m_vm(vm), m_bank(bank), m_sound_id(sound_id), @@ -98,7 +99,7 @@ BlockSoundHandler::BlockSoundHandler(SoundBank& bank, // } m_next_grain = 0; - m_countdown = m_sfx.Grains[0].Delay; + m_countdown = m_sfx->Grains[0].Delay; while (m_countdown <= 0 && !m_done) { DoGrain(); } @@ -209,7 +210,7 @@ void BlockSoundHandler::SetVolPan(s32 vol, s32 pan) { } if (pan == PAN_RESET) { - m_app_pan = m_sfx.Pan; + m_app_pan = m_sfx->Pan; } else if (pan != PAN_DONT_CHANGE) { m_app_pan = pan; } @@ -283,7 +284,7 @@ void BlockSoundHandler::SetPBend(s32 bend) { } void BlockSoundHandler::DoGrain() { - auto& grain = m_sfx.Grains[m_next_grain]; + auto& grain = m_sfx->Grains[m_next_grain]; s32 ret = grain(*this); @@ -296,22 +297,23 @@ void BlockSoundHandler::DoGrain() { } m_next_grain++; - if (m_next_grain >= m_sfx.Grains.size()) { + if (m_next_grain >= m_sfx->Grains.size()) { m_done = true; return; } - m_countdown = m_sfx.Grains[m_next_grain].Delay + ret; + m_countdown = m_sfx->Grains[m_next_grain].Delay + ret; } SoundHandler* BlockSoundHandler::CheckInstanceLimit( const std::map>& handlers, - s32 vol) { - if (!m_sfx.InstanceLimit) { + s32 vol, + bool parent) { + if (!m_sfx->InstanceLimit) { return nullptr; } - if (!m_sfx.Flags.has_instlimit()) { + if (!m_sfx->Flags.has_instlimit()) { return nullptr; } @@ -326,22 +328,20 @@ SoundHandler* BlockSoundHandler::CheckInstanceLimit( } // See if this is playing the same sound - // 989snd checks both an orig_sound and a SH.Sound, but we never change the sound. - // We'd need to revisit this if we eventually support BRANCH grains. - if (&handler->m_sfx == &m_sfx) { + if ((parent && handler->m_orig_sfx == m_sfx) || handler->m_sfx == m_sfx) { inst++; - if (!weakest || // - (m_sfx.Flags.instlimit_vol() && handler->m_app_volume < weakest->m_app_volume) || // - (m_sfx.Flags.instlimit_tick() && handler->m_start_tick < weakest->m_start_tick)) { + if (!weakest || // + (m_sfx->Flags.instlimit_vol() && handler->m_app_volume < weakest->m_app_volume) || // + (m_sfx->Flags.instlimit_tick() && handler->m_start_tick < weakest->m_start_tick)) { weakest = handler; } } } // See if this handler would cause us to exceed the limit - if (m_sfx.InstanceLimit - 1 < inst) { - if (weakest && ((m_sfx.Flags.instlimit_vol() && weakest->m_app_volume < vol) || - m_sfx.Flags.instlimit_tick())) { + if (m_sfx->InstanceLimit - 1 < inst) { + if (weakest && ((m_sfx->Flags.instlimit_vol() && weakest->m_app_volume < vol) || + m_sfx->Flags.instlimit_tick())) { // existing weakest is worst return weakest; } else { diff --git a/game/sound/989snd/blocksound_handler.h b/game/sound/989snd/blocksound_handler.h index 146da0e02c..76e64e832f 100644 --- a/game/sound/989snd/blocksound_handler.h +++ b/game/sound/989snd/blocksound_handler.h @@ -48,7 +48,8 @@ class BlockSoundHandler : public SoundHandler { void UpdatePitch(); SoundHandler* CheckInstanceLimit(const std::map>& handlers, - s32 vol) override; + s32 vol, + bool parent) override; bool m_paused{false}; @@ -59,7 +60,8 @@ class BlockSoundHandler : public SoundHandler { u32 m_grains_to_skip{0}; bool m_skip_grains{false}; - SFXBlock::SFX& m_sfx; + SFXBlock::SFX* m_sfx = nullptr; + SFXBlock::SFX* m_orig_sfx = nullptr; VoiceManager& m_vm; std::list> m_voices; diff --git a/game/sound/989snd/handle_allocator.h b/game/sound/989snd/handle_allocator.h index 885e95dfbd..49fd3dd8cd 100644 --- a/game/sound/989snd/handle_allocator.h +++ b/game/sound/989snd/handle_allocator.h @@ -2,31 +2,19 @@ // SPDX-License-Identifier: ISC #pragma once -#include -#include - #include "common/common_types.h" namespace snd { class IdAllocator { public: - u32 GetId() { - u32 id = 0; - if (mFreeIds.empty()) { - id = mNextId++; - } else { - id = mFreeIds.front(); - mFreeIds.pop(); - } - return id; - } - - void FreeId(u32 id) { mFreeIds.push(id); } + // Use a unique ID each time - the Overlord will sometimes do the wrong thing if handles are + // reused. The real 989snd also includes type + handle index in this. + u32 GetId() { return mNextId++; } + void FreeId(u32) {} private: u32 mNextId{1}; - std::queue mFreeIds; }; } // namespace snd diff --git a/game/sound/989snd/lfo.cpp b/game/sound/989snd/lfo.cpp index 0b6bdfb865..60e983d513 100644 --- a/game/sound/989snd/lfo.cpp +++ b/game/sound/989snd/lfo.cpp @@ -19,7 +19,7 @@ void LFOTracker::Init() { void LFOTracker::CalcDepth() { if (m_target == LFOTarget::VOLUME) { - m_range = (m_handler.m_sfx.Vol * m_depth) >> 10; + m_range = (m_handler.m_sfx->Vol * m_depth) >> 10; } if (m_target == LFOTarget::PAN) { m_range = (180 * m_depth) >> 10; diff --git a/game/sound/989snd/player.cpp b/game/sound/989snd/player.cpp index 361b41bd3c..b5adc12baa 100644 --- a/game/sound/989snd/player.cpp +++ b/game/sound/989snd/player.cpp @@ -142,7 +142,7 @@ u32 Player::PlaySound(BankHandle bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm return 0; } - auto handler_to_stop = handler.value()->CheckInstanceLimit(mHandlers, vol); + auto handler_to_stop = handler.value()->CheckInstanceLimit(mHandlers, vol, true); if (handler_to_stop) { handler_to_stop->Stop(); if (handler_to_stop == handler.value().get()) { diff --git a/game/sound/989snd/sfxgrain.cpp b/game/sound/989snd/sfxgrain.cpp index 64e582963f..12c9716c42 100644 --- a/game/sound/989snd/sfxgrain.cpp +++ b/game/sound/989snd/sfxgrain.cpp @@ -180,7 +180,7 @@ s32 Grain::snd_SFX_GRAIN_TYPE_STOPCHILDSOUND(BlockSoundHandler& handler) { for (auto it = handler.m_children.begin(); it != handler.m_children.end();) { auto* sound = static_cast(it->get()); // TODO VERIFY that this works - if (&sound->m_sfx == &block.Sounds[psp.sound_id]) { + if (sound->m_sfx == &block.Sounds[psp.sound_id]) { it = handler.m_children.erase(it); } else { ++it; @@ -201,6 +201,46 @@ s32 Grain::snd_SFX_GRAIN_TYPE_PLUGIN_MESSAGE(BlockSoundHandler& handler) { } s32 Grain::snd_SFX_GRAIN_TYPE_BRANCH(BlockSoundHandler& handler) { + auto psp = std::get(data); + s32 index = psp.sound_id; + if (index <= -1) { + lg::error("Unsupported BRANCH sound not specified by ID"); + // unsure exactly how this case worked + return 0; + } + + // kill voices + for (auto& p : handler.m_voices) { + auto v = p.lock(); + if (v == nullptr) { + continue; + } + + v->KeyOff(); + v->SetVolumeL(0); + v->SetVolumeR(0); + } + + auto& block = static_cast(handler.m_bank); + auto* new_sfx = &block.Sounds[index]; + if (new_sfx->Flags.has_instlimit()) { + // TODO: not entirely straightforward. + } + + handler.m_sfx = new_sfx; + handler.m_orig_volume = new_sfx->Vol; + handler.m_orig_pan = new_sfx->Pan; + handler.m_cur_volume = (handler.m_app_volume * new_sfx->Vol) >> 10; + handler.m_group = new_sfx->VolGroup; + handler.m_countdown = new_sfx->Grains[0].Delay; + handler.m_next_grain = -1; + handler.m_skip_grains = false; + handler.m_grains_to_play = 0; + handler.m_grains_to_skip = 0; + // the ID case only supports BRANCHing to sfx in the same block + // (this would need to be a pointer, not a reference) + // handler.m_bank = block; + return 0; } @@ -219,7 +259,7 @@ s32 Grain::snd_SFX_GRAIN_TYPE_LOOP_START(BlockSoundHandler& handler) { s32 Grain::snd_SFX_GRAIN_TYPE_LOOP_END(BlockSoundHandler& handler) { bool found = false; for (int i = handler.m_next_grain - 1; i >= 0 && !found; i--) { - if (handler.m_sfx.Grains[i].Type == GrainType::LOOP_START) { + if (handler.m_sfx->Grains[i].Type == GrainType::LOOP_START) { handler.m_next_grain = i - 1; found = true; } @@ -234,8 +274,8 @@ s32 Grain::snd_SFX_GRAIN_TYPE_LOOP_END(BlockSoundHandler& handler) { s32 Grain::snd_SFX_GRAIN_TYPE_LOOP_CONTINUE(BlockSoundHandler& handler) { bool found = false; - for (int i = handler.m_next_grain + 1; i < (int)handler.m_sfx.Grains.size() && !found; i++) { - if (handler.m_sfx.Grains[i].Type == GrainType::LOOP_END) { + for (int i = handler.m_next_grain + 1; i < (int)handler.m_sfx->Grains.size() && !found; i++) { + if (handler.m_sfx->Grains[i].Type == GrainType::LOOP_END) { handler.m_next_grain = i; found = true; } @@ -416,9 +456,9 @@ s32 Grain::snd_SFX_GRAIN_TYPE_GOTO_MARKER(BlockSoundHandler& handler) { auto target_mark = cp.param[0]; bool found = false; - for (int i = 0; i < (int)handler.m_sfx.Grains.size() && !found; i++) { - if (handler.m_sfx.Grains.at(i).Type == GrainType::MARKER) { - auto mcp = std::get(handler.m_sfx.Grains.at(i).data); + for (int i = 0; i < (int)handler.m_sfx->Grains.size() && !found; i++) { + if (handler.m_sfx->Grains.at(i).Type == GrainType::MARKER) { + auto mcp = std::get(handler.m_sfx->Grains.at(i).data); auto mark = mcp.param[0]; if (mark == target_mark) { @@ -444,9 +484,9 @@ s32 Grain::snd_SFX_GRAIN_TYPE_GOTO_RANDOM_MARKER(BlockSoundHandler& handler) { s32 range = upper_bound - lower_bound + 1; s32 target_mark = (rand() % range) + lower_bound; - for (int i = 0; i < (int)handler.m_sfx.Grains.size() && !found; i++) { - if (handler.m_sfx.Grains.at(i).Type == GrainType::MARKER) { - auto mcp = std::get(handler.m_sfx.Grains.at(i).data); + for (int i = 0; i < (int)handler.m_sfx->Grains.size() && !found; i++) { + if (handler.m_sfx->Grains.at(i).Type == GrainType::MARKER) { + auto mcp = std::get(handler.m_sfx->Grains.at(i).data); auto mark = mcp.param[0]; if (mark == target_mark) { @@ -534,7 +574,7 @@ s32 Grain::snd_SFX_GRAIN_TYPE_KILL_VOICES(BlockSoundHandler& handler) { } s32 Grain::snd_SFX_GRAIN_TYPE_ON_STOP_MARKER(BlockSoundHandler& handler) { - handler.m_next_grain = handler.m_sfx.Grains.size() - 1; + handler.m_next_grain = handler.m_sfx->Grains.size() - 1; return 0; } diff --git a/game/sound/989snd/sound_handler.h b/game/sound/989snd/sound_handler.h index c7fe71596a..9affd82913 100644 --- a/game/sound/989snd/sound_handler.h +++ b/game/sound/989snd/sound_handler.h @@ -33,7 +33,8 @@ class SoundHandler { // should be removed. virtual SoundHandler* CheckInstanceLimit( const std::map>& handlers, - s32 vol) { + s32 vol, + bool parent) { return nullptr; } }; diff --git a/goal_src/jak3/levels/wascity/wlander-male.gc b/goal_src/jak3/levels/wascity/wlander-male.gc index cebbc2f539..4f65931a79 100644 --- a/goal_src/jak3/levels/wascity/wlander-male.gc +++ b/goal_src/jak3/levels/wascity/wlander-male.gc @@ -532,6 +532,8 @@ (set! (-> s4-1 volume) 1024) (set! (-> s4-1 pitch-mod) 0) (set! (-> s4-1 reg 0) (the-as uint (-> this shot-sound))) + ;; uncomment the following line to enable the wastelander shot variations. + ;; (logior! (-> s4-1 mask) (sound-mask reg0)) (s5-2 s4-1 (new-sound-id) (-> this root trans)) ) 0