diff --git a/game/sound/989snd/blocksound_handler.cpp b/game/sound/989snd/blocksound_handler.cpp index f1e189d5a07..375bb5e1ed4 100644 --- a/game/sound/989snd/blocksound_handler.cpp +++ b/game/sound/989snd/blocksound_handler.cpp @@ -16,8 +16,14 @@ BlockSoundHandler::BlockSoundHandler(SoundBank& bank, s32 sfx_vol, s32 sfx_pan, SndPlayParams& params, - u32 sound_id) - : m_group(sfx.VolGroup), m_sfx(sfx), m_vm(vm), m_bank(bank), m_sound_id(sound_id) { + u32 sound_id, + s32 start_tick) + : m_group(sfx.VolGroup), + m_sfx(sfx), + m_vm(vm), + m_bank(bank), + m_sound_id(sound_id), + m_start_tick(start_tick) { s32 vol, pan, pitch_mod, pitch_bend; if (sfx_vol == -1) { sfx_vol = sfx.Vol; @@ -298,4 +304,53 @@ void BlockSoundHandler::DoGrain() { m_countdown = m_sfx.Grains[m_next_grain].Delay + ret; } +SoundHandler* BlockSoundHandler::CheckInstanceLimit( + const std::map>& handlers, + s32 vol) { + if (!m_sfx.InstanceLimit) { + return nullptr; + } + + if (!m_sfx.Flags.has_instlimit()) { + return nullptr; + } + + BlockSoundHandler* weakest = nullptr; + int inst = 0; + + for (const auto& [id, handler_ptr] : handlers) { + // Only compare to BlockSoundHandlers + auto* handler = dynamic_cast(handler_ptr.get()); + if (!handler) { + continue; + } + + // 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) { + 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)) { + 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())) { + // existing weakest is worst + return weakest; + } else { + // new sound is weakest + return this; + } + } else { + return nullptr; + } +} + } // namespace snd diff --git a/game/sound/989snd/blocksound_handler.h b/game/sound/989snd/blocksound_handler.h index f1b30a80fae..146da0e02c7 100644 --- a/game/sound/989snd/blocksound_handler.h +++ b/game/sound/989snd/blocksound_handler.h @@ -26,7 +26,8 @@ class BlockSoundHandler : public SoundHandler { s32 sfx_vol, s32 sfx_pan, SndPlayParams& params, - u32 sound_id); + u32 sound_id, + s32 start_tick); ~BlockSoundHandler() override; bool Tick() override; @@ -46,6 +47,9 @@ class BlockSoundHandler : public SoundHandler { void UpdatePitch(); + SoundHandler* CheckInstanceLimit(const std::map>& handlers, + s32 vol) override; + bool m_paused{false}; u8 m_group{0}; @@ -90,5 +94,6 @@ class BlockSoundHandler : public SoundHandler { u32 m_next_grain{0}; u32 m_sound_id{0}; + s32 m_start_tick{0}; }; } // namespace snd diff --git a/game/sound/989snd/musicbank.cpp b/game/sound/989snd/musicbank.cpp index bcef3f4f9fd..1abe67125c5 100644 --- a/game/sound/989snd/musicbank.cpp +++ b/game/sound/989snd/musicbank.cpp @@ -7,12 +7,8 @@ namespace snd { -std::optional> MusicBank::MakeHandler(VoiceManager& vm, - u32 sound_id, - s32 vol, - s32 pan, - s32 pm, - s32 pb) { +std::optional> +MusicBank::MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb, s32 tick) { auto& sound = Sounds[sound_id]; // FIXME: global midi list @@ -42,7 +38,8 @@ std::optional> MusicBank::MakeHandler(VoiceManager u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) { + SndPlayParams& params, + s32 tick) { return std::nullopt; } diff --git a/game/sound/989snd/musicbank.h b/game/sound/989snd/musicbank.h index 76a3b75d387..c23f8271e89 100644 --- a/game/sound/989snd/musicbank.h +++ b/game/sound/989snd/musicbank.h @@ -66,17 +66,14 @@ class MusicBank : public SoundBank { std::span samples, std::span midi_data); - std::optional> MakeHandler(VoiceManager& vm, - u32 sound_id, - s32 vol, - s32 pan, - s32 pm, - s32 pb) override; + std::optional> + MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb, s32 tick) override; std::optional> MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) override; + SndPlayParams& params, + s32 tick) override; }; } // namespace snd diff --git a/game/sound/989snd/player.cpp b/game/sound/989snd/player.cpp index da8c7e6b1fb..6655c47a921 100644 --- a/game/sound/989snd/player.cpp +++ b/game/sound/989snd/player.cpp @@ -137,11 +137,19 @@ u32 Player::PlaySound(BankHandle bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm return 0; } - auto handler = bank->MakeHandler(mVmanager, sound_id, vol, pan, pm, pb); + auto handler = bank->MakeHandler(mVmanager, sound_id, vol, pan, pm, pb, GetTick()); if (!handler.has_value()) { return 0; } + auto handler_to_stop = handler.value()->CheckInstanceLimit(mHandlers, vol); + if (handler_to_stop) { + handler_to_stop->Stop(); + if (handler_to_stop == handler.value().get()) { + return 0; + } + } + u32 handle = mHandleAllocator.GetId(); mHandlers.emplace(handle, std::move(handler.value())); // fmt::print("play_sound {}:{} - {}\n", bank_id, sound_id, handle); @@ -149,6 +157,16 @@ u32 Player::PlaySound(BankHandle bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm return handle; } +void Player::DebugPrintAllSoundsInBank(BankHandle bank_id) { + std::scoped_lock lock(mTickLock); + auto* bank = mLoader.GetBankByHandle(bank_id); + if (!bank) { + lg::error("DebugPrintAllSoundsInBank: invalid bank"); + return; + } + bank->DebugPrintAllSounds(); +} + u32 Player::PlaySoundByName(BankHandle bank_id, char* bank_name, char* sound_name, diff --git a/game/sound/989snd/player.h b/game/sound/989snd/player.h index f03498c20a5..41e67717904 100644 --- a/game/sound/989snd/player.h +++ b/game/sound/989snd/player.h @@ -2,10 +2,10 @@ // SPDX-License-Identifier: ISC #pragma once +#include #include #include #include -#include #include #include "ame_handler.h" @@ -42,6 +42,7 @@ class Player { s32 pan, s32 pm, s32 pb); + void DebugPrintAllSoundsInBank(BankHandle bank); void SetSoundReg(u32 sound_id, u8 reg, u8 value); void SetGlobalExcite(u8 value) { GlobalExcite = value; }; bool SoundStillActive(u32 sound_id); @@ -71,7 +72,7 @@ class Player { private: std::recursive_mutex mTickLock; // TODO does not need to recursive with some light restructuring IdAllocator mHandleAllocator; - std::unordered_map> mHandlers; + std::map> mHandlers; void Tick(s16Output* stream, int samples); diff --git a/game/sound/989snd/sfxblock.cpp b/game/sound/989snd/sfxblock.cpp index 9fc9920ec17..07387c0f375 100644 --- a/game/sound/989snd/sfxblock.cpp +++ b/game/sound/989snd/sfxblock.cpp @@ -5,20 +5,24 @@ #include "common/log/log.h" +#include "third-party/magic_enum.hpp" + namespace snd { std::optional> SFXBlock::MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) { + SndPlayParams& params, + s32 current_tick) { auto& SFX = Sounds[sound_id]; if (SFX.Grains.empty()) { return std::nullopt; } - auto handler = std::make_unique(*this, SFX, vm, vol, pan, params, sound_id); + auto handler = + std::make_unique(*this, SFX, vm, vol, pan, params, sound_id, current_tick); return handler; } @@ -31,4 +35,22 @@ std::optional SFXBlock::GetSoundByName(const char* name) { return std::nullopt; } +void SFXBlock::DebugPrintAllSounds() { + for (const auto& [name, id] : Names) { + printf("%s : %d\n", name.c_str(), id); + const auto& sound = Sounds.at(id); + printf(" Vol: %d\n", sound.Vol); + printf(" VolGroup: %d\n", sound.VolGroup); + printf(" Pan: %d\n", sound.Pan); + printf(" InstanceLimit: %d\n", sound.InstanceLimit); + printf(" Flags: 0x%x\n", sound.Flags.flags); + printf(" User: 0x%x 0x%x 0x%x 0x%x\n", sound.UserData.data[0], sound.UserData.data[1], + sound.UserData.data[2], sound.UserData.data[3]); + printf(" Grains\n"); + for (const auto& grain : sound.Grains) { + fmt::print(" {} ({})\n", magic_enum::enum_name(grain.Type), (int)grain.Type); + } + } +} + } // namespace snd diff --git a/game/sound/989snd/sfxblock.h b/game/sound/989snd/sfxblock.h index e3a91097c57..4e779112e70 100644 --- a/game/sound/989snd/sfxblock.h +++ b/game/sound/989snd/sfxblock.h @@ -52,13 +52,15 @@ class SFXBlock : public SoundBank { u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) override; + SndPlayParams& params, + s32 current_tick) override; std::optional GetName() override { return Name; }; std::optional GetSoundByName(const char* name) override; std::optional GetSoundUserData(u32 sound_id) override { return &Sounds.at(sound_id).UserData; }; + void DebugPrintAllSounds() override; }; } // namespace snd diff --git a/game/sound/989snd/sfxgrain.cpp b/game/sound/989snd/sfxgrain.cpp index 835e677ff5f..6bf63735fe6 100644 --- a/game/sound/989snd/sfxgrain.cpp +++ b/game/sound/989snd/sfxgrain.cpp @@ -158,7 +158,8 @@ s32 Grain::snd_SFX_GRAIN_TYPE_STARTCHILDSOUND(BlockSoundHandler& handler) { s32 index = psp.sound_id; if (index >= 0) { - auto child_handler = block.MakeHandler(handler.m_vm, index, vol, pan, params); + auto child_handler = + block.MakeHandler(handler.m_vm, index, vol, pan, params, handler.m_start_tick); if (child_handler.has_value()) { handler.m_children.emplace_front(std::move(child_handler.value())); } @@ -257,7 +258,7 @@ s32 Grain::snd_SFX_GRAIN_TYPE_RAND_PLAY(BlockSoundHandler& handler) { auto cp = std::get(data); auto options = cp.param[0]; auto count = cp.param[1]; - auto previous = cp.param[2]; + auto& previous = cp.param[2]; int rnd = rand() % options; if (rnd == previous) { diff --git a/game/sound/989snd/sndplay.cpp b/game/sound/989snd/sndplay.cpp index cf10838738e..ba934983f56 100644 --- a/game/sound/989snd/sndplay.cpp +++ b/game/sound/989snd/sndplay.cpp @@ -28,6 +28,7 @@ int main(int argc, char* argv[]) { printf("commands:\n"); printf(" play [id]\n"); printf(" stop\n"); + printf(" dump-info\n"); while (true) { printf("> "); @@ -77,6 +78,10 @@ int main(int argc, char* argv[]) { printf("stopping all sounds\n"); player.StopAllSounds(); } + + if (parts[0] == "dump-info") { + player.DebugPrintAllSoundsInBank(bankid); + } } return 0; diff --git a/game/sound/989snd/sound_handler.h b/game/sound/989snd/sound_handler.h index 85ef1821343..c7fe71596ae 100644 --- a/game/sound/989snd/sound_handler.h +++ b/game/sound/989snd/sound_handler.h @@ -2,6 +2,9 @@ // SPDX-License-Identifier: ISC #pragma once +#include +#include + #include "common/common_types.h" namespace snd { @@ -25,5 +28,13 @@ class SoundHandler { virtual void SetPBend(s32 /*mod*/){}; virtual void SetRegister(u8 /*reg*/, u8 /*value*/) {} virtual u32 SoundID() const { return -1; } + + // Check if this handler violates an instance limit. If so, return pointer to the sound that + // should be removed. + virtual SoundHandler* CheckInstanceLimit( + const std::map>& handlers, + s32 vol) { + return nullptr; + } }; } // namespace snd diff --git a/game/sound/989snd/soundbank.h b/game/sound/989snd/soundbank.h index 3b20e504464..dac216b9348 100644 --- a/game/sound/989snd/soundbank.h +++ b/game/sound/989snd/soundbank.h @@ -54,32 +54,31 @@ class SoundBank { u32 BankID; s8 BankNum; - virtual std::optional> MakeHandler(VoiceManager& vm, - u32 sound_id, - s32 vol, - s32 pan, - s32 pm, - s32 pb) { + virtual std::optional> + MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb, s32 current_tick) { SndPlayParams params{}; params.vol = vol; params.pan = pan; params.pitch_mod = pm; params.pitch_bend = pb; - return MakeHandler(vm, sound_id, -1, -1, params); + return MakeHandler(vm, sound_id, -1, -1, params, current_tick); }; virtual std::optional> MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) = 0; + SndPlayParams& params, + s32 current_tick) = 0; virtual std::optional GetName() { return std::nullopt; }; virtual std::optional GetSoundByName(const char* /*name*/) { return std::nullopt; }; virtual std::optional GetSoundUserData(u32 /*sound_id*/) { return std::nullopt; }; + + virtual void DebugPrintAllSounds() {} }; } // namespace snd