From 5b999293942332c9be9e86eb6912bafcc0e8553e Mon Sep 17 00:00:00 2001 From: Ziemas Date: Sat, 3 Dec 2022 00:08:44 +0100 Subject: [PATCH] 989snd: Support version >= 2 sound effects (#1991) Supports most of the grain types now while maintaining compatibility with the old stuff (at least the subset of things jak1 uses) Would benefit from some testing in Jak 1 to make sure I didn't break anything. Sorry the git history is a mess, I'll do something about it later. --- game/overlord/fake_iso.cpp | 64 +- game/overlord/sbank.cpp | 89 +- game/overlord/sbank.h | 11 + game/overlord/soundcommon.cpp | 13 + game/overlord/soundcommon.h | 1 + game/overlord/srpc.cpp | 472 +++++++- game/overlord/srpc.h | 80 +- game/overlord/ssound.cpp | 128 +- game/overlord/ssound.h | 4 +- game/sound/989snd/ame_handler.cpp | 7 +- game/sound/989snd/ame_handler.h | 6 +- game/sound/989snd/blocksound_handler.cpp | 120 +- game/sound/989snd/blocksound_handler.h | 123 +- game/sound/989snd/lfo.cpp | 140 +++ game/sound/989snd/lfo.h | 43 + game/sound/989snd/lfo_sine.c.inc | 172 +++ game/sound/989snd/loader.cpp | 118 +- game/sound/989snd/loader.h | 7 +- game/sound/989snd/locator.h | 2 +- game/sound/989snd/midi_handler.cpp | 55 +- game/sound/989snd/midi_handler.h | 10 +- game/sound/989snd/musicbank.cpp | 41 +- game/sound/989snd/musicbank.h | 12 +- game/sound/989snd/player.cpp | 86 +- game/sound/989snd/player.h | 15 +- game/sound/989snd/sfxblock.cpp | 35 +- game/sound/989snd/sfxblock.h | 79 +- game/sound/989snd/sfxblock2.cpp | 103 ++ game/sound/989snd/sfxblock2.h | 115 ++ game/sound/989snd/sfxgrain.cpp | 614 ++++++++++ game/sound/989snd/sfxgrain.h | 529 +++++++++ game/sound/989snd/sndplay.cpp | 68 +- game/sound/989snd/sound_handler.h | 6 +- game/sound/989snd/soundbank.h | 37 +- game/sound/989snd/util.cpp | 8 +- game/sound/989snd/vagvoice.cpp | 4 +- game/sound/989snd/vagvoice.h | 8 +- game/sound/CMakeLists.txt | 5 +- game/sound/sndshim.cpp | 92 +- game/sound/sndshim.h | 82 +- goal_src/jak2/engine/game/main.gc | 2 +- goal_src/jak2/engine/sound/gsound.gc | 4 - goal_src/jak2/game.gp | 171 +++ third-party/magic_enum.hpp | 1387 ++++++++++++++++++++++ 44 files changed, 4689 insertions(+), 479 deletions(-) create mode 100644 game/sound/989snd/lfo.cpp create mode 100644 game/sound/989snd/lfo.h create mode 100644 game/sound/989snd/lfo_sine.c.inc create mode 100644 game/sound/989snd/sfxblock2.cpp create mode 100644 game/sound/989snd/sfxblock2.h create mode 100644 game/sound/989snd/sfxgrain.cpp create mode 100644 game/sound/989snd/sfxgrain.h create mode 100644 third-party/magic_enum.hpp diff --git a/game/overlord/fake_iso.cpp b/game/overlord/fake_iso.cpp index 288724d79..495aa4807 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -64,6 +64,10 @@ static uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len); static uint32_t FS_SyncRead(); static uint32_t FS_LoadSoundBank(char*, void*); static uint32_t FS_LoadMusic(char*, void*); + +static uint32_t FS_LoadSoundBank2(char*, void*); +static uint32_t FS_LoadMusic2(char*, void*); + static void FS_PollDrive(); static void LoadMusicTweaks(); @@ -84,10 +88,16 @@ void fake_iso_init_globals() { fake_iso.close = FS_Close; fake_iso.begin_read = FS_BeginRead; fake_iso.sync_read = FS_SyncRead; - fake_iso.load_sound_bank = FS_LoadSoundBank; - fake_iso.load_music = FS_LoadMusic; fake_iso.poll_drive = FS_PollDrive; + if (g_game_version == GameVersion::Jak1) { + fake_iso.load_sound_bank = FS_LoadSoundBank; + fake_iso.load_music = FS_LoadMusic; + } else { + fake_iso.load_sound_bank = FS_LoadSoundBank2; + fake_iso.load_music = FS_LoadMusic2; + } + sReadInfo = nullptr; } @@ -361,6 +371,56 @@ uint32_t FS_LoadSoundBank(char* name, void* buffer) { snd_ResolveBankXREFS(); PrintBankInfo(bank); bank->bank_handle = handle; + + return 0; +} + +uint32_t FS_LoadMusic2(char* name, void* buffer) { + FileRecord* file = nullptr; + u32* bank_handle = (u32*)buffer; + char namebuf[16]; + char isoname[16]; + u32 handle; + + strncpy(namebuf, name, 12); + namebuf[8] = 0; + strcat(namebuf, ".mus"); + + MakeISOName(isoname, namebuf); + + file = FS_FindIN(isoname); + if (!file) { + return 6; + } + + handle = snd_BankLoadEx(get_file_path(file), 0, 0xcfcc0, 0x61a80); + snd_ResolveBankXREFS(); + *bank_handle = handle; + + return 0; +} + +uint32_t FS_LoadSoundBank2(char* name, void* buffer) { + SoundBank* bank = (SoundBank*)buffer; + FileRecord* file = nullptr; + char namebuf[16]; + char isoname[16]; + u32 handle; + + strncpy(namebuf, name, 12); + namebuf[8] = 0; + strcat(namebuf, ".sbk"); + + MakeISOName(isoname, namebuf); + file = FS_FindIN(isoname); + if (!file) { + return 6; + } + + handle = snd_BankLoadEx(get_file_path(file), 0, bank->spu_loc, bank->spu_size); + snd_ResolveBankXREFS(); + bank->bank_handle = handle; + return 0; } diff --git a/game/overlord/sbank.cpp b/game/overlord/sbank.cpp index 54bbbf027..f3b3e6fb1 100644 --- a/game/overlord/sbank.cpp +++ b/game/overlord/sbank.cpp @@ -4,25 +4,67 @@ #include "soundcommon.h" -constexpr int N_BANKS = 3; -SoundBank* gBanks[N_BANKS]; +#include "common/log/log.h" + +#include "game/runtime.h" + +static constexpr int N_BANKS = 6; + SoundBank gCommonBank; -SoundBank gLevelBank[2]; +SoundBank gGunBank; +SoundBank gBoardBank; +SoundBank gLevelBanks[3]; + +SoundBank* gBanks[N_BANKS] = {&gCommonBank, &gGunBank, &gBoardBank, + &gLevelBanks[0], &gLevelBanks[1], &gLevelBanks[2]}; void sbank_init_globals() { - gBanks[0] = &gCommonBank; - gBanks[1] = &gLevelBank[0]; - gBanks[2] = &gLevelBank[1]; memset((void*)&gCommonBank, 0, sizeof(gCommonBank)); - memset((void*)&gLevelBank, 0, sizeof(gLevelBank)); + memset((void*)&gGunBank, 0, sizeof(gGunBank)); + memset((void*)&gBoardBank, 0, sizeof(gBoardBank)); + memset((void*)&gLevelBanks, 0, sizeof(gLevelBanks)); } void InitBanks() { for (auto& gBank : gBanks) { gBank->bank_handle = 0; gBank->sound_count = 0; + + gBank->in_use = false; + gBank->unk4 = 0; + + // paper over bank allocation differences + if (g_game_version == GameVersion::Jak1) + gBank->in_use = 1; + strcpy(gBank->name, ""); } + + if (g_game_version == GameVersion::Jak2) { + strncpy(gBanks[0]->name, "common", 16); + gBanks[0]->spu_loc = 0x20000; + gBanks[0]->spu_size = 0xAFCC0; + + strncpy(gBanks[1]->name, "gun", 16); + gBanks[0]->spu_loc = 0x131740; + gBanks[0]->spu_size = 0; + + strncpy(gBanks[2]->name, "board", 16); + gBanks[0]->spu_loc = 0x131740; + gBanks[0]->spu_size = 0; + + strncpy(gBanks[3]->name, "level0", 16); + gBanks[0]->spu_loc = 0x131740; + gBanks[0]->spu_size = 0x42800; + + strncpy(gBanks[4]->name, "level1", 16); + gBanks[0]->spu_loc = 0x173f40; + gBanks[0]->spu_size = 0x42800; + + strncpy(gBanks[5]->name, "level2", 16); + gBanks[0]->spu_loc = 0x1B6740; + gBanks[0]->spu_size = 0x42800; + } } SoundBank* AllocateBank() { @@ -50,25 +92,34 @@ SoundBank* AllocateBank() { return gBanks[idx]; } +SoundBank* AllocateBankName(const char* name) { + if ((!strncmp(name, "common", 16) || !strncmp(name, "commonj", 16)) && !gBanks[0]->in_use) { + return gBanks[0]; + } + + for (int i = 3; i < N_BANKS; i++) { + if (!gBanks[i]->in_use) { + gBanks[i]->bank_handle = 0; + gBanks[i]->unk4 = 0; + return gBanks[i]; + } + } + + return nullptr; +} + s32 LookupSoundIndex(const char* name, SoundBank** bank_out) { - int idx = 0; - while (true) { - if (idx > N_BANKS - 1) { - return -1; + for (auto bank : gBanks) { + if (!bank->bank_handle) { + continue; } - auto& bank = gBanks[idx]; - if (bank->bank_handle == 0) { - break; - } - - for (int i = 0; i < (int)bank->sound_count; i++) { + for (int i = 0; i < bank->sound_count; i++) { if (memcmp(bank->sound[i].name, name, 16) == 0) { *bank_out = bank; return i; } } - idx++; } return -1; @@ -84,7 +135,7 @@ SoundBank* LookupBank(const char* name) { auto& bank = gBanks[idx]; // they had some weird stuff here that took advantage of the fact that this region was // 16-byte aligned, so it probably wasn't a memcmp, but this is easier. - if (memcmp(bank->name, name, 16) == 0) { + if ((memcmp(bank->name, name, 16) == 0) && bank->in_use) { return bank; } idx--; diff --git a/game/overlord/sbank.h b/game/overlord/sbank.h index dfc9fbd89..9d40c3c57 100644 --- a/game/overlord/sbank.h +++ b/game/overlord/sbank.h @@ -11,10 +11,20 @@ struct SoundBank { char name[16]; u32 bank_handle; u32 sound_count; + union { SoundRecord sound[1]; + // Needs to fit the biggest bank (common.sbk) u8 buffer[10 * 2048]; + + // Jak 2 additions go here + struct { + u32 spu_loc; + u32 spu_size; + u32 unk4; + bool in_use; + }; }; }; @@ -24,5 +34,6 @@ void InitBanks(); void ReloadBankInfo(); SoundBank* AllocateBank(); +SoundBank* AllocateBankName(const char* name); s32 LookupSoundIndex(const char* name, SoundBank** bank_out); SoundBank* LookupBank(const char* name); diff --git a/game/overlord/soundcommon.cpp b/game/overlord/soundcommon.cpp index 14dc23418..b6c6a2faa 100644 --- a/game/overlord/soundcommon.cpp +++ b/game/overlord/soundcommon.cpp @@ -1,6 +1,8 @@ #include "soundcommon.h" +#include #include +#include #include #include "common/util/Assert.h" @@ -16,6 +18,17 @@ void ReadBankSoundInfo(SoundBank* bank, SoundBank* unk, s32 unk2) { ASSERT(false); } +// I'm not bored enough to reimplement their strcpy +// Only for use with 16 character sound names! +void strcpy_toupper(char* dest, const char* source) { + // clear the dest string + memset(dest, 0, 16); + std::string string(source); + std::transform(string.begin(), string.end(), string.begin(), ::toupper); + std::replace(string.begin(), string.end(), '-', '_'); + string.copy(dest, 16); +} + void PrintBankInfo(SoundBank* bank) { // we dont need this and it spams the console too much return; diff --git a/game/overlord/soundcommon.h b/game/overlord/soundcommon.h index ba9ef4949..affe16630 100644 --- a/game/overlord/soundcommon.h +++ b/game/overlord/soundcommon.h @@ -6,6 +6,7 @@ #include "game/overlord/sbank.h" +void strcpy_toupper(char* dest, const char* source); void PrintBankInfo(SoundBank* buffer); void ReadBankSoundInfo(SoundBank* bank, SoundBank* unk, s32 unk2); diff --git a/game/overlord/srpc.cpp b/game/overlord/srpc.cpp index 76dc507f1..5371fa308 100644 --- a/game/overlord/srpc.cpp +++ b/game/overlord/srpc.cpp @@ -8,6 +8,7 @@ #include "ramdisk.h" #include "sbank.h" +#include "common/log/log.h" #include "common/util/Assert.h" #include "common/versions.h" @@ -15,11 +16,13 @@ #include "game/common/loader_rpc_types.h" #include "game/common/player_rpc_types.h" #include "game/graphics/gfx.h" +#include "game/overlord/soundcommon.h" #include "game/runtime.h" #include "game/sce/iop.h" #include "game/sound/sndshim.h" #include "third-party/fmt/core.h" +#include "third-party/magic_enum.hpp" using namespace iop; @@ -35,6 +38,7 @@ s32 gMusicTweak = 0x80; s32 gMusicPause = 0; u32 gFreeMem = 0; u32 gFrameNum = 0; +u8 gFPS = 60; // added u32 gMusicFadeHack = 0; @@ -57,6 +61,8 @@ void srpc_init_globals() { } void* RPC_Player(unsigned int fno, void* data, int size); +void* RPC_Player2(unsigned int fno, void* data, int size); +PerGameVersion RPC_Player_Func = {RPC_Player, RPC_Player2}; u32 Thread_Player() { sceSifQueueData dq; @@ -66,14 +72,17 @@ u32 Thread_Player() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, PLAYER_RPC_ID[g_game_version], RPC_Player, gPlayerBuf, nullptr, nullptr, - &dq); + sceSifRegisterRpc(&serve, PLAYER_RPC_ID[g_game_version], RPC_Player_Func[g_game_version], + gPlayerBuf, nullptr, nullptr, &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; } void* RPC_Loader(unsigned int fno, void* data, int size); +void* RPC_Loader2(unsigned int fno, void* data, int size); + +PerGameVersion RPC_Loader_Func = {RPC_Loader, RPC_Loader2}; u32 Thread_Loader() { sceSifQueueData dq; @@ -83,18 +92,14 @@ u32 Thread_Loader() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, LOADER_RPC_ID[g_game_version], RPC_Loader, gLoaderBuf, nullptr, nullptr, - &dq); + sceSifRegisterRpc(&serve, LOADER_RPC_ID[g_game_version], RPC_Loader_Func[g_game_version], + gLoaderBuf, nullptr, nullptr, &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; } void* RPC_Player(unsigned int /*fno*/, void* data, int size) { - if (g_game_version == GameVersion::Jak2) { - printf("RPC_Player skip %d\n", (int)((SoundRpcCommand*)data)->command); - return nullptr; - } if (gSoundEnable) { gFreeMem = QueryTotalFreeMemSize(); if (!PollSema(gSema)) { @@ -124,8 +129,8 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { int n_messages = size / SRPC_MESSAGE_SIZE; SoundRpcCommand* cmd = (SoundRpcCommand*)(data); while (n_messages > 0) { - switch (cmd->command) { - case SoundCommand::PLAY: { + switch (cmd->j1command) { + case Jak1SoundCommand::PLAY: { if (cmd->play.sound_id == 0) { break; } @@ -217,7 +222,7 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { sound->id = cmd->play.sound_id; } } break; - case SoundCommand::PAUSE_SOUND: { + case Jak1SoundCommand::PAUSE_SOUND: { Sound* sound = LookupSound(cmd->sound_id.sound_id); if (sound != nullptr) { snd_PauseSound(sound->sound_handle); @@ -225,7 +230,7 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { PauseVAGStream(); } } break; - case SoundCommand::STOP_SOUND: { + case Jak1SoundCommand::STOP_SOUND: { Sound* sound = LookupSound(cmd->sound_id.sound_id); if (sound != nullptr) { snd_StopSound(sound->sound_handle); @@ -233,7 +238,7 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { StopVAGStream(nullptr, 0); } } break; - case SoundCommand::CONTINUE_SOUND: { + case Jak1SoundCommand::CONTINUE_SOUND: { Sound* sound = LookupSound(cmd->sound_id.sound_id); if (sound != nullptr) { snd_ContinueSound(sound->sound_handle); @@ -241,7 +246,7 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { UnpauseVAGStream(); } } break; - case SoundCommand::SET_PARAM: { + case Jak1SoundCommand::SET_PARAM: { Sound* sound = LookupSound(cmd->sound_id.sound_id); u32 mask = cmd->param.parms.mask; if (sound != nullptr) { @@ -282,7 +287,7 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { SetVAGStreamVolume(cmd->param.parms.volume); } } break; - case SoundCommand::SET_MASTER_VOLUME: { + case Jak1SoundCommand::SET_MASTER_VOLUME: { u32 group = cmd->master_volume.group.group; for (int i = 0; i < 32; i++) { if (((group >> i) & 1) != 0) { @@ -296,7 +301,7 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { } } } break; - case SoundCommand::PAUSE_GROUP: { + case Jak1SoundCommand::PAUSE_GROUP: { snd_PauseAllSoundsInGroup(cmd->group.group); if ((cmd->group.group & 4) != 0) { PauseVAGStream(); @@ -305,14 +310,14 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { gMusicPause = 1; } } break; - case SoundCommand::STOP_GROUP: { + case Jak1SoundCommand::STOP_GROUP: { u8 group = cmd->group.group; KillSoundsInGroup(group); if ((group & 4) != 0) { StopVAGStream(nullptr, 0); } } break; - case SoundCommand::CONTINUE_GROUP: { + case Jak1SoundCommand::CONTINUE_GROUP: { snd_ContinueAllSoundsInGroup(cmd->group.group); if (cmd->group.group & 4) { UnpauseVAGStream(); @@ -322,10 +327,10 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { gMusicPause = 0; } } break; - case SoundCommand::SET_FALLOFF_CURVE: { + case Jak1SoundCommand::SET_FALLOFF_CURVE: { SetCurve(cmd->fallof_curve.curve, cmd->fallof_curve.falloff, cmd->fallof_curve.ease); } break; - case SoundCommand::SET_SOUND_FALLOFF: { + case Jak1SoundCommand::SET_SOUND_FALLOFF: { SoundBank* bank; s32 idx = LookupSoundIndex(cmd->fallof.name, &bank); if (idx >= 0) { @@ -333,20 +338,21 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { (cmd->fallof.curve << 28) | (cmd->fallof.max << 14) | cmd->fallof.min; } } break; - case SoundCommand::SET_FLAVA: { + case Jak1SoundCommand::SET_FLAVA: { gFlava = cmd->flava.flava; } break; - case SoundCommand::SET_EAR_TRANS: { - SetEarTrans(&cmd->ear_trans.ear_trans, &cmd->ear_trans.cam_trans, - cmd->ear_trans.cam_angle); + case Jak1SoundCommand::SET_EAR_TRANS: { + SetEarTrans(&cmd->ear_trans.ear_trans, &cmd->ear_trans.ear_trans, + &cmd->ear_trans.cam_trans, cmd->ear_trans.cam_angle); } break; - case SoundCommand::SHUTDOWN: { + case Jak1SoundCommand::SHUTDOWN: { gSoundEnable = 0; snd_StopSoundSystem(); // TODO ShutdownFilingSystem(); } break; default: { - ASSERT_MSG(false, fmt::format("Unhandled RPC Player command {}", (int)cmd->command)); + ASSERT_MSG(false, fmt::format("Unhandled RPC Player command {}", + magic_enum::enum_name(cmd->j1command))); } break; } n_messages--; @@ -356,11 +362,300 @@ void* RPC_Player(unsigned int /*fno*/, void* data, int size) { return nullptr; } -void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { - if (g_game_version == GameVersion::Jak2) { - printf("RPC_Loader skip %d\n", (int)((SoundRpcCommand*)data)->command); +void* RPC_Player2(unsigned int /*fno*/, void* data, int size) { + if (!gSoundEnable) { return nullptr; } + + gFreeMem = QueryTotalFreeMemSize(); + if (!PollSema(gSema)) { + if (gMusic) { + if (!gMusicPause && !LookupSound(666)) { + Sound* music = AllocateSound(); + if (music != nullptr) { + gMusicFade = 0; + gMusicFadeDir = 1; + SetMusicVol(); + music->sound_handle = snd_PlaySoundVolPanPMPB(gMusic, 0, 0x400, -1, 0, 0); + music->id = 666; + music->is_music = 1; + } + } + } + + SignalSema(gSema); + } + + SetMusicVol(); + Sound* music = LookupSound(666); + if (music != nullptr) { + snd_SetSoundVolPan(music->sound_handle, 0x7FFFFFFF, 0); + } + + int n_messages = size / SRPC_MESSAGE_SIZE; + SoundRpcCommand* cmd = (SoundRpcCommand*)(data); + if (!gSoundEnable) { + return nullptr; + } + + while (n_messages > 0) { + switch (cmd->j2command) { + case Jak2SoundCommand::play: { + if (!cmd->play.sound_id) { + break; + } + + auto sound = LookupSound(cmd->play.sound_id); + if (sound != nullptr) { + // update + sound->params = cmd->play.parms; + sound->is_music = false; + SFXUserData data{}; + s32 found = snd_GetSoundUserData(0, nullptr, -1, sound->name, &data); + if ((sound->params.mask & 0x40) == 0) { + s16 fo_min = 5; + if (found && data.data[0]) + fo_min = data.data[0]; + sound->params.fo_min = fo_min; + } + if ((sound->params.mask & 0x80) == 0) { + s16 fo_max = 30; + if (found && data.data[1]) + fo_max = data.data[1]; + sound->params.fo_max = fo_max; + } + if ((sound->params.mask & 0x100) == 0) { + s16 fo_curve = 2; + if (found && data.data[2]) + fo_curve = data.data[2]; + sound->params.fo_curve = fo_curve; + } + UpdateVolume(sound); + snd_SetSoundPitchModifier(sound->sound_handle, sound->params.pitch_mod); + if (sound->params.mask & 0x4) { + snd_SetSoundPitchBend(sound->sound_handle, sound->params.bend); + } + if (sound->params.mask & 0x800) { + snd_SetSoundReg(sound->sound_handle, 0, sound->params.reg[0]); + } + if (sound->params.mask & 0x1000) { + snd_SetSoundReg(sound->sound_handle, 1, sound->params.reg[1]); + } + if (sound->params.mask & 0x2000) { + snd_SetSoundReg(sound->sound_handle, 2, sound->params.reg[2]); + } + + } else { + // new sound + sound = AllocateSound(); + if (sound == nullptr) { + // no free sounds + break; + } + strcpy_toupper(sound->name, cmd->play.name); + // TODO update params struct + sound->params = cmd->play.parms; + sound->is_music = false; + sound->bank_entry = nullptr; + + SFXUserData data{}; + s32 found = snd_GetSoundUserData(0, nullptr, -1, sound->name, &data); + if ((sound->params.mask & 0x40) == 0) { + s16 fo_min = 5; + if (found && data.data[0]) + fo_min = data.data[0]; + sound->params.fo_min = fo_min; + } + if ((sound->params.mask & 0x80) == 0) { + s16 fo_max = 30; + if (found && data.data[1]) + fo_max = data.data[1]; + sound->params.fo_max = fo_max; + } + if ((sound->params.mask & 0x100) == 0) { + s16 fo_curve = 2; + if (found && data.data[2]) + fo_curve = data.data[2]; + sound->params.fo_curve = fo_curve; + } + // lg::warn("RPC: PLAY {} v:{}, p:{}", sound->name, GetVolume(sound), GetPan(sound)); + + s32 handle = snd_PlaySoundByNameVolPanPMPB(0, nullptr, sound->name, GetVolume(sound), + GetPan(sound), sound->params.pitch_mod, + sound->params.bend); + sound->sound_handle = handle; + if (handle != 0) { + sound->id = cmd->play.sound_id; + if (sound->params.mask & 0x800) { + snd_SetSoundReg(sound->sound_handle, 0, sound->params.reg[0]); + } + if (sound->params.mask & 0x1000) { + snd_SetSoundReg(sound->sound_handle, 1, sound->params.reg[1]); + } + if (sound->params.mask & 0x2000) { + snd_SetSoundReg(sound->sound_handle, 2, sound->params.reg[2]); + } + } + } + } break; + case Jak2SoundCommand::pause_sound: { + Sound* sound = LookupSound(cmd->sound_id.sound_id); + if (sound != nullptr) { + snd_PauseSound(sound->sound_handle); + } + // TODO vag + } break; + case Jak2SoundCommand::stop_sound: { + Sound* sound = LookupSound(cmd->sound_id.sound_id); + if (sound != nullptr) { + snd_StopSound(sound->sound_handle); + } + // TODO vag + } break; + case Jak2SoundCommand::continue_sound: { + Sound* sound = LookupSound(cmd->sound_id.sound_id); + if (sound != nullptr) { + snd_ContinueSound(sound->sound_handle); + } + // TODO vag + } break; + case Jak2SoundCommand::set_param: { + Sound* sound = LookupSound(cmd->sound_id.sound_id); + u32 mask = cmd->param.parms.mask; + if (sound != nullptr) { + if (mask & 1) { + if (mask & 0x10) { + sound->auto_time = cmd->param.auto_time; + sound->new_volume = cmd->param.parms.volume; + } else { + sound->params.volume = cmd->param.parms.volume; + } + } + if (mask & 0x20) { + sound->params.trans = cmd->param.parms.trans; + } + if (mask & 0x21) { + UpdateVolume(sound); + } + if (mask & 2) { + sound->params.pitch_mod = cmd->param.parms.pitch_mod; + if (mask & 0x10) { + snd_AutoPitch(sound->sound_handle, sound->params.pitch_mod, cmd->param.auto_time, + cmd->param.auto_from); + } else { + snd_SetSoundPitchModifier(sound->sound_handle, cmd->param.parms.pitch_mod); + } + } + if (mask & 4) { + sound->params.bend = cmd->param.parms.bend; + if (mask & 0x10) { + snd_AutoPitchBend(sound->sound_handle, sound->params.bend, cmd->param.auto_time, + cmd->param.auto_from); + } else { + snd_SetSoundPitchBend(sound->sound_handle, cmd->param.parms.bend); + } + } + if (mask & 0x400) { + sound->params.priority = cmd->param.parms.priority; + } + if (mask & 0x8) { + sound->params.group = cmd->param.parms.group; + } + if (mask & 0x40) { + sound->params.fo_min = cmd->param.parms.fo_min; + } + if (mask & 0x80) { + sound->params.fo_max = cmd->param.parms.fo_max; + } + if (mask & 0x100) { + sound->params.fo_curve = cmd->param.parms.fo_curve; + } + if (mask & 0x800) { + sound->params.reg[0] = cmd->param.parms.reg[0]; + snd_SetSoundReg(sound->sound_handle, 0, cmd->param.parms.reg[0]); + } + if (mask & 0x1000) { + sound->params.reg[1] = cmd->param.parms.reg[1]; + snd_SetSoundReg(sound->sound_handle, 1, cmd->param.parms.reg[1]); + } + if (mask & 0x2000) { + sound->params.reg[2] = cmd->param.parms.reg[2]; + snd_SetSoundReg(sound->sound_handle, 2, cmd->param.parms.reg[2]); + } + } + // TODO vag + } break; + case Jak2SoundCommand::set_master_volume: { + u32 group = cmd->master_volume.group.group; + // FIXME array of set volumes + for (int i = 0; i < 32; i++) { + if (((group >> i) & 1) != 0) { + if (i == 1) { + gMusicVol = cmd->master_volume.volume; + } else if (i == 2) { + SetDialogVolume(cmd->master_volume.volume); + } else { + snd_SetMasterVolume(i, cmd->master_volume.volume); + } + } + } + } break; + case Jak2SoundCommand::pause_group: { + snd_PauseAllSoundsInGroup(cmd->group.group); + if (cmd->group.group & 2) { + gMusicPause = 1; + } + if (cmd->group.group & 4) { + // TODO vag + } + } break; + case Jak2SoundCommand::stop_group: { + KillSoundsInGroup(cmd->group.group); + } break; + case Jak2SoundCommand::continue_group: { + snd_ContinueAllSoundsInGroup(cmd->group.group); + if (cmd->group.group & 2) { + gMusicPause = 0; + } + if (cmd->group.group & 4) { + // TODO vag + } + } break; + case Jak2SoundCommand::set_midi_reg: { + if (cmd->midi_reg.reg == 16) { + snd_SetGlobalExcite(cmd->midi_reg.value); + } else { + Sound* sound = LookupSound(666); + snd_SetMIDIRegister(sound->sound_handle, cmd->midi_reg.reg, cmd->midi_reg.value); + } + } break; + case Jak2SoundCommand::set_reverb: { + lg::warn("RPC_Player: unimplemented set_reverb"); + // TODO reverb + } break; + case Jak2SoundCommand::set_ear_trans: { + SetEarTrans(&cmd->ear_trans_j2.ear_trans1, &cmd->ear_trans_j2.ear_trans2, + &cmd->ear_trans_j2.cam_trans, cmd->ear_trans_j2.cam_angle); + } break; + case Jak2SoundCommand::shutdown: { + gSoundEnable = 0; + } break; + case Jak2SoundCommand::set_fps: { + gFPS = cmd->fps.fps; + } break; + default: + ASSERT_MSG(false, fmt::format("Unhandled RPC Player command {}", + magic_enum::enum_name(cmd->j2command))); + } + + n_messages--; + cmd++; + } + + return nullptr; +} + +void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { int n_messages = size / SRPC_MESSAGE_SIZE; SoundRpcCommand* cmd = (SoundRpcCommand*)(data); if (gSoundEnable) { @@ -369,8 +664,8 @@ void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { ASSERT(false); } while (n_messages > 0) { - switch (cmd->command) { - case SoundCommand::LOAD_BANK: { + switch (cmd->j1command) { + case Jak1SoundCommand::LOAD_BANK: { // see if it's already loaded auto bank = LookupBank(cmd->load_bank.bank_name); if (!bank) { @@ -382,7 +677,7 @@ void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { } } } break; - case SoundCommand::UNLOAD_BANK: { + case Jak1SoundCommand::UNLOAD_BANK: { SoundBank* bank = LookupBank(cmd->load_bank.bank_name); if (bank != nullptr) { s32 id = bank->bank_handle; @@ -391,20 +686,20 @@ void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { snd_ResolveBankXREFS(); } } break; - case SoundCommand::GET_IRX_VERSION: { + case Jak1SoundCommand::GET_IRX_VERSION: { cmd->irx_version.major = IRX_VERSION_MAJOR; cmd->irx_version.minor = IRX_VERSION_MINOR; gInfoEE = cmd->irx_version.ee_addr; return cmd; } break; - case SoundCommand::RELOAD_INFO: { + case Jak1SoundCommand::RELOAD_INFO: { ReloadBankInfo(); } break; - case SoundCommand::SET_LANGUAGE: { + case Jak1SoundCommand::SET_LANGUAGE: { gLanguage = languages[cmd->set_language.langauge_id]; printf("IOP language: %s\n", gLanguage); // added. } break; - case SoundCommand::LOAD_MUSIC: { + case Jak1SoundCommand::LOAD_MUSIC: { while (WaitSema(gSema)) ; if (gMusic) { @@ -419,10 +714,10 @@ void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { LoadMusic(cmd->load_bank.bank_name, &gMusic); SignalSema(gSema); } break; - case SoundCommand::LIST_SOUNDS: { + case Jak1SoundCommand::LIST_SOUNDS: { PrintActiveSounds(); } break; - case SoundCommand::UNLOAD_MUSIC: { + case Jak1SoundCommand::UNLOAD_MUSIC: { while (WaitSema(gSema)) ; if (gMusic) { @@ -437,7 +732,8 @@ void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { SignalSema(gSema); } break; default: - ASSERT_MSG(false, fmt::format("Unhandled RPC Loader command {}", (int)cmd->command)); + ASSERT_MSG(false, fmt::format("Unhandled RPC Loader command {}", + magic_enum::enum_name(cmd->j1command))); } n_messages--; cmd++; @@ -446,6 +742,104 @@ void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { return nullptr; } +static void UnLoadMusic(s32* handle) { + gMusicFadeDir = -1; + while (gMusicFade) + DelayThread(1000); + snd_UnloadBank(*handle); + snd_ResolveBankXREFS(); + *handle = 0; +} + +void* RPC_Loader2(unsigned int fno, void* data, int size) { + int n_messages = size / SRPC_MESSAGE_SIZE; + SoundRpcCommand* cmd = (SoundRpcCommand*)(data); + if (!gSoundEnable) { + return nullptr; + } + + while (n_messages > 0) { + switch (cmd->j2command) { + case Jak2SoundCommand::load_bank: { + if (LookupBank(cmd->load_bank.bank_name)) { + break; + } + + auto bank = AllocateBankName(cmd->load_bank.bank_name); + if (bank == nullptr) { + break; + } + + strncpy(bank->name, cmd->load_bank.bank_name, 16); + bank->in_use = true; + bank->unk4 = 0; + LoadSoundBank(cmd->load_bank.bank_name, bank); + } break; + case Jak2SoundCommand::load_music: { + while (WaitSema(gSema)) + ; + if (gMusic) { + UnLoadMusic(&gMusic); + } + LoadMusic(cmd->load_bank.bank_name, &gMusic); + SignalSema(gSema); + } break; + case Jak2SoundCommand::unload_bank: { + auto bank = LookupBank(cmd->load_bank.bank_name); + if (!bank) { + break; + } + auto handle = bank->bank_handle; + if (!bank->unk4) { + bank->in_use = false; + } + bank->in_use = 0; + snd_UnloadBank(handle); + snd_ResolveBankXREFS(); + } break; + case Jak2SoundCommand::get_irx_version: { + cmd->irx_version.major = 4; + cmd->irx_version.minor = 0; + gInfoEE = cmd->irx_version.ee_addr; + return data; + } break; + case Jak2SoundCommand::set_language: { + gLanguage = languages[cmd->set_language.langauge_id]; + } break; + case Jak2SoundCommand::list_sounds: { + // Not present in real jak2 overlord + PrintActiveSounds(); + } break; + case Jak2SoundCommand::unload_music: { + while (WaitSema(gSema)) + ; + if (gMusic) { + UnLoadMusic(&gMusic); + } + SignalSema(gSema); + } break; + case Jak2SoundCommand::set_stereo_mode: { + s32 mode = cmd->stereo_mode.stereo_mode; + if (mode == 0) { + snd_SetPlayBackMode(1); + } else if (mode == 1) { + snd_SetPlayBackMode(2); + } else if (mode == 2) { + snd_SetPlayBackMode(0); + } + } break; + default: + ASSERT_MSG(false, fmt::format("Unhandled RPC Loader command {}", + magic_enum::enum_name(cmd->j2command))); + } + + n_messages--; + cmd++; + } + + return nullptr; +} + static s32 dmaid = 0; s32 VBlank_Handler(void*) { diff --git a/game/overlord/srpc.h b/game/overlord/srpc.h index 005b9f2c7..4cf66708d 100644 --- a/game/overlord/srpc.h +++ b/game/overlord/srpc.h @@ -20,7 +20,7 @@ struct MusicTweaks { } MusicTweak[MUSIC_TWEAK_COUNT]; }; -enum class SoundCommand : u16 { +enum class Jak1SoundCommand : u16 { LOAD_BANK = 0, LOAD_MUSIC = 1, UNLOAD_BANK = 2, @@ -46,6 +46,60 @@ enum class SoundCommand : u16 { UNLOAD_MUSIC = 22 }; +enum class Jak2SoundCommand : u16 { + iop_store = 0, + iop_free = 1, + load_bank = 2, + load_bank_from_iop = 3, + load_bank_from_ee = 4, + load_music = 5, + unload_bank = 6, + play = 7, + pause_sound = 8, + stop_sound = 9, + continue_sound = 10, + set_param = 11, + set_master_volume = 12, + pause_group = 13, + stop_group = 14, + continue_group = 15, + get_irx_version = 16, + set_falloff_curve = 17, + set_sound_falloff = 18, + reload_info = 19, + set_language = 20, + set_flava = 21, + set_midi_reg = 22, + set_reverb = 23, + set_ear_trans = 24, + shutdown = 25, + list_sounds = 26, + unload_music = 27, + set_fps = 28, + boot_load = 29, + game_load = 30, + num_tests = 31, + num_testruns = 32, + num_sectors = 33, + num_streamsectors = 34, + num_streambanks = 35, + track_pitch = 36, + linvel_nom = 37, + linvel_stm = 38, + seek_nom = 39, + seek_stm = 40, + read_seq_nom = 41, + read_seq_stm = 42, + read_spr_nom = 43, + read_spr_stm = 44, + read_spr_strn_nom = 45, + rand_stm_abort = 46, + rand_nom_abort = 47, + iop_mem = 48, + cancel_dgo = 49, + set_stereo_mode = 50, +}; + struct SoundRpcGetIrxVersion { u32 major; u32 minor; @@ -96,6 +150,13 @@ struct SoundRpcSetEarTrans { s32 cam_angle; }; +struct SoundRpc2SetEarTrans { + Vec3w ear_trans1; + Vec3w ear_trans2; + Vec3w cam_trans; + s32 cam_angle; +}; + struct SoundRpcSetFPSCommand { u8 fps; }; @@ -123,9 +184,21 @@ struct SoundRpcMasterVolCommand { s32 volume; }; +struct SoundRpcStereoMode { + s32 stereo_mode; +}; + +struct SoundRpcSetMidiReg { + s32 reg; + s32 value; +}; + struct SoundRpcCommand { u16 rsvd1; - SoundCommand command; + union { + Jak1SoundCommand j1command; + Jak2SoundCommand j2command; + }; union { SoundRpcGetIrxVersion irx_version; SoundRpcBankCommand load_bank; @@ -134,6 +207,7 @@ struct SoundRpcCommand { SoundRpcSoundIdCommand sound_id; SoundRpcSetFPSCommand fps; SoundRpcSetEarTrans ear_trans; + SoundRpc2SetEarTrans ear_trans_j2; SoundRpcSetReverb reverb; SoundRpcSetFallof fallof; SoundRpcSetFallofCurve fallof_curve; @@ -141,6 +215,8 @@ struct SoundRpcCommand { SoundRpcSetFlavaCommand flava; SoundRpcMasterVolCommand master_volume; SoundRpcSetParamCommand param; + SoundRpcStereoMode stereo_mode; + SoundRpcSetMidiReg midi_reg; u8 max_size[0x4C]; // Temporary }; }; diff --git a/game/overlord/ssound.cpp b/game/overlord/ssound.cpp index 886dc5139..b5994d827 100644 --- a/game/overlord/ssound.cpp +++ b/game/overlord/ssound.cpp @@ -7,15 +7,16 @@ #include "game/overlord/iso.h" #include "game/overlord/srpc.h" +#include "game/runtime.h" #include "game/sound/sndshim.h" using namespace iop; Sound gSounds[64]; -Curve gCurve[8]; // TODO verify count +Curve gCurve[12]; // TODO verify count VolumePair gPanTable[361]; -Vec3w gEarTrans; +Vec3w gEarTrans[2]; Vec3w gCamTrans; s32 gCamAngle; @@ -75,18 +76,31 @@ void InitSound_Overlord() { s.id = 0; } - SetCurve(1, 0, 0); - SetCurve(2, 0x1000, 0); - SetCurve(3, 0, 0x1000); - SetCurve(4, 0x800, 0); - SetCurve(5, 0x800, 0x800); - SetCurve(6, -0x1000, 0); - SetCurve(6, -0x800, 0); + if (g_game_version == GameVersion::Jak1) { + SetCurve(1, 0, 0); + SetCurve(2, 4096, 0); + SetCurve(3, 0, 4096); + SetCurve(4, 2048, 0); + SetCurve(5, 2048, 2048); + SetCurve(6, -4096, 0); + SetCurve(7, -2048, 0); + } else { + SetCurve(2, 0, 0); + SetCurve(9, 0, 0); + SetCurve(11, 0, 0); + SetCurve(10, 0, 0); + SetCurve(3, 4096, 0); + SetCurve(4, 0, 4096); + SetCurve(5, 2048, 0); + SetCurve(6, 2048, 2048); + SetCurve(7, -4096, 0); + SetCurve(8, -2048, 0); + } snd_StartSoundSystem(); snd_RegisterIOPMemAllocator(SndMemAlloc, SndMemFree); snd_LockVoiceAllocator(1); - u32 voice = snd_ExternVoiceVoiceAlloc(2, 0x7f); + u32 voice = snd_ExternVoiceAlloc(2, 0x7f); snd_UnlockVoiceAllocator(); // The voice allocator returns a number in the range 0-47 where voices @@ -216,22 +230,56 @@ Sound* AllocateSound() { } s32 CalculateFallofVolume(Vec3w* pos, s32 volume, s32 fo_curve, s32 fo_min, s32 fo_max) { - if (fo_curve == 0) { - return volume; - } + s32 xdiff = 0; + s32 ydiff = 0; + s32 zdiff = 0; - s32 xdiff = gEarTrans.x - pos->x; - s32 ydiff = gEarTrans.y - pos->y; - s32 zdiff = gEarTrans.z - pos->z; + if (g_game_version == GameVersion::Jak1) { + if (fo_curve == 0) { + return volume; + } + + xdiff = gEarTrans[0].x - pos->x; + ydiff = gEarTrans[0].y - pos->y; + zdiff = gEarTrans[0].z - pos->z; + } else { + if (fo_curve == 1) { + return volume; + } + + if (fo_curve < 9) { + xdiff = gEarTrans[0].x - pos->x; + ydiff = gEarTrans[0].y - pos->y; + zdiff = gEarTrans[0].z - pos->z; + } + + if (fo_curve == 9) { + xdiff = gEarTrans[1].x - pos->x; + ydiff = gEarTrans[1].y - pos->y; + zdiff = gEarTrans[1].z - pos->z; + } + + if (fo_curve == 10) { + xdiff = 0; + ydiff = gEarTrans[0].y - pos->y; + zdiff = 0; + } + + if (fo_curve == 11) { + xdiff = gEarTrans[1].x - pos->x; + ydiff = gEarTrans[1].y - pos->y; + zdiff = gEarTrans[1].z - pos->z; + } + } if (xdiff < 0) { - xdiff = pos->x - gEarTrans.x; + xdiff = -xdiff; } if (ydiff < 0) { - ydiff = pos->y - gEarTrans.y; + ydiff = -ydiff; } if (zdiff < 0) { - zdiff = pos->z - gEarTrans.z; + zdiff = -zdiff; } s32 min = fo_min << 8; @@ -300,7 +348,12 @@ s32 CalculateFallofVolume(Vec3w* pos, s32 volume, s32 fo_curve, s32 fo_min, s32 factor = 0x10000; } - return (factor * volume) >> 16; + s32 ret = (factor * volume) >> 16; + if (fo_curve == 11 && ret < 0x180) { + ret = 0x180; + } + + return ret; } s32 CalculateAngle(Vec3w* trans) { @@ -368,8 +421,10 @@ static void UpdateLocation(Sound* sound) { return; } - if ((sound->bank_entry->fallof_params >> 28) == 0) { - return; + if (g_game_version == GameVersion::Jak1) { + if ((sound->bank_entry->fallof_params >> 28) == 0) { + return; + } } s32 id = snd_SoundIsStillPlaying(sound->sound_handle); @@ -439,12 +494,13 @@ void UpdateVolume(Sound* sound) { } } -void SetEarTrans(Vec3w* ear_trans, Vec3w* cam_trans, s32 cam_angle) { +void SetEarTrans(Vec3w* ear_trans1, Vec3w* ear_trans2, Vec3w* cam_trans, s32 cam_angle) { s32 tick = snd_GetTick(); u32 delta = tick - sLastTick; sLastTick = tick; - gEarTrans = *ear_trans; + gEarTrans[0] = *ear_trans1; + gEarTrans[1] = *ear_trans2; gCamTrans = *cam_trans; gCamAngle = cam_angle; @@ -465,14 +521,24 @@ void PrintActiveSounds() { for (auto& s : gSounds) { if (s.id != 0 && s.is_music == 0) { - u32 len = strlen(s.bank_entry->name); - if (len > 16) { - len = 16; + if (s.bank_entry != nullptr) { + u32 len = strlen(s.bank_entry->name); + if (len > 16) { + len = 16; + } + sprintf(string, " : Vol %d", GetVolume(&s)); + memcpy(string, s.bank_entry->name, len); + printf("%s\n", string); + } else { // added for printing jak2 sounds + u32 len = strlen(s.name); + if (len > 16) { + len = 16; + } + sprintf(string, " : Vol %d, ID %d, Curve %d", GetVolume(&s), s.id, + s.params.fo_curve); + memcpy(string, s.name, len); + printf("%s\n", string); } - s32 volume = GetVolume(&s); - sprintf(string, " : Vol %d", volume); - memcpy(string, s.bank_entry->name, len); - printf("%s\n", string); } } } diff --git a/game/overlord/ssound.h b/game/overlord/ssound.h index df016bae3..8e465febd 100644 --- a/game/overlord/ssound.h +++ b/game/overlord/ssound.h @@ -36,9 +36,11 @@ struct SoundParams { s32 volume; Vec3w trans; u8 group; + u8 reg[3]; }; struct Sound { + char name[16]; s32 id; s32 sound_handle; s32 is_music; @@ -57,7 +59,7 @@ struct Curve { void InitSound_Overlord(); void SetCurve(s32 curve, s32 fallof, s32 ease); -void SetEarTrans(Vec3w* ear_trans, Vec3w* cam_trans, s32 cam_angle); +void SetEarTrans(Vec3w* ear_trans1, Vec3w* ear_trans2, Vec3w* cam_trans, s32 cam_angle); void KillSoundsInGroup(u8 group); void PrintActiveSounds(); void SetMusicVol(); diff --git a/game/sound/989snd/ame_handler.cpp b/game/sound/989snd/ame_handler.cpp index cce42ca85..f926b3542 100644 --- a/game/sound/989snd/ame_handler.cpp +++ b/game/sound/989snd/ame_handler.cpp @@ -17,7 +17,7 @@ ame_handler::ame_handler(MultiMIDIBlockHeader* block, s32 vol, s32 pan, locator& loc, - u32 bank) + SoundBank& bank) : m_sound(sound), m_bank(bank), m_header(block), @@ -75,7 +75,6 @@ void ame_handler::stop_segment(u32 id) { return; m->second->stop(); - m_midis.erase(id); } void ame_handler::pause() { @@ -115,7 +114,9 @@ void ame_handler::set_vol_pan(s32 vol, s32 pan) { } void ame_handler::set_pmod(s32 mod) { - // TODO + for (auto& m : m_midis) { + m.second->set_pmod(mod); + } } #define AME_BEGIN(op) \ diff --git a/game/sound/989snd/ame_handler.h b/game/sound/989snd/ame_handler.h index 2f9707191..eea3df00b 100644 --- a/game/sound/989snd/ame_handler.h +++ b/game/sound/989snd/ame_handler.h @@ -26,9 +26,9 @@ class ame_handler : public sound_handler { s32 vol, s32 pan, locator& loc, - u32 bank); + SoundBank& bank); bool tick() override; - u32 bank() override { return m_bank; }; + SoundBank& bank() override { return m_bank; }; void pause() override; void unpause() override; @@ -61,7 +61,7 @@ class ame_handler : public sound_handler { std::pair run_ame(midi_handler&, u8* stream); MIDISound& m_sound; - u32 m_bank{0}; + SoundBank& m_bank; MultiMIDIBlockHeader* m_header{nullptr}; locator& m_locator; diff --git a/game/sound/989snd/blocksound_handler.cpp b/game/sound/989snd/blocksound_handler.cpp index 79030bf84..4367ad66c 100644 --- a/game/sound/989snd/blocksound_handler.cpp +++ b/game/sound/989snd/blocksound_handler.cpp @@ -5,10 +5,14 @@ #include "util.h" +#include "common/log/log.h" + namespace snd { +std::array g_block_reg{}; + void blocksound_handler::init() { m_next_grain = 0; - m_countdown = m_sfx.grains[0].Delay; + m_countdown = m_sfx.grains[0]->delay(); // if (m_sfx.d.Flags & 2) { // fmt::print("solo flag\n"); @@ -16,6 +20,12 @@ void blocksound_handler::init() { // return; // } + int idx = 0; + for (auto& g : m_sfx.grains) { + lg::info("grain {}: {}", idx, g->inspect()); + idx++; + } + while (m_countdown <= 0 && !m_done) { do_grain(); } @@ -24,9 +34,21 @@ void blocksound_handler::init() { bool blocksound_handler::tick() { m_voices.remove_if([](std::weak_ptr& p) { return p.expired(); }); - if (m_done) { + for (auto& lfo : m_lfo) { + lfo.tick(); + } + + for (auto it = m_children.begin(); it != m_children.end();) { + bool done = it->get()->tick(); + if (done) { + it = m_children.erase(it); + } else { + ++it; + } + } + + if (m_done && m_children.empty()) { if (m_voices.empty()) { - // fmt::print("{}: voices empty\n", (void*)this); return m_done; } else { return false; @@ -86,14 +108,10 @@ void blocksound_handler::stop() { void blocksound_handler::set_vol_pan(s32 vol, s32 pan) { if (vol >= 0) { if (vol != VOLUME_DONT_CHANGE) { - m_app_volume = (vol * m_sfx.d.Vol) >> 10; + m_app_volume = vol; } } else { - m_app_volume = -vol; - } - - if (m_app_volume >= 128) { - m_app_volume = 127; + m_app_volume = -1024 * vol / 127; } if (pan == PAN_RESET) { @@ -102,21 +120,24 @@ void blocksound_handler::set_vol_pan(s32 vol, s32 pan) { m_app_pan = pan; } - vol = m_app_volume; // + lfo vol - // TODO LFO logic here + s32 new_vol = ((m_app_volume * m_orig_volume) >> 10) + m_lfo_volume; + new_vol = std::clamp(new_vol, 0, 127); - pan = m_app_pan; // + lfo pan - while (pan >= 360) { - pan -= 360; + s32 new_pan = m_app_pan + m_lfo_pan; + while (new_pan >= 360) { + new_pan -= 360; + } + while (new_pan < 0) { + new_pan += 360; } - while (pan < 0) { - pan += 360; - } + if (new_pan != m_cur_pan || new_vol != m_cur_volume) { + m_cur_volume = new_vol; + m_cur_pan = new_pan; - if (pan != m_cur_pan || vol != m_cur_volume) { - m_cur_volume = vol; - m_cur_pan = pan; + for (auto& c : m_children) { + c->set_vol_pan(m_app_volume * m_orig_volume / 127, pan); + } for (auto& p : m_voices) { auto voice = p.lock(); @@ -135,8 +156,8 @@ void blocksound_handler::set_vol_pan(s32 vol, s32 pan) { } void blocksound_handler::update_pitch() { - m_cur_pm = m_app_pm; - m_cur_pb = m_app_pb; + m_cur_pm = m_app_pm + m_lfo_pm; + m_cur_pb = std::clamp(m_app_pb + m_lfo_pb, INT16_MIN, INT16_MAX); for (auto& p : m_voices) { auto voice = p.lock(); @@ -147,72 +168,27 @@ void blocksound_handler::update_pitch() { auto note = pitchbend(voice->tone, m_cur_pb, m_cur_pm, m_note, m_fine); auto pitch = PS1Note2Pitch(voice->tone.CenterNote, voice->tone.CenterFine, note.first, note.second); + voice->set_pitch(pitch); } } void blocksound_handler::set_pmod(s32 mod) { + // TODO update children m_app_pm = mod; update_pitch(); } void blocksound_handler::set_pbend(s32 bend) { + // TODO update children m_app_pb = bend; update_pitch(); } -s32 blocksound_handler::null(SFXGrain& grain) { - return 0; -} - -s32 blocksound_handler::play_tone(SFXGrain& grain) { - auto voice = std::make_shared(grain.GrainParams.tone); - - voice->basevol = m_vm.make_volume(127, 0, m_cur_volume, m_cur_pan, grain.GrainParams.tone.Vol, - grain.GrainParams.tone.Pan); - - voice->start_note = m_note; - voice->start_fine = m_fine; - voice->group = m_group; - - m_vm.start_tone(voice); - m_voices.emplace_front(voice); - - return 0; -} - -s32 blocksound_handler::rand_play(SFXGrain& grain) { - int options = grain.GrainParams.control.param[0]; - int count = grain.GrainParams.control.param[1]; - int previous = grain.GrainParams.control.param[2]; - - int rnd = rand() % options; - if (rnd == previous) { - rnd++; - if (rnd >= options) { - rnd = 0; - } - } - - grain.GrainParams.control.param[2] = rnd; - m_next_grain += rnd * count; - m_grains_to_play = count + 1; - m_grains_to_skip = (options - 1 - rnd) * count; - m_skip_grains = true; - - return 0; -} - void blocksound_handler::do_grain() { auto& grain = m_sfx.grains[m_next_grain]; - auto handler = m_grain_handler.find((grain_type)grain.Type); - if (handler != m_grain_handler.end()) { - (this->*(handler->second))(grain); - } else { - throw std::runtime_error( - fmt::format("{}: Ignoring grain {}, type {}\n", (void*)this, m_next_grain, grain.Type)); - } + s32 ret = grain->execute(*this); if (m_skip_grains) { m_grains_to_play--; @@ -228,7 +204,7 @@ void blocksound_handler::do_grain() { return; } - m_countdown = m_sfx.grains[m_next_grain].Delay; + m_countdown = m_sfx.grains[m_next_grain]->delay() + ret; } } // namespace snd diff --git a/game/sound/989snd/blocksound_handler.h b/game/sound/989snd/blocksound_handler.h index 07f919bb6..0fa1a8e89 100644 --- a/game/sound/989snd/blocksound_handler.h +++ b/game/sound/989snd/blocksound_handler.h @@ -1,44 +1,90 @@ #pragma once -#include - -#include "sfxblock.h" #include "sound_handler.h" #include "vagvoice.h" #include "common/common_types.h" +#include "game/sound/989snd/lfo.h" +#include "sfxblock2.h" + namespace snd { + +extern std::array g_block_reg; + class blocksound_handler : public sound_handler { public: - blocksound_handler(SFX& sfx, voice_manager& vm, s32 vol, s32 pan, s32 pm, s32 pb, u32 bank_id) - : m_sfx(sfx), m_vm(vm), m_bank(bank_id) { - vol = (vol * m_sfx.d.Vol) >> 10; - if (vol >= 128) { - vol = 127; + blocksound_handler(SoundBank& bank, + SFX2& sfx, + voice_manager& vm, + s32 sfx_vol, + s32 sfx_pan, + SndPlayParams& params) + : m_sfx(sfx), m_vm(vm), m_bank(bank) { + s32 vol, pan, pitch_mod, pitch_bend; + if (sfx_vol == -1) { + sfx_vol = sfx.d.Vol; + } + if (sfx_pan == -1) { + sfx_pan = sfx.d.Pan; } - if (pan >= PAN_DONT_CHANGE) { - pan = m_sfx.d.Pan; + if (params.vol.has_value()) { + vol = params.vol.value(); + } else { + vol = 1024; } - m_cur_volume = vol; + if (params.pan.has_value()) { + pan = params.pan.value(); + } else { + pan = -1; + } + + if (params.pitch_mod.has_value()) { + pitch_mod = params.pitch_mod.value(); + } else { + pitch_mod = 0; + } + + if (params.pitch_bend.has_value()) { + pitch_bend = params.pitch_bend.value(); + } else { + pitch_bend = 0; + } + + if (vol == VOLUME_DONT_CHANGE) { + vol = 1024; + } + s32 play_vol = (sfx_vol * vol) >> 10; + if (play_vol >= 128) { + play_vol = 127; + } + + if (pan == PAN_RESET || pan == PAN_DONT_CHANGE) { + pan = sfx_pan; + } + + m_orig_volume = sfx_vol; + m_orig_pan = sfx_pan; + + m_cur_volume = play_vol; m_cur_pan = pan; - m_cur_pm = pm; - m_cur_pb = pb; + m_cur_pb = pitch_bend; + m_cur_pm = pitch_mod; m_app_volume = vol; m_app_pan = pan; - m_app_pm = 0; // why only this one? - m_app_pb = pb; + m_app_pb = pitch_bend; + m_app_pm = pitch_mod; - m_orig_pan = m_sfx.d.Pan; - m_orig_volume = m_sfx.d.Vol; + m_lfo_volume = 0; + m_lfo_pan = 0; + m_lfo_pb = 0; + m_lfo_pm = 0; - m_group = sfx.d.VolGroup; - - m_grain_handler.insert(std::make_pair(grain_type::null, &blocksound_handler::null)); - m_grain_handler.insert(std::make_pair(grain_type::tone, &blocksound_handler::play_tone)); - m_grain_handler.insert(std::make_pair(grain_type::rand_play, &blocksound_handler::rand_play)); + if (params.registers.has_value()) { + m_registers = params.registers.value(); + } } ~blocksound_handler() override { @@ -51,7 +97,7 @@ class blocksound_handler : public sound_handler { } bool tick() override; - u32 bank() override { return m_bank; }; + SoundBank& bank() override { return m_bank; }; void pause() override; void unpause() override; @@ -59,34 +105,15 @@ class blocksound_handler : public sound_handler { u8 group() override { return m_group; }; void set_vol_pan(s32 vol, s32 pan) override; void set_pmod(s32 mod) override; + void set_register(u8 reg, u8 value) override { m_registers.at(reg) = value; }; void set_pbend(s32 bend); // TODO override; void init(); - private: - enum class grain_type : u32 { - null = 0, - tone = 1, - xref_id = 2, - xref_num = 3, - lfo_settings = 4, - loop_start = 21, - loop_end = 22, - loop_continue = 23, - rand_play = 25, - rand_delay = 26, - }; - void do_grain(); - s32 null(SFXGrain& grain); - s32 play_tone(SFXGrain& grain); - s32 rand_play(SFXGrain& grain); void update_pitch(); - using grain_fp = int (blocksound_handler::*)(SFXGrain& grain); - std::unordered_map m_grain_handler; - bool m_paused{false}; u8 m_group{0}; @@ -96,11 +123,13 @@ class blocksound_handler : public sound_handler { u32 m_grains_to_skip{0}; bool m_skip_grains{false}; - SFX& m_sfx; + SFX2& m_sfx; voice_manager& m_vm; std::list> m_voices; + std::list> m_children; + s32 m_current_pb{0}; s32 m_current_pm{0}; @@ -118,13 +147,15 @@ class blocksound_handler : public sound_handler { s32 m_lfo_volume{0}; s32 m_lfo_pan{0}; s32 m_lfo_pm{0}; + s32 m_lfo_pb{0}; - u32 m_bank{0}; + SoundBank& m_bank; u8 m_note{60}; u8 m_fine{0}; - std::array m_registers{}; + std::array m_registers{}; + std::array m_lfo{{*this, *this, *this, *this}}; // TODO LFO diff --git a/game/sound/989snd/lfo.cpp b/game/sound/989snd/lfo.cpp new file mode 100644 index 000000000..c4d12ff2f --- /dev/null +++ b/game/sound/989snd/lfo.cpp @@ -0,0 +1,140 @@ +#include "lfo.h" + +#include + +#include "blocksound_handler.h" + +namespace snd { +#include "lfo_sine.c.inc" + +void LFOTracker::init() { + if (m_type == lfo_type::RAND) { + m_state_hold1 = -(rand() & 0x7fff) * (rand() & 1); + m_state_hold2 = 1; + } + + calc_depth(); + tick(); +} + +void LFOTracker::calc_depth() { + if (m_target == lfo_target::VOLUME) { + m_range = (m_handler.m_sfx.d.Vol * m_depth) >> 10; + } + if (m_target == lfo_target::PAN) { + m_range = (180 * m_depth) >> 10; + } + if (m_target == lfo_target::PMOD) { + m_range = (6096 * m_depth) >> 10; + } + if (m_target == lfo_target::PBEND) { + m_range = (0x7fff * m_depth) >> 10; + } + + m_last_lfo = 0; +} + +void LFOTracker::tick() { + m_tick++; + + if (m_target == lfo_target::NONE || (m_tick & 1) == 0) { + return; + } + + switch (m_target) { + case lfo_target::VOLUME: { + s32 vol = (m_range * (get_lfo(2) - 0x7fff)) >> 16; + if (m_handler.m_lfo_volume != vol) { + m_handler.m_lfo_volume = vol; + m_handler.set_vol_pan(VOLUME_DONT_CHANGE, PAN_DONT_CHANGE); + } + } break; + case lfo_target::PAN: { + s32 pan = (m_range * get_lfo(2)) >> 15; + if (m_handler.m_lfo_pan != pan) { + m_handler.m_lfo_pan = pan; + m_handler.set_vol_pan(VOLUME_DONT_CHANGE, PAN_DONT_CHANGE); + } + } break; + case lfo_target::PMOD: { + s32 pm = (get_lfo(2) * m_range) >> 15; + if (m_handler.m_lfo_pm != pm) { + m_handler.m_lfo_pm = pm; + m_handler.update_pitch(); + } + } break; + case lfo_target::PBEND: { + s32 pb = (get_lfo(2) * m_range) >> 15; + if (m_handler.m_lfo_pb != pb) { + m_handler.m_lfo_pb = pb; + m_handler.update_pitch(); + } + } break; + case lfo_target::UNK1: { + } break; + case lfo_target::UNK2: { + } break; + default: + break; + } +} + +s32 LFOTracker::get_lfo(s32 step_mult) { + s32 step = m_next_step >> 16; + m_next_step += step_mult * m_step_size; + if (m_next_step > 0x7ffffff) { + m_next_step -= 0x8000000; + } + + s32 ret = 0; + + switch (m_type) { + case lfo_type::OFF: + ret = 0; + break; + case lfo_type::SINE: + ret = gLFO_sine.at(step); + break; + case lfo_type::SQUARE: + if (step >= m_state_hold1) { + ret = -32767; + } else { + ret = 32767; + } + break; + case lfo_type::TRIANGLE: + if (step < 512) { + ret = 0x7fff * step / 512; + } else if (step >= 1536) { + ret = 0x7fff * (step - 1536) / 512 - 0x7fff; + } else { + ret = 0x7fff - 65534 * (step - 512) / 1024; + } + break; + case lfo_type::SAW: + if (step >= 1024) { + ret = 0x7fff * (step - 1024) / 1024 - 0x7fff; + } else { + ret = 0x7fff * step / 1023; + } + break; + case lfo_type::RAND: + if (step >= 1024 && m_state_hold2 == 1) { + m_state_hold2 = 0; + m_state_hold1 = 2 * ((rand() & 0x7fff) - 0x3fff); + } else if (step < 1024 && m_state_hold2 == 0) { + m_state_hold2 = 1; + m_state_hold1 = -(rand() & 0x7fff) * (rand() & 1); + } + ret = m_state_hold1; + break; + } + + if ((m_setup_flags & 1) != 0) { + ret = -ret; + } + + return ret; +} + +} // namespace snd diff --git a/game/sound/989snd/lfo.h b/game/sound/989snd/lfo.h new file mode 100644 index 000000000..dbfd62f36 --- /dev/null +++ b/game/sound/989snd/lfo.h @@ -0,0 +1,43 @@ +#ifndef LFO_H_ +#define LFO_H_ + +#include "common/common_types.h" + +namespace snd { + +enum class lfo_type { OFF, SINE, SQUARE, TRIANGLE, SAW, RAND }; +enum class lfo_target { NONE, VOLUME, PAN, PMOD, PBEND, UNK1, UNK2 }; + +class blocksound_handler; + +class LFOTracker { + public: + LFOTracker(blocksound_handler& handler) : m_handler(handler) {} + lfo_type m_type{lfo_type::OFF}; + lfo_target m_target{0}; + u8 m_target_extra{0}; + u8 m_setup_flags{0}; + u8 m_running_flags{0}; + s16 m_depth{0}; + u32 m_orig_depth{0}; + s32 m_next_step{0}; + u32 m_step_size{0}; + u32 m_orig_step_size{0}; + s32 m_state_hold1{0}; + s32 m_state_hold2{0}; + s32 m_range{0}; + s32 m_last_lfo{0}; + + u32 m_tick{0}; + + void init(); + void calc_depth(); + void tick(); + s32 get_lfo(s32 step_mult); + + blocksound_handler& m_handler; +}; + +} // namespace snd + +#endif // LFO_H_ diff --git a/game/sound/989snd/lfo_sine.c.inc b/game/sound/989snd/lfo_sine.c.inc new file mode 100644 index 000000000..a82db6310 --- /dev/null +++ b/game/sound/989snd/lfo_sine.c.inc @@ -0,0 +1,172 @@ +std::array gLFO_sine = { + {32767, 32766, 32766, 32765, 32764, 32763, 32761, 32759, 32757, 32754, 32751, 32748, + 32744, 32740, 32736, 32732, 32727, 32722, 32717, 32711, 32705, 32699, 32692, 32685, + 32678, 32670, 32662, 32654, 32646, 32637, 32628, 32618, 32609, 32599, 32588, 32578, + 32567, 32556, 32544, 32532, 32520, 32508, 32495, 32482, 32468, 32455, 32441, 32426, + 32412, 32397, 32382, 32366, 32350, 32334, 32318, 32301, 32284, 32267, 32249, 32231, + 32213, 32194, 32176, 32156, 32137, 32117, 32097, 32077, 32056, 32035, 32014, 31992, + 31970, 31948, 31926, 31903, 31880, 31856, 31833, 31809, 31785, 31760, 31735, 31710, + 31684, 31659, 31633, 31606, 31580, 31553, 31525, 31498, 31470, 31442, 31413, 31385, + 31356, 31326, 31297, 31267, 31236, 31206, 31175, 31144, 31113, 31081, 31049, 31017, + 30984, 30951, 30918, 30885, 30851, 30817, 30783, 30748, 30713, 30678, 30643, 30607, + 30571, 30535, 30498, 30461, 30424, 30386, 30349, 30311, 30272, 30234, 30195, 30156, + 30116, 30076, 30036, 29996, 29955, 29915, 29873, 29832, 29790, 29748, 29706, 29663, + 29621, 29577, 29534, 29490, 29446, 29402, 29358, 29313, 29268, 29222, 29177, 29131, + 29085, 29038, 28992, 28945, 28897, 28850, 28802, 28754, 28706, 28657, 28608, 28559, + 28510, 28460, 28410, 28360, 28309, 28259, 28208, 28156, 28105, 28053, 28001, 27948, + 27896, 27843, 27790, 27736, 27683, 27629, 27575, 27520, 27466, 27411, 27355, 27300, + 27244, 27188, 27132, 27076, 27019, 26962, 26905, 26847, 26789, 26731, 26673, 26615, + 26556, 26497, 26437, 26378, 26318, 26258, 26198, 26137, 26077, 26016, 25954, 25893, + 25831, 25769, 25707, 25645, 25582, 25519, 25456, 25392, 25329, 25265, 25201, 25136, + 25072, 25007, 24942, 24877, 24811, 24745, 24679, 24613, 24546, 24480, 24413, 24346, + 24278, 24211, 24143, 24075, 24006, 23938, 23869, 23800, 23731, 23661, 23592, 23522, + 23452, 23382, 23311, 23240, 23169, 23098, 23027, 22955, 22883, 22811, 22739, 22666, + 22594, 22521, 22448, 22374, 22301, 22227, 22153, 22079, 22004, 21930, 21855, 21780, + 21705, 21629, 21554, 21478, 21402, 21326, 21249, 21173, 21096, 21019, 20942, 20864, + 20787, 20709, 20631, 20553, 20474, 20396, 20317, 20238, 20159, 20079, 20000, 19920, + 19840, 19760, 19680, 19599, 19519, 19438, 19357, 19276, 19194, 19113, 19031, 18949, + 18867, 18785, 18702, 18620, 18537, 18454, 18371, 18287, 18204, 18120, 18036, 17952, + 17868, 17784, 17699, 17615, 17530, 17445, 17360, 17274, 17189, 17103, 17017, 16931, + 16845, 16759, 16672, 16586, 16499, 16412, 16325, 16238, 16150, 16063, 15975, 15887, + 15799, 15711, 15623, 15534, 15446, 15357, 15268, 15179, 15090, 15001, 14911, 14822, + 14732, 14642, 14552, 14462, 14372, 14281, 14191, 14100, 14009, 13918, 13827, 13736, + 13645, 13553, 13462, 13370, 13278, 13186, 13094, 13002, 12909, 12817, 12724, 12632, + 12539, 12446, 12353, 12260, 12166, 12073, 11980, 11886, 11792, 11698, 11604, 11510, + 11416, 11322, 11227, 11133, 11038, 10944, 10849, 10754, 10659, 10564, 10469, 10373, + 10278, 10182, 10087, 9991, 9895, 9799, 9703, 9607, 9511, 9415, 9319, 9222, + 9126, 9029, 8932, 8836, 8739, 8642, 8545, 8448, 8351, 8253, 8156, 8059, + 7961, 7864, 7766, 7668, 7571, 7473, 7375, 7277, 7179, 7081, 6982, 6884, + 6786, 6688, 6589, 6491, 6392, 6293, 6195, 6096, 5997, 5898, 5799, 5700, + 5601, 5502, 5403, 5304, 5205, 5106, 5006, 4907, 4807, 4708, 4608, 4509, + 4409, 4310, 4210, 4110, 4011, 3911, 3811, 3711, 3611, 3511, 3411, 3311, + 3211, 3111, 3011, 2911, 2811, 2711, 2610, 2510, 2410, 2310, 2209, 2109, + 2009, 1908, 1808, 1708, 1607, 1507, 1406, 1306, 1206, 1105, 1005, 904, + 804, 703, 603, 502, 402, 301, 201, 100, 0, -100, -201, -301, + -402, -502, -603, -703, -804, -904, -1005, -1105, -1206, -1306, -1406, -1507, + -1607, -1708, -1808, -1908, -2009, -2109, -2209, -2310, -2410, -2510, -2610, -2711, + -2811, -2911, -3011, -3111, -3211, -3311, -3411, -3511, -3611, -3711, -3811, -3911, + -4011, -4110, -4210, -4310, -4409, -4509, -4608, -4708, -4807, -4907, -5006, -5106, + -5205, -5304, -5403, -5502, -5601, -5700, -5799, -5898, -5997, -6096, -6195, -6293, + -6392, -6491, -6589, -6688, -6786, -6884, -6982, -7081, -7179, -7277, -7375, -7473, + -7571, -7668, -7766, -7864, -7961, -8059, -8156, -8253, -8351, -8448, -8545, -8642, + -8739, -8836, -8932, -9029, -9126, -9222, -9319, -9415, -9511, -9607, -9703, -9799, + -9895, -9991, -10087, -10182, -10278, -10373, -10469, -10564, -10659, -10754, -10849, -10944, + -11038, -11133, -11227, -11322, -11416, -11510, -11604, -11698, -11792, -11886, -11980, -12073, + -12166, -12260, -12353, -12446, -12539, -12632, -12724, -12817, -12909, -13002, -13094, -13186, + -13278, -13370, -13462, -13553, -13645, -13736, -13827, -13918, -14009, -14100, -14191, -14281, + -14372, -14462, -14552, -14642, -14732, -14822, -14911, -15001, -15090, -15179, -15268, -15357, + -15446, -15534, -15623, -15711, -15799, -15887, -15975, -16063, -16150, -16238, -16325, -16412, + -16499, -16586, -16672, -16759, -16845, -16931, -17017, -17103, -17189, -17274, -17360, -17445, + -17530, -17615, -17699, -17784, -17868, -17952, -18036, -18120, -18204, -18287, -18371, -18454, + -18537, -18620, -18702, -18785, -18867, -18949, -19031, -19113, -19194, -19276, -19357, -19438, + -19519, -19599, -19680, -19760, -19840, -19920, -20000, -20079, -20159, -20238, -20317, -20396, + -20474, -20553, -20631, -20709, -20787, -20864, -20942, -21019, -21096, -21173, -21249, -21326, + -21402, -21478, -21554, -21629, -21705, -21780, -21855, -21930, -22004, -22079, -22153, -22227, + -22301, -22374, -22448, -22521, -22594, -22666, -22739, -22811, -22883, -22955, -23027, -23098, + -23169, -23240, -23311, -23382, -23452, -23522, -23592, -23661, -23731, -23800, -23869, -23938, + -24006, -24075, -24143, -24211, -24278, -24346, -24413, -24480, -24546, -24613, -24679, -24745, + -24811, -24877, -24942, -25007, -25072, -25136, -25201, -25265, -25329, -25392, -25456, -25519, + -25582, -25645, -25707, -25769, -25831, -25893, -25954, -26016, -26077, -26137, -26198, -26258, + -26318, -26378, -26437, -26497, -26556, -26615, -26673, -26731, -26789, -26847, -26905, -26962, + -27019, -27076, -27132, -27188, -27244, -27300, -27355, -27411, -27466, -27520, -27575, -27629, + -27683, -27736, -27790, -27843, -27896, -27948, -28001, -28053, -28105, -28156, -28208, -28259, + -28309, -28360, -28410, -28460, -28510, -28559, -28608, -28657, -28706, -28754, -28802, -28850, + -28897, -28945, -28992, -29038, -29085, -29131, -29177, -29222, -29268, -29313, -29358, -29402, + -29446, -29490, -29534, -29577, -29621, -29663, -29706, -29748, -29790, -29832, -29873, -29915, + -29955, -29996, -30036, -30076, -30116, -30156, -30195, -30234, -30272, -30311, -30349, -30386, + -30424, -30461, -30498, -30535, -30571, -30607, -30643, -30678, -30713, -30748, -30783, -30817, + -30851, -30885, -30918, -30951, -30984, -31017, -31049, -31081, -31113, -31144, -31175, -31206, + -31236, -31267, -31297, -31326, -31356, -31385, -31413, -31442, -31470, -31498, -31525, -31553, + -31580, -31606, -31633, -31659, -31684, -31710, -31735, -31760, -31785, -31809, -31833, -31856, + -31880, -31903, -31926, -31948, -31970, -31992, -32014, -32035, -32056, -32077, -32097, -32117, + -32137, -32156, -32176, -32194, -32213, -32231, -32249, -32267, -32284, -32301, -32318, -32334, + -32350, -32366, -32382, -32397, -32412, -32426, -32441, -32455, -32468, -32482, -32495, -32508, + -32520, -32532, -32544, -32556, -32567, -32578, -32588, -32599, -32609, -32618, -32628, -32637, + -32646, -32654, -32662, -32670, -32678, -32685, -32692, -32699, -32705, -32711, -32717, -32722, + -32727, -32732, -32736, -32740, -32744, -32748, -32751, -32754, -32757, -32759, -32761, -32763, + -32764, -32765, -32766, -32766, -32767, -32766, -32766, -32765, -32764, -32763, -32761, -32759, + -32757, -32754, -32751, -32748, -32744, -32740, -32736, -32732, -32727, -32722, -32717, -32711, + -32705, -32699, -32692, -32685, -32678, -32670, -32662, -32654, -32646, -32637, -32628, -32618, + -32609, -32599, -32588, -32578, -32567, -32556, -32544, -32532, -32520, -32508, -32495, -32482, + -32468, -32455, -32441, -32426, -32412, -32397, -32382, -32366, -32350, -32334, -32318, -32301, + -32284, -32267, -32249, -32231, -32213, -32194, -32176, -32156, -32137, -32117, -32097, -32077, + -32056, -32035, -32014, -31992, -31970, -31948, -31926, -31903, -31880, -31856, -31833, -31809, + -31785, -31760, -31735, -31710, -31684, -31659, -31633, -31606, -31580, -31553, -31525, -31498, + -31470, -31442, -31413, -31385, -31356, -31326, -31297, -31267, -31236, -31206, -31175, -31144, + -31113, -31081, -31049, -31017, -30984, -30951, -30918, -30885, -30851, -30817, -30783, -30748, + -30713, -30678, -30643, -30607, -30571, -30535, -30498, -30461, -30424, -30386, -30349, -30311, + -30272, -30234, -30195, -30156, -30116, -30076, -30036, -29996, -29955, -29915, -29873, -29832, + -29790, -29748, -29706, -29663, -29621, -29577, -29534, -29490, -29446, -29402, -29358, -29313, + -29268, -29222, -29177, -29131, -29085, -29038, -28992, -28945, -28897, -28850, -28802, -28754, + -28706, -28657, -28608, -28559, -28510, -28460, -28410, -28360, -28309, -28259, -28208, -28156, + -28105, -28053, -28001, -27948, -27896, -27843, -27790, -27736, -27683, -27629, -27575, -27520, + -27466, -27411, -27355, -27300, -27244, -27188, -27132, -27076, -27019, -26962, -26905, -26847, + -26789, -26731, -26673, -26615, -26556, -26497, -26437, -26378, -26318, -26258, -26198, -26137, + -26077, -26016, -25954, -25893, -25831, -25769, -25707, -25645, -25582, -25519, -25456, -25392, + -25329, -25265, -25201, -25136, -25072, -25007, -24942, -24877, -24811, -24745, -24679, -24613, + -24546, -24480, -24413, -24346, -24278, -24211, -24143, -24075, -24006, -23938, -23869, -23800, + -23731, -23661, -23592, -23522, -23452, -23382, -23311, -23240, -23169, -23098, -23027, -22955, + -22883, -22811, -22739, -22666, -22594, -22521, -22448, -22374, -22301, -22227, -22153, -22079, + -22004, -21930, -21855, -21780, -21705, -21629, -21554, -21478, -21402, -21326, -21249, -21173, + -21096, -21019, -20942, -20864, -20787, -20709, -20631, -20553, -20474, -20396, -20317, -20238, + -20159, -20079, -20000, -19920, -19840, -19760, -19680, -19599, -19519, -19438, -19357, -19276, + -19194, -19113, -19031, -18949, -18867, -18785, -18702, -18620, -18537, -18454, -18371, -18287, + -18204, -18120, -18036, -17952, -17868, -17784, -17699, -17615, -17530, -17445, -17360, -17274, + -17189, -17103, -17017, -16931, -16845, -16759, -16672, -16586, -16499, -16412, -16325, -16238, + -16150, -16063, -15975, -15887, -15799, -15711, -15623, -15534, -15446, -15357, -15268, -15179, + -15090, -15001, -14911, -14822, -14732, -14642, -14552, -14462, -14372, -14281, -14191, -14100, + -14009, -13918, -13827, -13736, -13645, -13553, -13462, -13370, -13278, -13186, -13094, -13002, + -12909, -12817, -12724, -12632, -12539, -12446, -12353, -12260, -12166, -12073, -11980, -11886, + -11792, -11698, -11604, -11510, -11416, -11322, -11227, -11133, -11038, -10944, -10849, -10754, + -10659, -10564, -10469, -10373, -10278, -10182, -10087, -9991, -9895, -9799, -9703, -9607, + -9511, -9415, -9319, -9222, -9126, -9029, -8932, -8836, -8739, -8642, -8545, -8448, + -8351, -8253, -8156, -8059, -7961, -7864, -7766, -7668, -7571, -7473, -7375, -7277, + -7179, -7081, -6982, -6884, -6786, -6688, -6589, -6491, -6392, -6293, -6195, -6096, + -5997, -5898, -5799, -5700, -5601, -5502, -5403, -5304, -5205, -5106, -5006, -4907, + -4807, -4708, -4608, -4509, -4409, -4310, -4210, -4110, -4011, -3911, -3811, -3711, + -3611, -3511, -3411, -3311, -3211, -3111, -3011, -2911, -2811, -2711, -2610, -2510, + -2410, -2310, -2209, -2109, -2009, -1908, -1808, -1708, -1607, -1507, -1406, -1306, + -1206, -1105, -1005, -904, -804, -703, -603, -502, -402, -301, -201, -100, + 0, 100, 201, 301, 402, 502, 603, 703, 804, 904, 1005, 1105, + 1206, 1306, 1406, 1507, 1607, 1708, 1808, 1908, 2009, 2109, 2209, 2310, + 2410, 2510, 2610, 2711, 2811, 2911, 3011, 3111, 3211, 3311, 3411, 3511, + 3611, 3711, 3811, 3911, 4011, 4110, 4210, 4310, 4409, 4509, 4608, 4708, + 4807, 4907, 5006, 5106, 5205, 5304, 5403, 5502, 5601, 5700, 5799, 5898, + 5997, 6096, 6195, 6293, 6392, 6491, 6589, 6688, 6786, 6884, 6982, 7081, + 7179, 7277, 7375, 7473, 7571, 7668, 7766, 7864, 7961, 8059, 8156, 8253, + 8351, 8448, 8545, 8642, 8739, 8836, 8932, 9029, 9126, 9222, 9319, 9415, + 9511, 9607, 9703, 9799, 9895, 9991, 10087, 10182, 10278, 10373, 10469, 10564, + 10659, 10754, 10849, 10944, 11038, 11133, 11227, 11322, 11416, 11510, 11604, 11698, + 11792, 11886, 11980, 12073, 12166, 12260, 12353, 12446, 12539, 12632, 12724, 12817, + 12909, 13002, 13094, 13186, 13278, 13370, 13462, 13553, 13645, 13736, 13827, 13918, + 14009, 14100, 14191, 14281, 14372, 14462, 14552, 14642, 14732, 14822, 14911, 15001, + 15090, 15179, 15268, 15357, 15446, 15534, 15623, 15711, 15799, 15887, 15975, 16063, + 16150, 16238, 16325, 16412, 16499, 16586, 16672, 16759, 16845, 16931, 17017, 17103, + 17189, 17274, 17360, 17445, 17530, 17615, 17699, 17784, 17868, 17952, 18036, 18120, + 18204, 18287, 18371, 18454, 18537, 18620, 18702, 18785, 18867, 18949, 19031, 19113, + 19194, 19276, 19357, 19438, 19519, 19599, 19680, 19760, 19840, 19920, 20000, 20079, + 20159, 20238, 20317, 20396, 20474, 20553, 20631, 20709, 20787, 20864, 20942, 21019, + 21096, 21173, 21249, 21326, 21402, 21478, 21554, 21629, 21705, 21780, 21855, 21930, + 22004, 22079, 22153, 22227, 22301, 22374, 22448, 22521, 22594, 22666, 22739, 22811, + 22883, 22955, 23027, 23098, 23169, 23240, 23311, 23382, 23452, 23522, 23592, 23661, + 23731, 23800, 23869, 23938, 24006, 24075, 24143, 24211, 24278, 24346, 24413, 24480, + 24546, 24613, 24679, 24745, 24811, 24877, 24942, 25007, 25072, 25136, 25201, 25265, + 25329, 25392, 25456, 25519, 25582, 25645, 25707, 25769, 25831, 25893, 25954, 26016, + 26077, 26137, 26198, 26258, 26318, 26378, 26437, 26497, 26556, 26615, 26673, 26731, + 26789, 26847, 26905, 26962, 27019, 27076, 27132, 27188, 27244, 27300, 27355, 27411, + 27466, 27520, 27575, 27629, 27683, 27736, 27790, 27843, 27896, 27948, 28001, 28053, + 28105, 28156, 28208, 28259, 28309, 28360, 28410, 28460, 28510, 28559, 28608, 28657, + 28706, 28754, 28802, 28850, 28897, 28945, 28992, 29038, 29085, 29131, 29177, 29222, + 29268, 29313, 29358, 29402, 29446, 29490, 29534, 29577, 29621, 29663, 29706, 29748, + 29790, 29832, 29873, 29915, 29955, 29996, 30036, 30076, 30116, 30156, 30195, 30234, + 30272, 30311, 30349, 30386, 30424, 30461, 30498, 30535, 30571, 30607, 30643, 30678, + 30713, 30748, 30783, 30817, 30851, 30885, 30918, 30951, 30984, 31017, 31049, 31081, + 31113, 31144, 31175, 31206, 31236, 31267, 31297, 31326, 31356, 31385, 31413, 31442, + 31470, 31498, 31525, 31553, 31580, 31606, 31633, 31659, 31684, 31710, 31735, 31760, + 31785, 31809, 31833, 31856, 31880, 31903, 31926, 31948, 31970, 31992, 32014, 32035, + 32056, 32077, 32097, 32117, 32137, 32156, 32176, 32194, 32213, 32231, 32249, 32267, + 32284, 32301, 32318, 32334, 32350, 32366, 32382, 32397, 32412, 32426, 32441, 32455, + 32468, 32482, 32495, 32508, 32520, 32532, 32544, 32556, 32567, 32578, 32588, 32599, + 32609, 32618, 32628, 32637, 32646, 32654, 32662, 32670, 32678, 32685, 32692, 32699, + 32705, 32711, 32717, 32722, 32727, 32732, 32736, 32740, 32744, 32748, 32751, 32754, + 32757, 32759, 32761, 32763, 32764, 32765, 32766, 32766}}; diff --git a/game/sound/989snd/loader.cpp b/game/sound/989snd/loader.cpp index 237fec6af..832f2e316 100644 --- a/game/sound/989snd/loader.cpp +++ b/game/sound/989snd/loader.cpp @@ -6,9 +6,11 @@ #include #include "midi_handler.h" +#include "sfxblock.h" #include "common/log/log.h" +#include "sfxblock2.h" #include namespace snd { @@ -16,71 +18,6 @@ enum chunk : u32 { bank, samples, midi }; #define FOURCC(a, b, c, d) ((u32)(((d) << 24) | ((c) << 16) | ((b) << 8) | (a))) -u32 loader::read_music_bank(SoundBankData* data) { - u32 handle = m_id_allocator.get_id(); - - auto bank = std::make_unique(*this); - - auto sound = (MIDISound*)((uintptr_t)data + data->FirstSound); - for (int i = 0; i < data->NumSounds; i++) { - bank->sounds.emplace_back(sound[i]); - } - - auto progdata = (ProgData*)((uintptr_t)data + data->FirstProg); - for (int i = 0; i < data->NumProgs; i++) { - Prog prog; - prog.d = progdata[i]; - bank->programs.emplace_back(std::move(prog)); - } - - for (auto& prog : bank->programs) { - auto tonedata = (Tone*)((uintptr_t)data + prog.d.FirstTone); - for (int i = 0; i < prog.d.NumTones; i++) { - Tone tone = tonedata[i]; - tone.BankID = handle; - prog.tones.emplace_back(tone); - } - } - - bank->type = BankType::Music; - - bank->bank_id = handle; - bank->bank_name = data->BankID; - m_soundbanks.emplace(handle, std::move(bank)); - - return handle; -} - -u32 loader::read_sfx_bank(SFXBlockData* data) { - u32 handle = m_id_allocator.get_id(); - - auto bank = std::make_unique(*this); - - auto sounddata = (SFXData*)((uintptr_t)data + data->FirstSound); - for (int i = 0; i < data->NumSounds; i++) { - SFX sound; - sound.d = sounddata[i]; - bank->sounds.push_back(sound); - } - - for (auto& sound : bank->sounds) { - auto graindata = (SFXGrain*)((uintptr_t)data + data->FirstGrain + sound.d.FirstGrain); - for (int i = 0; i < sound.d.NumGrains; i++) { - SFXGrain grain = graindata[i]; - if (grain.Type == 1) { - grain.GrainParams.tone.BankID = handle; - } - sound.grains.push_back(grain); - } - } - - bank->type = BankType::SFX; - - bank->bank_id = handle; - m_soundbanks.emplace(handle, std::move(bank)); - return handle; -} - u32 loader::read_bank(std::fstream& in) { size_t origin = in.tellg(); FileAttributes<3> attr; @@ -91,27 +28,38 @@ u32 loader::read_bank(std::fstream& in) { return -1; } + /* + * if there's midi data the pointer to the allocated memory is stored + * just before the sound bank data... if (attr.num_chunks > 2) { - // Fix for bugged tooling I assume? attr.where[chunk::bank].size += 4; } + */ // auto pos = in.tellg(); auto bank_buf = std::make_unique(attr.where[chunk::bank].size); in.seekg(origin + attr.where[chunk::bank].offset, std::fstream::beg); in.read((char*)bank_buf.get(), attr.where[chunk::bank].size); - auto bank = (BankTag*)bank_buf.get(); + auto bank_tag = (BankTag*)bank_buf.get(); - u32 bank_id = 0; + u32 bank_id = m_id_allocator.get_id(); + std::unique_ptr bank; - if (bank->DataID == FOURCC('S', 'B', 'v', '2')) { - bank_id = read_music_bank((SoundBankData*)bank_buf.get()); - } else if (bank->DataID == FOURCC('S', 'B', 'l', 'k')) { - bank_id = read_sfx_bank((SFXBlockData*)bank_buf.get()); + if (bank_tag->DataID == FOURCC('S', 'B', 'v', '2')) { + bank = std::make_unique(*this, bank_id, bank_tag); + } else if (bank_tag->DataID == FOURCC('S', 'B', 'l', 'k')) { + if (bank_tag->Version < 2) { + bank = std::make_unique(*this, bank_id, bank_tag); + } else { + bank = std::make_unique(*this, bank_id, bank_tag); + } } else { + m_id_allocator.free_id(bank_id); throw std::runtime_error("Unknown bank ID, bad file?"); } + m_soundbanks.emplace(bank_id, std::move(bank)); + if (attr.num_chunks >= 2) { in.seekg(origin + attr.where[chunk::samples].offset, std::fstream::beg); auto samples = std::make_unique(attr.where[chunk::samples].size); @@ -152,7 +100,7 @@ SoundBank* loader::get_bank_by_handle(u32 id) { return m_soundbanks[id].get(); } -MusicBank* loader::get_bank_by_name(u32 id) { +MusicBank* loader::get_bank_by_id(u32 id) { for (auto& b : m_soundbanks) { if (b.second->type == BankType::Music) { auto* bank = static_cast(b.second.get()); @@ -165,6 +113,30 @@ MusicBank* loader::get_bank_by_name(u32 id) { return nullptr; } +SoundBank* loader::get_bank_by_name(const char* name) { + for (auto& b : m_soundbanks) { + auto bankname = b.second->get_name(); + if (bankname.has_value()) { + if (bankname->compare(name) == 0) { + return b.second.get(); + } + } + } + + return nullptr; +} + +SoundBank* loader::get_bank_with_sound(const char* name) { + for (auto& b : m_soundbanks) { + auto sound = b.second->get_sound_by_name(name); + if (sound.has_value()) { + return b.second.get(); + } + } + + return nullptr; +} + MIDIBlock* loader::get_midi(u32 id) { return m_midi.at(id); } diff --git a/game/sound/989snd/loader.h b/game/sound/989snd/loader.h index 435d9c1dc..af345605c 100644 --- a/game/sound/989snd/loader.h +++ b/game/sound/989snd/loader.h @@ -33,10 +33,13 @@ struct FileAttributes { class loader : public locator { public: SoundBank* get_bank_by_handle(u32 id) override; - MusicBank* get_bank_by_name(u32 id) override; + MusicBank* get_bank_by_id(u32 id) override; MIDIBlock* get_midi(u32 id) override; u8* get_bank_samples(u32 id) override; + SoundBank* get_bank_by_name(const char* name); + SoundBank* get_bank_with_sound(const char* name); + void unload_bank(u32 id); u32 read_bank(std::fstream& in); @@ -46,8 +49,6 @@ class loader : public locator { private: void load_samples(u32 bank, std::unique_ptr samples); - u32 read_music_bank(SoundBankData* data); - u32 read_sfx_bank(SFXBlockData* data); id_allocator m_id_allocator; std::unordered_map> m_soundbanks; diff --git a/game/sound/989snd/locator.h b/game/sound/989snd/locator.h index 77c1257de..103d8f1ce 100644 --- a/game/sound/989snd/locator.h +++ b/game/sound/989snd/locator.h @@ -12,7 +12,7 @@ class locator { public: virtual ~locator() = default; virtual SoundBank* get_bank_by_handle(u32 id) = 0; - virtual MusicBank* get_bank_by_name(u32 id) = 0; + virtual MusicBank* get_bank_by_id(u32 id) = 0; virtual u8* get_bank_samples(u32 id) = 0; virtual MIDIBlock* get_midi(u32 id) = 0; }; diff --git a/game/sound/989snd/midi_handler.cpp b/game/sound/989snd/midi_handler.cpp index f89155116..f30880cc5 100644 --- a/game/sound/989snd/midi_handler.cpp +++ b/game/sound/989snd/midi_handler.cpp @@ -6,6 +6,7 @@ #include "common/log/log.h" +#include "game/sound/989snd/util.h" #include namespace snd { @@ -26,7 +27,7 @@ midi_handler::midi_handler(MIDIBlockHeader* block, s32 vol, s32 pan, locator& loc, - u32 bank) + SoundBank& bank) : m_sound(sound), m_locator(loc), m_repeats(sound.Repeats), @@ -57,7 +58,7 @@ midi_handler::midi_handler(MIDIBlockHeader* block, s32 vol, s32 pan, locator& loc, - u32 bank, + SoundBank& bank, std::optional parent) : m_parent(parent), m_sound(sound), @@ -140,7 +141,21 @@ void midi_handler::set_vol_pan(s32 vol, s32 pan) { } void midi_handler::set_pmod(s32 mod) { - // TODO + m_cur_pm = mod; + + for (auto& v : m_voices) { + auto voice = v.lock(); + if (voice == nullptr) { + continue; + } + + voice->current_pm = m_cur_pm; + auto note = pitchbend(voice->tone, voice->current_pb, voice->current_pm, voice->start_note, + voice->start_fine); + auto pitch = + PS1Note2Pitch(voice->tone.CenterNote, voice->tone.CenterFine, note.first, note.second); + voice->set_pitch(pitch); + } } void midi_handler::mute_channel(u8 channel) { @@ -172,8 +187,8 @@ void midi_handler::note_on() { // velocity); // Key on all the applicable tones for the program - auto bank = dynamic_cast(m_locator.get_bank_by_name(m_header->BankID)); - auto& program = bank->programs[m_programs[channel]]; + auto bank = dynamic_cast(m_locator.get_bank_by_id(m_header->BankID)); + auto& program = bank->m_programs[m_programs[channel]]; for (auto& t : program.tones) { if (note >= t.MapLow && note <= t.MapHigh) { @@ -192,12 +207,11 @@ void midi_handler::note_on() { voice->start_note = note; voice->start_fine = 0; - // TODO - // voice->current_pm = 0; - // voice->current_pb = 0; + voice->current_pm = m_pitch_bend[channel]; + voice->current_pb = m_cur_pm; voice->group = m_sound.VolGroup; - m_vm.start_tone(voice); + m_vm.start_tone(voice, m_bank.bank_id); m_voices.emplace_front(voice); } } @@ -260,10 +274,26 @@ void midi_handler::channel_pressure() { void midi_handler::channel_pitch() { u8 channel = m_status & 0xF; - u32 pitch = (m_seq_ptr[0] << 7) | m_seq_ptr[1]; - (void)pitch; - (void)channel; + s32 pitch = 0xFFFF * ((m_seq_ptr[0] & 0x7f) | ((m_seq_ptr[1] & 0x7f) << 7)) / 0x3FFF; // lg::debug("{}: pitch ch{:01x} {:04x}", m_time, channel, pitch); + + m_pitch_bend[channel] = pitch + 0x8000; + for (auto& v : m_voices) { + auto voice = v.lock(); + if (voice == nullptr) { + continue; + } + + if (voice->channel == channel) { + voice->current_pb = m_pitch_bend[channel]; + auto note = pitchbend(voice->tone, voice->current_pb, voice->current_pm, voice->start_note, + voice->start_fine); + auto pitch = + PS1Note2Pitch(voice->tone.CenterNote, voice->tone.CenterFine, note.first, note.second); + voice->set_pitch(pitch); + } + } + m_seq_ptr += 2; } @@ -289,6 +319,7 @@ void midi_handler::meta_event() { if (*m_seq_ptr == 0x51) { m_tempo = (m_seq_ptr[2] << 16) | (m_seq_ptr[3] << 8) | (m_seq_ptr[4]); + m_ppt = 100 * mics_per_tick / (m_tempo / m_ppq); } m_seq_ptr += len + 2; diff --git a/game/sound/989snd/midi_handler.h b/game/sound/989snd/midi_handler.h index ed2714595..72d8c4ee7 100644 --- a/game/sound/989snd/midi_handler.h +++ b/game/sound/989snd/midi_handler.h @@ -45,7 +45,7 @@ class midi_handler : public sound_handler { s32 vol, s32 pan, locator& loc, - u32 bank); + SoundBank& bank); midi_handler(MIDIBlockHeader* block, voice_manager& vm, @@ -53,7 +53,7 @@ class midi_handler : public sound_handler { s32 vol, s32 pan, locator& loc, - u32 bank, + SoundBank& bank, std::optional parent); ~midi_handler() override { @@ -69,7 +69,7 @@ class midi_handler : public sound_handler { bool tick() override; void mute_channel(u8 channel); void unmute_channel(u8 channel); - u32 bank() override { return m_bank; }; + SoundBank& bank() override { return m_bank; }; void pause() override; void stop() override; @@ -98,8 +98,9 @@ class midi_handler : public sound_handler { locator& m_locator; s32 m_vol{0x7f}; s32 m_pan{0}; + s32 m_cur_pm{0}; s8 m_repeats{0}; - u32 m_bank; + SoundBank& m_bank; bool m_paused{false}; @@ -108,6 +109,7 @@ class midi_handler : public sound_handler { std::array m_mute_state{}; std::array m_chanvol{}; std::array m_chanpan{}; + std::array m_pitch_bend{}; u8* m_sample_data{nullptr}; u8* m_seq_data_start{nullptr}; diff --git a/game/sound/989snd/musicbank.cpp b/game/sound/989snd/musicbank.cpp index d85dcb77f..e0672000d 100644 --- a/game/sound/989snd/musicbank.cpp +++ b/game/sound/989snd/musicbank.cpp @@ -6,21 +6,48 @@ #include "../common/synth.h" namespace snd { + +MusicBank::MusicBank(locator& loc, u32 id, BankTag* tag) + : SoundBank(id, BankType::Music), m_locator(loc) { + auto data = (SoundBankData*)tag; + + auto sound = (MIDISound*)((uintptr_t)data + data->FirstSound); + for (int i = 0; i < data->NumSounds; i++) { + m_sounds.emplace_back(sound[i]); + } + + auto progdata = (ProgData*)((uintptr_t)data + data->FirstProg); + for (int i = 0; i < data->NumProgs; i++) { + Prog prog; + prog.d = progdata[i]; + m_programs.emplace_back(std::move(prog)); + } + + for (auto& prog : m_programs) { + auto tonedata = (Tone*)((uintptr_t)data + prog.d.FirstTone); + for (int i = 0; i < prog.d.NumTones; i++) { + prog.tones.emplace_back(tonedata[i]); + } + } + + bank_name = data->BankID; +} + std::unique_ptr MusicBank::make_handler(voice_manager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb) { - auto& sound = sounds[sound_id]; + auto& sound = m_sounds[sound_id]; std::unique_ptr handler; if (sound.Type == 4) { // midi auto midi = static_cast(m_locator.get_midi(sound.MIDIID)); - handler = std::make_unique(midi, vm, sound, vol, pan, m_locator, bank_id); + handler = std::make_unique(midi, vm, sound, vol, pan, m_locator, *this); } else if (sound.Type == 5) { // ame auto midi = static_cast(m_locator.get_midi(sound.MIDIID)); - handler = std::make_unique(midi, vm, sound, vol, pan, m_locator, bank_id); + handler = std::make_unique(midi, vm, sound, vol, pan, m_locator, *this); } else { // error } @@ -28,4 +55,12 @@ std::unique_ptr MusicBank::make_handler(voice_manager& vm, return handler; } +std::unique_ptr MusicBank::make_handler(voice_manager& vm, + u32 sound_id, + s32 vol, + s32 pan, + SndPlayParams& params) { + return nullptr; +} + } // namespace snd diff --git a/game/sound/989snd/musicbank.h b/game/sound/989snd/musicbank.h index 703344027..b9918bc87 100644 --- a/game/sound/989snd/musicbank.h +++ b/game/sound/989snd/musicbank.h @@ -60,7 +60,7 @@ struct MIDISound { struct Prog; class MusicBank : public SoundBank { public: - MusicBank(locator& loc) : m_locator(loc) {} + MusicBank(locator& loc, u32 id, BankTag* tag); std::unique_ptr make_handler(voice_manager& vm, u32 sound_id, s32 vol, @@ -68,8 +68,14 @@ class MusicBank : public SoundBank { s32 pm, s32 pb) override; - std::vector programs; - std::vector sounds; + std::unique_ptr make_handler(voice_manager& vm, + u32 sound_id, + s32 vol, + s32 pan, + SndPlayParams& params) override; + + std::vector m_programs; + std::vector m_sounds; private: locator& m_locator; diff --git a/game/sound/989snd/player.cpp b/game/sound/989snd/player.cpp index 750d83f79..df1999902 100644 --- a/game/sound/989snd/player.cpp +++ b/game/sound/989snd/player.cpp @@ -143,6 +143,38 @@ u32 player::play_sound(u32 bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 return handle; } +u32 player::play_sound_by_name(u32 bank_id, + char* bank_name, + char* sound_name, + s32 vol, + s32 pan, + s32 pm, + s32 pb) { + std::scoped_lock lock(m_ticklock); + SoundBank* bank = nullptr; + if (bank_id == 0 && bank_name != nullptr) { + bank = m_loader.get_bank_by_name(bank_name); + } else if (bank_id != 0) { + bank = m_loader.get_bank_by_handle(bank_id); + } else { + bank = m_loader.get_bank_with_sound(sound_name); + } + + if (bank == nullptr) { + //lg::error("play_sound_by_name: failed to find bank for sound {}", sound_name); + return 0; + } + + auto sound = bank->get_sound_by_name(sound_name); + if (sound.has_value()) { + return play_sound(bank->bank_id, sound.value(), vol, pan, pm, pb); + } + + //lg::error("play_sound_by_name: failed to find sound {}", sound_name); + + return 0; +} + void player::stop_sound(u32 sound_id) { std::scoped_lock lock(m_ticklock); auto handler = m_handlers.find(sound_id); @@ -155,7 +187,7 @@ void player::stop_sound(u32 sound_id) { // m_handlers.erase(sound_id); } -void player::set_midi_reg(u32 sound_id, u8 reg, u8 value) { +void player::set_sound_reg(u32 sound_id, u8 reg, u8 value) { std::scoped_lock lock(m_ticklock); if (m_handlers.find(sound_id) == m_handlers.end()) { // fmt::print("set_midi_reg: Handler {} does not exist\n", sound_id); @@ -210,7 +242,7 @@ void player::unload_bank(u32 bank_handle) { return; for (auto it = m_handlers.begin(); it != m_handlers.end();) { - if (it->second->bank() == bank_handle) { + if (it->second->bank().bank_id == bank_handle) { m_handle_allocator.free_id(it->first); it = m_handlers.erase(it); } else { @@ -285,4 +317,54 @@ void player::set_sound_pmod(s32 sound_handle, s32 mod) { handler->second->set_pmod(mod); } + +void player::stop_all_sounds() { + for (auto it = m_handlers.begin(); it != m_handlers.end();) { + m_handle_allocator.free_id(it->first); + it = m_handlers.erase(it); + } +} + +s32 player::get_sound_user_data(s32 block_handle, + char* block_name, + s32 sound_id, + char* sound_name, + SFXUserData* dst) { + std::scoped_lock lock(m_ticklock); + SoundBank* bank = nullptr; + if (block_handle == 0 && block_name != nullptr) { + bank = m_loader.get_bank_by_name(block_name); + } else if (block_handle != 0) { + bank = m_loader.get_bank_by_handle(block_handle); + } else { + bank = m_loader.get_bank_with_sound(sound_name); + } + + if (bank == nullptr) { + return 0; + } + + if (sound_id == -1) { + auto sound = bank->get_sound_by_name(sound_name); + if (sound.has_value()) { + sound_id = sound.value(); + } else { + return 0; + } + } + + auto ud = bank->get_sound_user_data(sound_id); + if (ud.has_value()) { + dst->data[0] = ud.value()->data[0]; + dst->data[1] = ud.value()->data[1]; + dst->data[2] = ud.value()->data[2]; + dst->data[3] = ud.value()->data[3]; + return 1; + } else { + return 0; + } + + return 0; +} + } // namespace snd diff --git a/game/sound/989snd/player.h b/game/sound/989snd/player.h index 491fdc0c2..b44379b84 100644 --- a/game/sound/989snd/player.h +++ b/game/sound/989snd/player.h @@ -36,7 +36,14 @@ class player { u32 load_bank(fs::path& path, size_t offset); u32 play_sound(u32 bank, u32 sound, s32 vol, s32 pan, s32 pm, s32 pb); - void set_midi_reg(u32 sound_id, u8 reg, u8 value); + u32 play_sound_by_name(u32 bank, + char* bank_name, + char* sound_name, + s32 vol, + s32 pan, + s32 pm, + s32 pb); + void set_sound_reg(u32 sound_id, u8 reg, u8 value); bool sound_still_active(u32 sound_id); void set_master_volume(u32 group, s32 volume); void unload_bank(u32 bank_handle); @@ -53,6 +60,12 @@ class player { void init_cubeb(); void destroy_cubeb(); s32 get_tick() { return m_tick; }; + void stop_all_sounds(); + s32 get_sound_user_data(s32 block_handle, + char* block_name, + s32 sound_id, + char* sound_name, + SFXUserData* dst); private: std::recursive_mutex m_ticklock; // TODO does not need to recursive with some light restructuring diff --git a/game/sound/989snd/sfxblock.cpp b/game/sound/989snd/sfxblock.cpp index 96c719661..90f3e0bf9 100644 --- a/game/sound/989snd/sfxblock.cpp +++ b/game/sound/989snd/sfxblock.cpp @@ -1,25 +1,48 @@ #include "sfxblock.h" #include "blocksound_handler.h" +#include "sfxgrain.h" + +#include "common/log/log.h" namespace snd { + +SFXBlock::SFXBlock(locator& loc, u32 id, BankTag* tag) + : SoundBank(id, BankType::SFX), m_locator(loc) { + auto data = (SFXBlockData*)tag; + + auto sounddata = (SFX2Data*)((uintptr_t)data + data->FirstSound); + for (int i = 0; i < data->NumSounds; i++) { + SFX2 sound; + sound.index = i; + sound.d = sounddata[i]; + m_sounds.push_back(std::move(sound)); + } + + for (auto& sound : m_sounds) { + auto graindata = (SFXGrain*)((uintptr_t)data + data->FirstGrain + sound.d.FirstGrain); + for (int i = 0; i < sound.d.NumGrains; i++) { + SFXGrain& grain = graindata[i]; + sound.grains.push_back(new_grain((grain_type)grain.Type, grain)); + } + } +} + std::unique_ptr SFXBlock::make_handler(voice_manager& vm, u32 sound_id, s32 vol, s32 pan, - s32 pm, - s32 pb) { - std::unique_ptr handler; - auto& SFX = sounds[sound_id]; + SndPlayParams& params) { + auto& SFX = m_sounds[sound_id]; if (SFX.grains.empty()) { // fmt::print("skipping empty sfx\n"); return nullptr; } - handler = std::make_unique(sounds[sound_id], vm, vol, pan, pm, pb, bank_id); + auto handler = + std::make_unique(*this, m_sounds[sound_id], vm, vol, pan, params); handler->init(); return handler; } - } // namespace snd diff --git a/game/sound/989snd/sfxblock.h b/game/sound/989snd/sfxblock.h index 83cae1838..5d62fbeba 100644 --- a/game/sound/989snd/sfxblock.h +++ b/game/sound/989snd/sfxblock.h @@ -1,8 +1,11 @@ #pragma once #include +#include "sfxgrain.h" #include "soundbank.h" +#include "sfxblock2.h" + namespace snd { struct SFXBlockData : BankTag { @@ -19,76 +22,14 @@ struct SFXBlockData : BankTag { /* 28 */ u32 VagDataSize; /* 2c */ u32 SRAMAllocSize; /* 30 */ u32 NextBlock; + + /* these last ones are probably not in jak1? */ /* 34 */ u32 BlockNames; /* 38 */ u32 SFXUD; }; static_assert(sizeof(SFXBlockData) == 0x38 + 4); -struct XREFGrainParams { - /* 0 */ u32 BankID; - /* 4 */ u32 SoundIndex; - /* 8 */ s32 PitchMod; - /* c */ u32 Flags; -}; - -struct RandDelayParams { - /* 0 */ s32 Amount; -}; - -struct ControlParams { - /* 0 */ s16 param[4]; -}; - -struct LFOParams { - /* 0 */ u8 which_lfo; - /* 1 */ u8 target; - /* 2 */ u8 target_extra; - /* 3 */ u8 shape; - /* 4 */ u16 duty_cycle; - /* 6 */ u16 depth; - /* 8 */ u16 flags; - /* a */ u16 start_offset; - /* c */ u32 step_size; -}; - -struct PlaySoundParams { - /* 0 */ s32 vol; - /* 4 */ s32 pan; - /* 8 */ s8 reg_settings[4]; - /* c */ s32 sound_id; - /* 10 */ char snd_name[16]; -}; - -struct PluginParams { - /* 0 */ u32 id; - /* 4 */ u32 index; - /* 8 */ u8 data[24]; -}; - -struct LargestGrainParamStruct { - /* 0 */ char blank[32]; -}; - -/* -** Type 1 = Tone -*/ - -struct SFXGrain { - /* 0 */ u32 Type; - /* 4 */ s32 Delay; - union { - /* 8 */ Tone tone; - /* 8 */ XREFGrainParams xref; - /* 8 */ RandDelayParams delay; - /* 8 */ ControlParams control; - /* 8 */ LFOParams lfo; - /* 8 */ PlaySoundParams play_sound; - /* 8 */ PluginParams plugin_params; - /* 8 */ LargestGrainParamStruct junk; - } GrainParams; -}; - struct SFXData { /* 0 */ s8 Vol; /* 1 */ s8 VolGroup; @@ -106,23 +47,21 @@ enum SFXFlags { struct SFX { SFXData d; - std::vector grains; + std::vector> grains; }; class SFXBlock : public SoundBank { public: - SFXBlock(locator& loc) : m_locator(loc) {} + SFXBlock(locator& loc, u32 handle, BankTag* tag); std::unique_ptr make_handler(voice_manager& vm, u32 sound_id, s32 vol, s32 pan, - s32 pm, - s32 pb) override; - - std::vector sounds; + SndPlayParams& params) override; private: locator& m_locator; + std::vector m_sounds; }; } // namespace snd diff --git a/game/sound/989snd/sfxblock2.cpp b/game/sound/989snd/sfxblock2.cpp new file mode 100644 index 000000000..10c35256a --- /dev/null +++ b/game/sound/989snd/sfxblock2.cpp @@ -0,0 +1,103 @@ +#include "sfxblock2.h" + +#include "blocksound_handler.h" + +#include "common/log/log.h" + +namespace snd { +SFXBlock2::SFXBlock2(locator& loc, u32 id, BankTag* tag) + : SoundBank(id, BankType::SFX), m_locator(loc) { + auto data = (SFXBlockData2*)tag; + + auto sounddata = (SFX2Data*)((uintptr_t)data + data->FirstSound); + auto userdata = (SFXUserData*)((uintptr_t)data + data->SFXUD); + for (int i = 0; i < data->NumSounds; i++) { + SFX2 sound; + sound.index = i; + sound.d = sounddata[i]; + sound.user_data = userdata[i]; + m_sounds.push_back(std::move(sound)); + } + + for (auto& sound : m_sounds) { + auto graindata = (SFXGrain2*)((uintptr_t)data + data->FirstGrain + sound.d.FirstGrain); + for (int i = 0; i < sound.d.NumGrains; i++) { + SFXGrain2& grain = graindata[i]; + sound.grains.push_back(new_grain((grain_type)grain.OpcodeData.type, grain, + (u8*)((uintptr_t)data + data->GrainData))); + } + } + + auto names = (SFXBlockNames*)((uintptr_t)data + data->BlockNames); + char buf[8]; + strncpy(buf, (char*)names->BlockName, 8); + m_name = buf; + + if (names->SFXNameTableOffset != 0) { + // The sound names are hashed and divided up into 32 buckets + // to reduce the number of comparisons needed to search the list. + // An empty name entry signifies the end of each bucket. + // Let's go through all the buckets and collect the names. + + auto name_table = (SFXName*)((uintptr_t)names + names->SFXNameTableOffset); + for (int i = 0; i < 32; i++) { + auto name = &name_table[names->SFXHashOffsets[i]]; + while (name->Name[0] != 0) { + char buf[16]; + strncpy(buf, (char*)name->Name, 16); + + std::string str(buf); + m_names[str] = name->Index; + m_sounds.at(name->Index).name = str; + + name++; + } + } + } + + auto idx = 0; + for (auto& s : m_sounds) { + lg::warn("sound {} : {}", idx, s.name); + idx++; + } +} + +std::unique_ptr SFXBlock2::make_handler(voice_manager& vm, + u32 sound_id, + s32 vol, + s32 pan, + SndPlayParams& params) { + if (sound_id >= m_sounds.size()) { + lg::error("out of bounds sound_id"); + return nullptr; + } + + auto& SFX = m_sounds[sound_id]; + + if (SFX.grains.empty()) { + // fmt::print("skipping empty sfx\n"); + return nullptr; + } + + lg::info("playing sound: {}", SFX.name); + auto handler = + std::make_unique(*this, m_sounds[sound_id], vm, vol, pan, params); + handler->init(); + return handler; +} + +std::optional SFXBlock2::get_sound_by_name(const char* name) { + // lg::error("searching for sound {}", name); + // for (auto& s : m_names) { + // lg::error("{}", s.first); + // } + + auto sound = m_names.find(name); + if (sound != m_names.end()) { + return sound->second; + } + + return std::nullopt; +} + +} // namespace snd diff --git a/game/sound/989snd/sfxblock2.h b/game/sound/989snd/sfxblock2.h new file mode 100644 index 000000000..f133f9246 --- /dev/null +++ b/game/sound/989snd/sfxblock2.h @@ -0,0 +1,115 @@ +#pragma once +#include + +#include "sfxgrain.h" +#include "soundbank.h" + +namespace snd { + +enum class SFX2BlockFlags : u32 { + HasBlockNames = 1 << 8, +}; + +struct SFXBlockData2 : BankTag { + /* 10 */ s8 BlockNum; + /* 11 */ s8 pad1; + /* 12 */ s16 pad2; + /* 14 */ s16 pad3; + /* 16 */ s16 NumSounds; + /* 18 */ s16 NumGrains; + /* 1a */ s16 NumVAGs; + /* 1c */ /* SFX2Ptr */ u32 FirstSound; + /* 20 */ /* SFXGrain2Ptr */ u32 FirstGrain; + /* 24 */ u32 VagsInSR; + /* 28 */ u32 VagDataSize; + /* 2c */ u32 SRAMAllocSize; + /* 30 */ u32 NextBlock; + /* 34 */ u32 GrainData; // new + /* 38 */ /* SFXBlockNames* */ u32 BlockNames; + /* 3c */ /* SFXUserData* */ u32 SFXUD; +}; + +static_assert(sizeof(SFXBlockData2) == 0x3c + 4); + +struct SFXUserData { + /* 0 */ u32 data[4]; +}; + +struct SFXName { + /* 0 */ u32 Name[4]; + /* 10 */ s16 Index; + /* 12 */ s16 reserved; +}; + +struct VAGName { + /* 0 */ u32 Name[4]; + /* 10 */ u32 Offset; + /* 14 */ u32 res1; + /* 18 */ u32 res2; +}; + +struct VAGImport { + /* 0 */ u32 BlockName[2]; + /* 8 */ u32 VAGName[4]; + /* 18 */ u32 VAGLocation; + /* 1c */ u32 VAGSR; +}; + +struct VAGExport { + /* 0 */ u32 VAGName[4]; + /* 10 */ u32 VAGLocation; + /* 14 */ u32 VAGSR; +}; + +struct SFXBlockNames { + /* 0 */ u32 BlockName[2]; + /* 8 */ u32 SFXNameTableOffset; + /* c */ u32 VAGNameTableOffset; + /* 10 */ u32 VAGImportsTableOffset; + /* 14 */ u32 VAGExportsTableOffset; + /* 18 */ s16 SFXHashOffsets[32]; + /* 58 */ s16 VAGHashOffsets[32]; +}; + +struct SFX2Data { + /* 0 */ s8 Vol; + /* 1 */ s8 VolGroup; + /* 2 */ s16 Pan; + /* 4 */ s8 NumGrains; + /* 5 */ s8 InstanceLimit; + /* 6 */ u16 Flags; + /* 8 */ u32 FirstGrain; +}; + +struct SFX2 { + SFX2Data d; + std::string name; + std::vector> grains; + SFXUserData user_data; + int index; +}; + +class SFXBlock2 : public SoundBank { + public: + SFXBlock2(locator& loc, u32 handle, BankTag* tag); + std::unique_ptr make_handler(voice_manager& vm, + u32 sound_id, + s32 vol, + s32 pan, + SndPlayParams& params) override; + + std::optional get_name() override { return m_name; }; + std::optional get_sound_by_name(const char* name) override; + + std::optional get_sound_user_data(u32 sound_id) override { + return &m_sounds.at(sound_id).user_data; + }; + + private: + locator& m_locator; + std::string m_name; + std::unordered_map m_names; + std::vector m_sounds; +}; + +} // namespace snd diff --git a/game/sound/989snd/sfxgrain.cpp b/game/sound/989snd/sfxgrain.cpp new file mode 100644 index 000000000..32c2b0ca2 --- /dev/null +++ b/game/sound/989snd/sfxgrain.cpp @@ -0,0 +1,614 @@ +#include "sfxgrain.h" + +#include "blocksound_handler.h" +#include "lfo.h" + +#include "common/log/log.h" + +namespace snd { + +SFXGrain_Tone::SFXGrain_Tone(SFXGrain& grain) : Grain(grain), m_tone(grain.GrainParams.tone) {} +SFXGrain_Tone::SFXGrain_Tone(SFXGrain2& grain, u8* data) : Grain(grain) { + m_tone = *(Tone*)(data + (grain.OpcodeData.Opcode & 0xFFFFFF)); +} + +s32 SFXGrain_Tone::execute(blocksound_handler& handler) { + handler.m_cur_volume = + ((handler.m_app_volume * handler.m_orig_volume) >> 10) + handler.m_lfo_volume; + handler.m_cur_volume = std::clamp(handler.m_cur_volume, 0, 127); + + handler.m_cur_pan = handler.m_app_pan + handler.m_lfo_pan; + while (handler.m_cur_pan >= 360) + handler.m_cur_pan -= 360; + while (handler.m_cur_pan < 0) + handler.m_cur_pan += 360; + + if ((m_tone.Flags & 8) != 0) { + // Noise unsupported + return 0; + } + + auto voice = std::make_shared(m_tone); + + s32 vol = m_tone.Vol; + if (vol < 0) { + if (vol >= -4) { + vol = handler.m_registers.at(-vol - 1); + } else if (vol == -5) { + vol = rand() % 0x7f; + } else { + vol = g_block_reg.at(-vol - 6); + } + } + + vol = std::max(vol, 0); + + s32 pan = m_tone.Pan; + if (pan < 0) { + if (pan >= -4) { + pan = 360 * handler.m_registers.at(-pan - 1) / 127; + } else if (pan == -5) { + pan = rand() % 360; + } else { + pan = 360 * g_block_reg.at(-pan - 6) / 127; + } + } + + while (pan >= 360) + pan -= 360; + while (pan < 0) + pan += 360; + + voice->start_note = handler.m_note; + voice->start_fine = handler.m_fine; + voice->group = handler.m_group; + + voice->basevol = + handler.m_vm.make_volume(127, 0, handler.m_cur_volume, handler.m_cur_pan, vol, pan); + + handler.m_vm.start_tone(voice, handler.m_bank.bank_id); + handler.m_voices.emplace_front(voice); + + return 0; +} + +SFXGrain_LfoSettings::SFXGrain_LfoSettings(SFXGrain& grain) : Grain(grain) { + m_lfop = grain.GrainParams.lfo; +} +SFXGrain_LfoSettings::SFXGrain_LfoSettings(SFXGrain2& grain, u8* data) : Grain(grain) { + m_lfop = *(LFOParams*)(data + (grain.OpcodeData.Opcode & 0xFFFFFF)); +} +s32 SFXGrain_LfoSettings::execute(blocksound_handler& handler) { + auto& lfo = handler.m_lfo.at(m_lfop.which_lfo); + lfo.m_target = static_cast(m_lfop.target); + if (lfo.m_target != lfo_target::NONE) { + lfo.m_type = static_cast(m_lfop.shape); + lfo.m_target_extra = m_lfop.target_extra; + lfo.m_setup_flags = m_lfop.flags; + lfo.m_depth = m_lfop.depth; + lfo.m_orig_depth = m_lfop.depth; + lfo.m_step_size = m_lfop.step_size; + lfo.m_orig_step_size = m_lfop.step_size; + lfo.m_state_hold1 = 0; + lfo.m_last_lfo = 0; + if (lfo.m_type == lfo_type::SQUARE) { + lfo.m_state_hold1 = m_lfop.duty_cycle; + } + lfo.m_state_hold2 = 0; + if ((lfo.m_setup_flags & 2) != 0) { + lfo.m_next_step = (rand() & 0x7ff) << 16; + } else { + lfo.m_next_step = m_lfop.start_offset << 16; + } + + lg::info("starting LFO type {} for {}", magic_enum::enum_name(lfo.m_type), + magic_enum::enum_name(lfo.m_target)); + lfo.init(); + } else { + lfo.m_type = lfo_type::OFF; + } + + return 0; +} + +SFXGrain_StartChildSound::SFXGrain_StartChildSound(SFXGrain& grain) : Grain(grain) { + m_psp = grain.GrainParams.play_sound; +} +SFXGrain_StartChildSound::SFXGrain_StartChildSound(SFXGrain2& grain, u8* data) : Grain(grain) { + m_psp = *(PlaySoundParams*)(data + (grain.OpcodeData.Opcode & 0xFFFFFF)); +} +s32 SFXGrain_StartChildSound::execute(blocksound_handler& handler) { + s32 vol = m_psp.vol; + if (vol < 0) { + if (vol >= -4) { + vol = handler.m_registers.at(-vol - 1); + } else if (vol == -5) { + vol = rand() % 0x7f; + } else { + vol = g_block_reg.at(-vol - 6); + } + } + + vol = std::clamp(std::abs(vol), 0, 127); + + s32 pan = m_psp.pan; + if (pan < 0) { + if (pan >= -4) { + pan = 360 * std::min(std::abs(handler.m_registers.at(-pan - 1)), 127) / 127; + } else if (pan == -5) { + pan = rand() % 360; + } else { + pan = 360 * std::min(std::abs(g_block_reg.at(-pan - 6)), 127) / 127; + } + } + + SndPlayParams params{}; + params.vol = handler.m_app_volume * handler.m_orig_volume / 127; + params.pan = handler.m_app_pan; + params.pitch_mod = handler.m_app_pm; + params.pitch_bend = handler.m_app_pb; + params.registers = handler.m_registers; + + auto& block = static_cast(handler.bank()); + s32 index = m_psp.sound_id; + + if (index >= 0) { + handler.m_children.emplace_front(block.make_handler(handler.m_vm, index, vol, pan, params)); + + return 0; + } + + lg::error("indirect createchildsound"); + + return 0; +} + +SFXGrain_StopChildSound::SFXGrain_StopChildSound(SFXGrain& grain) : Grain(grain) { + m_psp = grain.GrainParams.play_sound; +} +SFXGrain_StopChildSound::SFXGrain_StopChildSound(SFXGrain2& grain, u8* data) : Grain(grain) { + m_psp = *(PlaySoundParams*)(data + (grain.OpcodeData.Opcode & 0xFFFFFF)); +} +s32 SFXGrain_StopChildSound::execute(blocksound_handler& handler) { + if (m_psp.sound_id >= 0) { + for (auto it = handler.m_children.begin(); it != handler.m_children.end();) { + auto* sound = static_cast(it->get()); + if (sound->m_sfx.index == m_psp.sound_id) { + it = handler.m_children.erase(it); + } else { + ++it; + } + } + + return 0; + } + + lg::error("indirect createchildsound"); + return 0; +} + +SFXGrain_PluginMessage::SFXGrain_PluginMessage(SFXGrain& grain) : Grain(grain) {} +SFXGrain_PluginMessage::SFXGrain_PluginMessage(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_PluginMessage::execute(blocksound_handler& handler) { + // lg::warn("plugin message"); + // TODO probably used + return 0; +} + +SFXGrain_Branch::SFXGrain_Branch(SFXGrain& grain) : Grain(grain) {} +SFXGrain_Branch::SFXGrain_Branch(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_Branch::execute(blocksound_handler& handler) { + return 0; +} + +SFXGrain_LoopEnd::SFXGrain_LoopEnd(SFXGrain& grain) : Grain(grain) {} +SFXGrain_LoopEnd::SFXGrain_LoopEnd(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_LoopEnd::execute(blocksound_handler& handler) { + bool found = false; + for (int i = handler.m_next_grain - 1; i >= 0 && !found; i--) { + if (handler.m_sfx.grains[i]->type() == grain_type::LOOP_START) { + handler.m_next_grain = i - 1; + found = true; + } + } + + if (!found) { + lg::error("LOOP_END could not find LOOP_START"); + } + + return 0; +} + +SFXGrain_LoopContinue::SFXGrain_LoopContinue(SFXGrain& grain) : Grain(grain) {} +SFXGrain_LoopContinue::SFXGrain_LoopContinue(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_LoopContinue::execute(blocksound_handler& handler) { + bool found = false; + for (int i = handler.m_next_grain + 1; i < handler.m_sfx.grains.size() && !found; i++) { + if (handler.m_sfx.grains[i]->type() == grain_type::LOOP_END) { + handler.m_next_grain = i; + found = true; + } + } + + if (!found) { + lg::error("LOOP_CONTINUE could not find LOOP_END"); + } + + return 0; +} + +SFXGrain_Stop::SFXGrain_Stop(SFXGrain& grain) : Grain(grain) {} +SFXGrain_Stop::SFXGrain_Stop(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_Stop::execute(blocksound_handler& handler) { + handler.m_done = true; + + return 0; +} + +SFXGrain_RandPlay::SFXGrain_RandPlay(SFXGrain& grain) : Grain(grain) { + options = grain.GrainParams.control.param[0]; + count = grain.GrainParams.control.param[1]; + previous = grain.GrainParams.control.param[2]; +} + +SFXGrain_RandPlay::SFXGrain_RandPlay(SFXGrain2& grain, u8* data) : Grain(grain) { + options = grain.OpcodeData.arg[0]; + count = grain.OpcodeData.arg[1]; + previous = grain.OpcodeData.arg[2]; +} + +s32 SFXGrain_RandPlay::execute(blocksound_handler& handler) { + int rnd = rand() % options; + if (rnd == previous) { + rnd++; + if (rnd >= options) { + rnd = 0; + } + } + + previous = rnd; + handler.m_next_grain += rnd * count; + handler.m_grains_to_play = count + 1; + handler.m_grains_to_skip = (options - 1 - rnd) * count; + handler.m_skip_grains = true; + return 0; +} + +SFXGrain_RandDelay::SFXGrain_RandDelay(SFXGrain& grain) : Grain(grain) { + m_max = grain.GrainParams.delay.Amount; +} + +SFXGrain_RandDelay::SFXGrain_RandDelay(SFXGrain2& grain, u8* data) : Grain(grain) { + m_max = (grain.OpcodeData.Opcode & 0xFFFFFF) + 1; +} +s32 SFXGrain_RandDelay::execute(blocksound_handler& handler) { + return rand() % m_max; +} + +SFXGrain_RandPB::SFXGrain_RandPB(SFXGrain& grain) : Grain(grain) { + m_pb = grain.GrainParams.control.param[0]; +} +SFXGrain_RandPB::SFXGrain_RandPB(SFXGrain2& grain, u8* data) : Grain(grain) { + m_pb = grain.OpcodeData.arg[0]; +} +s32 SFXGrain_RandPB::execute(blocksound_handler& handler) { + s32 rnd = rand(); + handler.set_pbend(m_pb * ((0xffff * (rnd % 0x7fff)) / 0x7fff - 0x8000) / 100); + + return 0; +} + +SFXGrain_PB::SFXGrain_PB(SFXGrain& grain) : Grain(grain) { + m_pb = grain.GrainParams.control.param[0]; +} +SFXGrain_PB::SFXGrain_PB(SFXGrain2& grain, u8* data) : Grain(grain) { + m_pb = grain.OpcodeData.arg[0]; +} +s32 SFXGrain_PB::execute(blocksound_handler& handler) { + if (m_pb >= 0) { + handler.set_pbend(0x7fff * m_pb / 127); + } else { + handler.set_pbend(-0x8000 * m_pb / -128); + } + + return 0; +} + +SFXGrain_AddPB::SFXGrain_AddPB(SFXGrain& grain) : Grain(grain) { + m_pb = grain.GrainParams.control.param[0]; +} +SFXGrain_AddPB::SFXGrain_AddPB(SFXGrain2& grain, u8* data) : Grain(grain) { + m_pb = grain.OpcodeData.arg[0]; +} +s32 SFXGrain_AddPB::execute(blocksound_handler& handler) { + s32 new_pb = handler.m_cur_pb + 0x7fff * m_pb / 127; + std::clamp(new_pb, INT16_MIN, INT16_MAX); + + handler.set_pbend(new_pb); + + return 0; +} + +SFXGrain_SetRegister::SFXGrain_SetRegister(SFXGrain& grain) : Grain(grain) { + m_reg = grain.GrainParams.control.param[0]; + m_value = grain.GrainParams.control.param[1]; +} +SFXGrain_SetRegister::SFXGrain_SetRegister(SFXGrain2& grain, u8* data) : Grain(grain) { + m_reg = grain.OpcodeData.arg[0]; + m_value = grain.OpcodeData.arg[1]; +} +s32 SFXGrain_SetRegister::execute(blocksound_handler& handler) { + if (m_reg < 0) { + g_block_reg.at(-m_reg - 1) = m_value; + } else { + handler.m_registers.at(m_reg) = m_value; + } + + return 0; +} + +SFXGrain_SetRegisterRand::SFXGrain_SetRegisterRand(SFXGrain& grain) : Grain(grain) { + m_reg = grain.GrainParams.control.param[0]; + m_lower_bound = grain.GrainParams.control.param[1]; + m_upper_bound = grain.GrainParams.control.param[2]; +} +SFXGrain_SetRegisterRand::SFXGrain_SetRegisterRand(SFXGrain2& grain, u8* data) : Grain(grain) { + m_reg = grain.OpcodeData.arg[0]; + m_lower_bound = grain.OpcodeData.arg[1]; + m_upper_bound = grain.OpcodeData.arg[2]; +} +s32 SFXGrain_SetRegisterRand::execute(blocksound_handler& handler) { + s32 range = m_upper_bound - m_lower_bound + 1; + s32 rnd = (rand() % range) + m_lower_bound; + if (m_reg < 0) { + g_block_reg.at(-m_reg - 1) = rnd; + } else { + handler.m_registers.at(m_reg) = rnd; + } + + return 0; +} + +SFXGrain_IncRegister::SFXGrain_IncRegister(SFXGrain& grain) : Grain(grain) { + m_reg = grain.GrainParams.control.param[0]; +} +SFXGrain_IncRegister::SFXGrain_IncRegister(SFXGrain2& grain, u8* data) : Grain(grain) { + m_reg = grain.OpcodeData.arg[0]; +} +s32 SFXGrain_IncRegister::execute(blocksound_handler& handler) { + if (m_reg < 0) { + s32 new_val = g_block_reg.at(-m_reg - 1) + 1; + g_block_reg.at(-m_reg - 1) = std::clamp(new_val, INT8_MIN, INT8_MAX); + + } else { + s32 new_val = handler.m_registers.at(m_reg) + 1; + handler.m_registers.at(m_reg) = std::clamp(new_val, INT8_MIN, INT8_MAX); + } + return 0; +} + +SFXGrain_DecRegister::SFXGrain_DecRegister(SFXGrain& grain) : Grain(grain) { + m_reg = grain.GrainParams.control.param[0]; +} +SFXGrain_DecRegister::SFXGrain_DecRegister(SFXGrain2& grain, u8* data) : Grain(grain) { + m_reg = grain.OpcodeData.arg[0]; +} +s32 SFXGrain_DecRegister::execute(blocksound_handler& handler) { + if (m_reg < 0) { + s32 new_val = g_block_reg.at(-m_reg - 1) - 1; + g_block_reg.at(-m_reg - 1) = std::clamp(new_val, INT8_MIN, INT8_MAX); + + } else { + s32 new_val = handler.m_registers.at(m_reg) - 1; + handler.m_registers.at(m_reg) = std::clamp(new_val, INT8_MIN, INT8_MAX); + } + + return 0; +} + +SFXGrain_TestRegister::SFXGrain_TestRegister(SFXGrain& grain) : Grain(grain) { + m_reg = grain.GrainParams.control.param[0]; + m_action = grain.GrainParams.control.param[1]; + m_cmp = grain.GrainParams.control.param[2]; +} +SFXGrain_TestRegister::SFXGrain_TestRegister(SFXGrain2& grain, u8* data) : Grain(grain) { + m_reg = grain.OpcodeData.arg[0]; + m_action = grain.OpcodeData.arg[1]; + m_cmp = grain.OpcodeData.arg[2]; +} +s32 SFXGrain_TestRegister::execute(blocksound_handler& handler) { + s32 value; + if (m_reg < 0) { + value = g_block_reg[m_reg - 1]; + } else { + value = handler.m_registers.at(m_reg); + } + + if (m_action == 0) { + if (value >= m_cmp) { + handler.m_next_grain++; + } + } else if (m_action == 1) { + if (value != m_cmp) { + handler.m_next_grain++; + } + } else if (m_action >= 2) { + if (m_cmp >= value) + handler.m_next_grain++; + } + + return 0; +} + +SFXGrain_GotoMarker::SFXGrain_GotoMarker(SFXGrain& grain) : Grain(grain) { + m_mark = grain.GrainParams.control.param[0]; +} +SFXGrain_GotoMarker::SFXGrain_GotoMarker(SFXGrain2& grain, u8* data) : Grain(grain) { + m_mark = grain.OpcodeData.arg[0]; +} +s32 SFXGrain_GotoMarker::execute(blocksound_handler& handler) { + bool found = false; + for (int i = 0; i < handler.m_sfx.grains.size() && !found; i++) { + if (handler.m_sfx.grains.at(i)->type() == grain_type::MARKER) { + if (static_cast(handler.m_sfx.grains.at(i).get())->marker() == m_mark) { + handler.m_next_grain = i - 1; + found = true; + } + } + } + + if (!found) { + lg::error("GOTO_MARKER to non-existing marker"); + } + + return 0; +} + +SFXGrain_GotoRandomMarker::SFXGrain_GotoRandomMarker(SFXGrain& grain) : Grain(grain) { + m_lower_bound = grain.GrainParams.control.param[0]; + m_upper_bound = grain.GrainParams.control.param[1]; +} +SFXGrain_GotoRandomMarker::SFXGrain_GotoRandomMarker(SFXGrain2& grain, u8* data) : Grain(grain) { + m_lower_bound = grain.OpcodeData.arg[0]; + m_upper_bound = grain.OpcodeData.arg[1]; +} +s32 SFXGrain_GotoRandomMarker::execute(blocksound_handler& handler) { + bool found = false; + s32 range = m_upper_bound - m_lower_bound + 1; + s32 mark = (rand() % range) + m_lower_bound; + + for (int i = 0; i < handler.m_sfx.grains.size() && !found; i++) { + if (handler.m_sfx.grains.at(i)->type() == grain_type::MARKER) { + if (static_cast(handler.m_sfx.grains.at(i).get())->marker() == mark) { + handler.m_next_grain = i - 1; + found = true; + } + } + } + + if (!found) { + lg::error("GOTO_RANDOM_MARKER to non-existing marker"); + } + + return 0; +} + +SFXGrain_WaitForAllVoices::SFXGrain_WaitForAllVoices(SFXGrain& grain) : Grain(grain) {} +SFXGrain_WaitForAllVoices::SFXGrain_WaitForAllVoices(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_WaitForAllVoices::execute(blocksound_handler& handler) { + if (!handler.m_voices.empty()) { + handler.m_next_grain--; + return 1; + } + + return 0; +} + +SFXGrain_PlayCycle::SFXGrain_PlayCycle(SFXGrain& grain) : Grain(grain) { + m_group_size = grain.GrainParams.control.param[0]; + m_group_count = grain.GrainParams.control.param[1]; + m_index = grain.GrainParams.control.param[2]; +} + +SFXGrain_PlayCycle::SFXGrain_PlayCycle(SFXGrain2& grain, u8* data) : Grain(grain) { + m_group_size = grain.OpcodeData.arg[0]; + m_group_count = grain.OpcodeData.arg[1]; + m_index = grain.OpcodeData.arg[2]; +} + +s32 SFXGrain_PlayCycle::execute(blocksound_handler& handler) { + auto a = m_index++; + if (m_index == m_group_size) { + m_index = 0; + } + + handler.m_next_grain += m_group_count * a; + handler.m_grains_to_play = m_group_count + 1; + handler.m_grains_to_skip = (m_group_size - 1 - a) * m_group_count; + handler.m_skip_grains = true; + return 0; +} + +SFXGrain_AddRegister::SFXGrain_AddRegister(SFXGrain& grain) : Grain(grain) { + m_val = grain.GrainParams.control.param[0]; + m_reg = grain.GrainParams.control.param[1]; +} +SFXGrain_AddRegister::SFXGrain_AddRegister(SFXGrain2& grain, u8* data) : Grain(grain) { + m_val = grain.OpcodeData.arg[0]; + m_reg = grain.OpcodeData.arg[1]; +} +s32 SFXGrain_AddRegister::execute(blocksound_handler& handler) { + if (m_reg < 0) { + s32 new_val = g_block_reg.at(-m_reg - 1) + m_val; + g_block_reg.at(-m_reg - 1) = std::clamp(new_val, INT8_MIN, INT8_MAX); + } else { + s32 new_val = handler.m_registers.at(m_reg) + m_val; + handler.m_registers.at(m_reg) = std::clamp(new_val, INT8_MIN, INT8_MAX); + } + return 0; +} + +SFXGrain_KeyOffVoices::SFXGrain_KeyOffVoices(SFXGrain& grain) : Grain(grain) {} +SFXGrain_KeyOffVoices::SFXGrain_KeyOffVoices(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_KeyOffVoices::execute(blocksound_handler& handler) { + for (auto& p : handler.m_voices) { + auto v = p.lock(); + if (v == nullptr) { + continue; + } + + v->key_off(); + } + return 0; +} + +SFXGrain_KillVoices::SFXGrain_KillVoices(SFXGrain& grain) : Grain(grain) {} +SFXGrain_KillVoices::SFXGrain_KillVoices(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_KillVoices::execute(blocksound_handler& handler) { + for (auto& p : handler.m_voices) { + auto v = p.lock(); + if (v == nullptr) { + continue; + } + + v->key_off(); + v->set_volume_l(0); + v->set_volume_r(0); + } + + return 0; +} + +SFXGrain_OnStopMarker::SFXGrain_OnStopMarker(SFXGrain& grain) : Grain(grain) {} +SFXGrain_OnStopMarker::SFXGrain_OnStopMarker(SFXGrain2& grain, u8* data) : Grain(grain) {} +s32 SFXGrain_OnStopMarker::execute(blocksound_handler& handler) { + handler.m_next_grain = handler.m_sfx.grains.size() - 1; + return 0; +} + +SFXGrain_CopyRegister::SFXGrain_CopyRegister(SFXGrain& grain) : Grain(grain) { + m_src = grain.GrainParams.control.param[0]; + m_dst = grain.GrainParams.control.param[1]; +} +SFXGrain_CopyRegister::SFXGrain_CopyRegister(SFXGrain2& grain, u8* data) : Grain(grain) { + m_src = grain.OpcodeData.arg[0]; + m_dst = grain.OpcodeData.arg[1]; +} +s32 SFXGrain_CopyRegister::execute(blocksound_handler& handler) { + s8 value = 0; + if (m_src < 0) { + value = g_block_reg.at(-m_src - 1); + } else { + value = handler.m_registers.at(m_src); + } + + if (m_dst < 0) { + g_block_reg.at(-m_dst - 1) = value; + } else { + handler.m_registers.at(m_dst) = value; + } + + return 0; +} + +} // namespace snd diff --git a/game/sound/989snd/sfxgrain.h b/game/sound/989snd/sfxgrain.h new file mode 100644 index 000000000..eec28c823 --- /dev/null +++ b/game/sound/989snd/sfxgrain.h @@ -0,0 +1,529 @@ +#pragma once +#include + +#include "common/common_types.h" +#include "common/log/log.h" + +#include "game/sound/989snd/vagvoice.h" + +#include "third-party/magic_enum.hpp" + +namespace snd { + +struct XREFGrainParams { + /* 0 */ u32 BankID; + /* 4 */ u32 SoundIndex; + /* 8 */ s32 PitchMod; + /* c */ u32 Flags; +}; + +struct RandDelayParams { + /* 0 */ s32 Amount; +}; + +struct ControlParams { + /* 0 */ s16 param[4]; +}; + +struct LFOParams { + /* 0 */ u8 which_lfo; + /* 1 */ u8 target; + /* 2 */ u8 target_extra; + /* 3 */ u8 shape; + /* 4 */ u16 duty_cycle; + /* 6 */ u16 depth; + /* 8 */ u16 flags; + /* a */ u16 start_offset; + /* c */ u32 step_size; +}; + +struct PlaySoundParams { + /* 0 */ s32 vol; + /* 4 */ s32 pan; + /* 8 */ s8 reg_settings[4]; + /* c */ s32 sound_id; + /* 10 */ char snd_name[16]; +}; + +struct PluginParams { + /* 0 */ u32 id; + /* 4 */ u32 index; + /* 8 */ u8 data[24]; +}; + +struct LargestGrainParamStruct { + /* 0 */ char blank[32]; +}; + +struct SFXGrain { + /* 0 */ u32 Type; + /* 4 */ s32 Delay; + union { + /* 8 */ Tone tone; + /* 8 */ XREFGrainParams xref; + /* 8 */ RandDelayParams delay; + /* 8 */ ControlParams control; + /* 8 */ LFOParams lfo; + /* 8 */ PlaySoundParams play_sound; + /* 8 */ PluginParams plugin_params; + /* 8 */ LargestGrainParamStruct junk; + } GrainParams; +}; + +struct SFXGrain2 { + union { + struct { + s8 arg[3]; + u8 type; + }; + + u32 Opcode; + } OpcodeData; + + s32 Delay; +}; + +enum class grain_type : u32 { + NULL_GRAIN = 0, + TONE = 1, + TONE2 = 9, + XREF_ID = 2, + XREF_NUM = 3, + LFO_SETTINGS = 4, + STARTCHILDSOUND = 5, + STOPCHILDSOUND = 6, + PLUGIN_MESSAGE = 7, + BRANCH = 8, + CONTROL_NULL = 20, + LOOP_START = 21, + LOOP_END = 22, + LOOP_CONTINUE = 23, + STOP = 24, + RAND_PLAY = 25, + RAND_DELAY = 26, + RAND_PB = 27, + PB = 28, + ADD_PB = 29, + SET_REGISTER = 30, + SET_REGISTER_RAND = 31, + INC_REGISTER = 32, + DEC_REGISTER = 33, + TEST_REGISTER = 34, + MARKER = 35, + GOTO_MARKER = 36, + GOTO_RANDOM_MARKER = 37, + WAIT_FOR_ALL_VOICES = 38, + PLAY_CYCLE = 39, + ADD_REGISTER = 40, + KEY_OFF_VOICES = 41, + KILL_VOICES = 42, + ON_STOP_MARKER = 43, + COPY_REGISTER = 44, +}; + +class blocksound_handler; + +class Grain { + public: + Grain(SFXGrain& grain) : m_type((grain_type)grain.Type), m_delay(grain.Delay) {} + Grain(SFXGrain2& grain) : m_type((grain_type)grain.OpcodeData.type), m_delay(grain.Delay) {} + Grain(SFXGrain2& grain, [[maybe_unused]] u8* data) + : m_type((grain_type)grain.OpcodeData.type), m_delay(grain.Delay) {} + + virtual ~Grain() = default; + + virtual s32 execute(blocksound_handler& handler) { return 0; }; + virtual std::string_view inspect() { return magic_enum::enum_name(type()); }; + s32 delay() { return m_delay; } + grain_type type() { return m_type; } + std::array& args() { return m_args; } + + private: + std::array m_args; + grain_type m_type{0}; + s32 m_delay{0}; +}; + +class SFXGrain_Null : public Grain { + public: + SFXGrain_Null(SFXGrain& grain) : Grain(grain){}; + SFXGrain_Null(SFXGrain2& grain, [[maybe_unused]] u8* data) : Grain(grain){}; +}; + +class SFXGrain_Tone : public Grain { + public: + SFXGrain_Tone(SFXGrain& grain); + SFXGrain_Tone(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + Tone m_tone; +}; + +class SFXGrain_XrefID : public Grain { + public: + SFXGrain_XrefID(SFXGrain& grain) : Grain(grain){}; + SFXGrain_XrefID(SFXGrain2& grain, [[maybe_unused]] u8* data) : Grain(grain){}; +}; + +class SFXGrain_XrefNum : public Grain { + public: + SFXGrain_XrefNum(SFXGrain& grain) : Grain(grain){}; + SFXGrain_XrefNum(SFXGrain2& grain, [[maybe_unused]] u8* data) : Grain(grain){}; +}; + +class SFXGrain_LfoSettings : public Grain { + public: + SFXGrain_LfoSettings(SFXGrain& grain); + SFXGrain_LfoSettings(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + LFOParams m_lfop{}; +}; + +class SFXGrain_StartChildSound : public Grain { + public: + SFXGrain_StartChildSound(SFXGrain& grain); + SFXGrain_StartChildSound(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + PlaySoundParams m_psp{}; +}; + +class SFXGrain_StopChildSound : public Grain { + public: + SFXGrain_StopChildSound(SFXGrain& grain); + SFXGrain_StopChildSound(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + PlaySoundParams m_psp{}; +}; + +class SFXGrain_PluginMessage : public Grain { + public: + SFXGrain_PluginMessage(SFXGrain& grain); + SFXGrain_PluginMessage(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_Branch : public Grain { + public: + SFXGrain_Branch(SFXGrain& grain); + SFXGrain_Branch(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_ControlNull : public Grain { + public: + SFXGrain_ControlNull(SFXGrain& grain) : Grain(grain){}; + SFXGrain_ControlNull(SFXGrain2& grain, u8* data) : Grain(grain){}; +}; + +class SFXGrain_LoopStart : public Grain { + public: + SFXGrain_LoopStart(SFXGrain& grain) : Grain(grain){}; + SFXGrain_LoopStart(SFXGrain2& grain, u8* data) : Grain(grain){}; +}; + +class SFXGrain_LoopEnd : public Grain { + public: + SFXGrain_LoopEnd(SFXGrain& grain); + SFXGrain_LoopEnd(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_LoopContinue : public Grain { + public: + SFXGrain_LoopContinue(SFXGrain& grain); + SFXGrain_LoopContinue(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_Stop : public Grain { + public: + SFXGrain_Stop(SFXGrain& grain); + SFXGrain_Stop(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_RandPlay : public Grain { + public: + SFXGrain_RandPlay(SFXGrain& grain); + SFXGrain_RandPlay(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int options{0}; + int count{0}; + int previous{0}; +}; + +class SFXGrain_RandDelay : public Grain { + public: + SFXGrain_RandDelay(SFXGrain& grain); + SFXGrain_RandDelay(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_max{0}; +}; + +class SFXGrain_RandPB : public Grain { + public: + SFXGrain_RandPB(SFXGrain& grain); + SFXGrain_RandPB(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_pb{0}; +}; + +class SFXGrain_PB : public Grain { + public: + SFXGrain_PB(SFXGrain& grain); + SFXGrain_PB(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_pb{0}; +}; + +class SFXGrain_AddPB : public Grain { + public: + SFXGrain_AddPB(SFXGrain& grain); + SFXGrain_AddPB(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_pb{0}; +}; + +class SFXGrain_SetRegister : public Grain { + public: + SFXGrain_SetRegister(SFXGrain& grain); + SFXGrain_SetRegister(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_reg{0}; + int m_value{0}; +}; + +class SFXGrain_SetRegisterRand : public Grain { + public: + SFXGrain_SetRegisterRand(SFXGrain& grain); + SFXGrain_SetRegisterRand(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_reg{0}; + int m_lower_bound{0}; + int m_upper_bound{0}; +}; + +class SFXGrain_IncRegister : public Grain { + public: + SFXGrain_IncRegister(SFXGrain& grain); + SFXGrain_IncRegister(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_reg{0}; +}; + +class SFXGrain_DecRegister : public Grain { + public: + SFXGrain_DecRegister(SFXGrain& grain); + SFXGrain_DecRegister(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_reg{0}; +}; + +class SFXGrain_TestRegister : public Grain { + public: + SFXGrain_TestRegister(SFXGrain& grain); + SFXGrain_TestRegister(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_reg{0}; + int m_cmp{0}; + int m_action{0}; +}; + +class SFXGrain_Marker : public Grain { + public: + SFXGrain_Marker(SFXGrain& grain) : Grain(grain), m_mark(grain.GrainParams.control.param[0]) {} + SFXGrain_Marker(SFXGrain2& grain, u8* data) : Grain(grain), m_mark(grain.OpcodeData.arg[0]) {} + int marker() { return m_mark; } + + private: + int m_mark{0}; +}; + +class SFXGrain_GotoMarker : public Grain { + public: + SFXGrain_GotoMarker(SFXGrain& grain); + SFXGrain_GotoMarker(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_mark{0}; +}; + +class SFXGrain_GotoRandomMarker : public Grain { + public: + SFXGrain_GotoRandomMarker(SFXGrain& grain); + SFXGrain_GotoRandomMarker(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_upper_bound{0}; + int m_lower_bound{0}; +}; + +class SFXGrain_WaitForAllVoices : public Grain { + public: + SFXGrain_WaitForAllVoices(SFXGrain& grain); + SFXGrain_WaitForAllVoices(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_PlayCycle : public Grain { + public: + SFXGrain_PlayCycle(SFXGrain& grain); + SFXGrain_PlayCycle(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_group_size; + int m_group_count; + int m_index; +}; + +class SFXGrain_AddRegister : public Grain { + public: + SFXGrain_AddRegister(SFXGrain& grain); + SFXGrain_AddRegister(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_val{0}; + int m_reg{0}; +}; + +class SFXGrain_KeyOffVoices : public Grain { + public: + SFXGrain_KeyOffVoices(SFXGrain& grain); + SFXGrain_KeyOffVoices(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_KillVoices : public Grain { + public: + SFXGrain_KillVoices(SFXGrain& grain); + SFXGrain_KillVoices(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_OnStopMarker : public Grain { + public: + SFXGrain_OnStopMarker(SFXGrain& grain); + SFXGrain_OnStopMarker(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; +}; + +class SFXGrain_CopyRegister : public Grain { + public: + SFXGrain_CopyRegister(SFXGrain& grain); + SFXGrain_CopyRegister(SFXGrain2& grain, u8* data); + s32 execute(blocksound_handler& handler) override; + + private: + int m_src{0}; + int m_dst{0}; +}; + +template +std::unique_ptr new_grain(grain_type id, Args&&... args) { + switch (id) { + case grain_type::NULL_GRAIN: + return std::make_unique(std::forward(args)...); + case grain_type::TONE: + case grain_type::TONE2: + return std::make_unique(std::forward(args)...); + case grain_type::XREF_ID: + return std::make_unique(std::forward(args)...); + case grain_type::XREF_NUM: + return std::make_unique(std::forward(args)...); + case grain_type::LFO_SETTINGS: + return std::make_unique(std::forward(args)...); + case grain_type::STARTCHILDSOUND: + return std::make_unique(std::forward(args)...); + case grain_type::STOPCHILDSOUND: + return std::make_unique(std::forward(args)...); + case grain_type::PLUGIN_MESSAGE: + return std::make_unique(std::forward(args)...); + case grain_type::BRANCH: + return std::make_unique(std::forward(args)...); + case grain_type::CONTROL_NULL: + return std::make_unique(std::forward(args)...); + case grain_type::LOOP_START: + return std::make_unique(std::forward(args)...); + case grain_type::LOOP_END: + return std::make_unique(std::forward(args)...); + case grain_type::LOOP_CONTINUE: + return std::make_unique(std::forward(args)...); + case grain_type::STOP: + return std::make_unique(std::forward(args)...); + case grain_type::RAND_PLAY: + return std::make_unique(std::forward(args)...); + case grain_type::RAND_DELAY: + return std::make_unique(std::forward(args)...); + case grain_type::RAND_PB: + return std::make_unique(std::forward(args)...); + case grain_type::PB: + return std::make_unique(std::forward(args)...); + case grain_type::ADD_PB: + return std::make_unique(std::forward(args)...); + case grain_type::SET_REGISTER: + return std::make_unique(std::forward(args)...); + case grain_type::SET_REGISTER_RAND: + return std::make_unique(std::forward(args)...); + case grain_type::INC_REGISTER: + return std::make_unique(std::forward(args)...); + case grain_type::DEC_REGISTER: + return std::make_unique(std::forward(args)...); + case grain_type::TEST_REGISTER: + return std::make_unique(std::forward(args)...); + case grain_type::MARKER: + return std::make_unique(std::forward(args)...); + case grain_type::GOTO_MARKER: + return std::make_unique(std::forward(args)...); + case grain_type::GOTO_RANDOM_MARKER: + return std::make_unique(std::forward(args)...); + case grain_type::WAIT_FOR_ALL_VOICES: + return std::make_unique(std::forward(args)...); + case grain_type::PLAY_CYCLE: + return std::make_unique(std::forward(args)...); + case grain_type::ADD_REGISTER: + return std::make_unique(std::forward(args)...); + case grain_type::KEY_OFF_VOICES: + return std::make_unique(std::forward(args)...); + case grain_type::KILL_VOICES: + return std::make_unique(std::forward(args)...); + case grain_type::ON_STOP_MARKER: + return std::make_unique(std::forward(args)...); + case grain_type::COPY_REGISTER: + return std::make_unique(std::forward(args)...); + default: + throw std::runtime_error(fmt::format("Unknown grain type {}", id)); + } + return nullptr; +} + +} // namespace snd diff --git a/game/sound/989snd/sndplay.cpp b/game/sound/989snd/sndplay.cpp index e5ac4dadd..ebca0d1a2 100644 --- a/game/sound/989snd/sndplay.cpp +++ b/game/sound/989snd/sndplay.cpp @@ -1,3 +1,12 @@ +#include +#include +#include + +#ifdef _WIN32 +#include +#define sleep(n) Sleep(n * 1000) +#endif + #include "player.h" #include "common/log/log.h" @@ -7,22 +16,65 @@ int main(int argc, char* argv[]) { unsigned bankid = 0; fs::path file = argv[1]; + bankid = player.load_bank(file, 0); if (argc > 2) { - bankid = player.load_bank(file, 0); unsigned sound = player.play_sound(bankid, atoi(argv[2]), 0x400, 0, 0, 0); lg::info("sound {} started", sound); } + printf("commands:\n"); + printf(" play [id]\n"); + printf(" stop\n"); + while (true) { -#ifdef __linux - timespec rqtp{}, rmtp{}; - rqtp.tv_nsec = 0; - rqtp.tv_sec = 1; - if (nanosleep(&rqtp, &rmtp) == -1) { - break; + printf("> "); + std::string command; + std::getline(std::cin, command); + + std::stringstream ss(command); + std::string tmp; + std::vector parts; + + while (std::getline(ss, tmp, ' ')) { + parts.push_back(tmp); + } + + if (parts[0] == "play") { + if (parts.size() < 2) { + printf("invalid args\n"); + } else { + auto id = player.play_sound(bankid, std::atoi(parts[1].c_str()), 0x400, 0, 0, 0); + printf("sound handle %d started\n", id); + } + } + + if (parts[0] == "playall") { + auto idx = 0; + auto id = player.play_sound(bankid, idx, 0x400, 0, 0, 0); + while (true) { + if (player.sound_still_active(id)) { + sleep(1); + } else { + idx++; + id = player.play_sound(bankid, idx, 0x400, 0, 0, 0); + } + } + } + + if (parts[0] == "setreg") { + if (parts.size() < 3) { + printf("invalid args\n"); + } else { + player.set_sound_reg(std::atoi(parts[1].c_str()), std::atoi(parts[2].c_str()), + std::atoi(parts[3].c_str())); + } + } + + if (parts[0] == "stop") { + printf("stopping all sounds\n"); + player.stop_all_sounds(); } -#endif } return 0; diff --git a/game/sound/989snd/sound_handler.h b/game/sound/989snd/sound_handler.h index 0550c56d3..218fcc27a 100644 --- a/game/sound/989snd/sound_handler.h +++ b/game/sound/989snd/sound_handler.h @@ -4,16 +4,18 @@ #include "common/common_types.h" +namespace snd { static constexpr int PAN_RESET = -1; static constexpr int PAN_DONT_CHANGE = -2; static constexpr int VOLUME_DONT_CHANGE = 0x7fffffff; -namespace snd { +class SoundBank; + class sound_handler { public: virtual ~sound_handler() = default; virtual bool tick() = 0; - virtual u32 bank() = 0; + virtual SoundBank& bank() = 0; virtual void pause() = 0; virtual void unpause() = 0; virtual u8 group() = 0; diff --git a/game/sound/989snd/soundbank.h b/game/sound/989snd/soundbank.h index 099b98e6f..d65ebea6e 100644 --- a/game/sound/989snd/soundbank.h +++ b/game/sound/989snd/soundbank.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "locator.h" #include "sound_handler.h" @@ -10,6 +11,15 @@ #include "../common/synth.h" namespace snd { + +struct SndPlayParams { + std::optional vol; + std::optional pan; + std::optional pitch_mod; + std::optional pitch_bend; + std::optional> registers; +}; + struct BankTag { /* 0 */ u32 DataID; /* 4 */ u32 Version; @@ -22,17 +32,40 @@ enum class BankType { SFX, }; +struct SFXUserData; class SoundBank { public: + SoundBank(u32 id, BankType type) : type(type), bank_id(id){}; virtual ~SoundBank() = default; - BankType type; + virtual std::unique_ptr make_handler(voice_manager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, - s32 pb) = 0; + s32 pb) { + SndPlayParams params{}; + params.vol = vol; + params.pan = pan; + params.pitch_mod = pm; + params.pitch_bend = pb; + return make_handler(vm, sound_id, -1, -1, params); + }; + + virtual std::unique_ptr make_handler(voice_manager& vm, + u32 sound_id, + s32 vol, + s32 pan, + SndPlayParams& params) = 0; + + virtual std::optional get_name() { return std::nullopt; }; + virtual std::optional get_sound_by_name(const char* name) { return std::nullopt; }; + virtual std::optional get_sound_user_data(u32 sound_id) { + return std::nullopt; + }; + + BankType type; u32 bank_id; u32 bank_name; std::unique_ptr sampleBuf; diff --git a/game/sound/989snd/util.cpp b/game/sound/989snd/util.cpp index 52b9f9b97..f721a1847 100644 --- a/game/sound/989snd/util.cpp +++ b/game/sound/989snd/util.cpp @@ -129,12 +129,14 @@ std::pair pitchbend(Tone& tone, int current_pm, int start_note, int start_fine) { - auto v9 = (start_note << 7) + start_fine + current_pm; - u32 v7; + s32 v9 = (start_note << 7) + start_fine + current_pm; + + s32 v7; if (current_pb >= 0) v7 = tone.PBHigh * (current_pb << 7) / 0x7fff + v9; else - v7 = tone.PBLow * (current_pb << 7) / 0x7fff + v9; + v7 = tone.PBLow * (current_pb << 7) / 0x8000 + v9; + return {v7 / 128, v7 % 128}; } diff --git a/game/sound/989snd/vagvoice.cpp b/game/sound/989snd/vagvoice.cpp index 2a5cbcca5..fdba66080 100644 --- a/game/sound/989snd/vagvoice.cpp +++ b/game/sound/989snd/vagvoice.cpp @@ -15,7 +15,7 @@ voice_manager::voice_manager(synth& synth, locator& loc) : m_synth(synth), m_loc m_group_duck.fill(0x10000); } -void voice_manager::start_tone(std::shared_ptr voice) { +void voice_manager::start_tone(std::shared_ptr voice, u32 bank) { s16 left = adjust_vol_to_group(voice->basevol.left, voice->group); s16 right = adjust_vol_to_group(voice->basevol.right, voice->group); @@ -35,7 +35,7 @@ void voice_manager::start_tone(std::shared_ptr voice) { voice->set_asdr1(voice->tone.ADSR1); voice->set_asdr2(voice->tone.ADSR2); - u8* sbuf = m_locator.get_bank_samples(voice->tone.BankID); + u8* sbuf = m_locator.get_bank_samples(bank); voice->set_sample((u16*)(sbuf + voice->tone.VAGInSR)); voice->key_on(); diff --git a/game/sound/989snd/vagvoice.h b/game/sound/989snd/vagvoice.h index 5ce8544e9..932c00593 100644 --- a/game/sound/989snd/vagvoice.h +++ b/game/sound/989snd/vagvoice.h @@ -32,11 +32,7 @@ struct Tone { /* c */ s16 ADSR2; /* e */ s16 Flags; /* 10 */ /*void**/ u32 VAGInSR; - ///* 14 */ u32 reserved1; confiscated - - // FIXME I'd rather restructure things than mess about like this. - // If we have to edit the structs they should't be loaded like this - /* 14 */ u32 BankID; + /* 14 */ u32 reserved1; }; class vag_voice : public voice { @@ -55,7 +51,7 @@ class vag_voice : public voice { class voice_manager { public: voice_manager(synth& synth, locator& loc); - void start_tone(std::shared_ptr voice); + void start_tone(std::shared_ptr voice, u32 bank); void pause(std::shared_ptr voice); void unpause(std::shared_ptr voice); void set_pan_table(vol_pair* table) { m_pan_table = table; }; diff --git a/game/sound/CMakeLists.txt b/game/sound/CMakeLists.txt index f9abcd3df..1c598c2a9 100644 --- a/game/sound/CMakeLists.txt +++ b/game/sound/CMakeLists.txt @@ -7,8 +7,11 @@ set(SOUND_SOURCES 989snd/blocksound_handler.cpp 989snd/musicbank.cpp 989snd/sfxblock.cpp + 989snd/sfxblock2.cpp + 989snd/sfxgrain.cpp 989snd/loader.cpp 989snd/vagvoice.cpp + 989snd/lfo.cpp 989snd/util.cpp common/synth.cpp common/voice.cpp @@ -35,4 +38,4 @@ if (NOT WIN32) -Wno-unused-parameter -Wno-shadow ) -endif() \ No newline at end of file +endif() diff --git a/game/sound/sndshim.cpp b/game/sound/sndshim.cpp index f3cc9d537..048184a0b 100644 --- a/game/sound/sndshim.cpp +++ b/game/sound/sndshim.cpp @@ -41,31 +41,32 @@ void snd_RegisterIOPMemAllocator(AllocFun, FreeFun) { // printf("snd_RegisterIOPMemAllocator\n"); } -void snd_LockVoiceAllocator(s32) { +int snd_LockVoiceAllocator(bool block) { // printf("snd_LockVoiceAllocator\n"); + return 0; } void snd_UnlockVoiceAllocator() { // printf("snd_UnlockVoiceAllocator\n"); } -s32 snd_ExternVoiceVoiceAlloc(s32, s32) { +s32 snd_ExternVoiceAlloc(s32 vol_group, s32 priority) { // printf("snd_ExternVoiceVoiceAlloc\n"); return 0; } -u32 snd_SRAMMalloc(u32) { +u32 snd_SRAMMalloc(u32 size) { // spu memory currently hardcoded return 0; } -void snd_SetMixerMode(s32, s32) {} +void snd_SetMixerMode(s32 channel_mode, s32 reverb_mode) {} -void snd_SetGroupVoiceRange(s32, s32, s32) {} +void snd_SetGroupVoiceRange(s32 group, s32 min, s32 max) {} -void snd_SetReverbDepth(s32, s32, s32) {} +void snd_SetReverbDepth(s32 core, s32 left, s32 right) {} -void snd_SetReverbType(s32, s32) {} +void snd_SetReverbType(s32 core, s32 type) {} void snd_SetPanTable(s16* table) { if (player) { @@ -89,9 +90,9 @@ s32 snd_SoundIsStillPlaying(s32 sound_handle) { return 0; } -void snd_StopSound(s32 handle) { +void snd_StopSound(s32 sound_handle) { if (player) { - player->stop_sound(handle); + player->stop_sound(sound_handle); } } @@ -101,9 +102,9 @@ void snd_SetSoundVolPan(s32 sound_handle, s32 vol, s32 pan) { } } -void snd_SetMasterVolume(s32 group, s32 volume) { +void snd_SetMasterVolume(s32 which, s32 volume) { if (player) { - player->set_master_volume(group, volume); + player->set_master_volume(which, volume); } } @@ -117,39 +118,55 @@ void snd_ResolveBankXREFS() { // Currently no-op, idk if we'd ever need it } -void snd_ContinueAllSoundsInGroup(u8 group) { +void snd_ContinueAllSoundsInGroup(u8 groups) { if (player) { - player->continue_all_sounds_in_group(group); + player->continue_all_sounds_in_group(groups); } } -void snd_PauseAllSoundsInGroup(u8 group) { +void snd_PauseAllSoundsInGroup(u8 groups) { if (player) { - player->pause_all_sounds_in_group(group); + player->pause_all_sounds_in_group(groups); } } void snd_SetMIDIRegister(s32 sound_handle, u8 reg, u8 value) { if (player) { - player->set_midi_reg(sound_handle, reg, value); + player->set_sound_reg(sound_handle, reg, value); } } -s32 snd_PlaySoundVolPanPMPB(s32 bank, s32 sound, s32 vol, s32 pan, s32 pm, s32 pb) { +s32 snd_PlaySoundVolPanPMPB(s32 bank, s32 sound, s32 vol, s32 pan, s32 pitch_mod, s32 pitch_bend) { if (player) { - return player->play_sound(bank, sound, vol, pan, pm, pb); + return player->play_sound(bank, sound, vol, pan, pitch_mod, pitch_bend); } else { return 0; } } -void snd_SetSoundPitchModifier(s32 sound, s32 mod) { +s32 snd_PlaySoundByNameVolPanPMPB(s32 bank_handle, + char* bank_name, + char* sound_name, + s32 vol, + s32 pan, + s32 pitch_mod, + s32 pitch_bend) { if (player) { - player->set_sound_pmod(sound, mod); + return player->play_sound_by_name(bank_handle, bank_name, sound_name, vol, pan, pitch_mod, + pitch_bend); + } else { + return 0; } } -void snd_SetSoundPitchBend(s32 sound, s32 bend) { +void snd_SetSoundPitchModifier(s32 sound_handle, s32 pitch_mod) { + if (player) { + player->set_sound_pmod(sound_handle, pitch_mod); + } +} + +void snd_SetSoundPitchBend(s32 sound_handle, s32 bend) { + // TODO if (bend != 0) { } } @@ -166,16 +183,16 @@ void snd_ContinueSound(s32 sound_handle) { } } -void snd_AutoPitch(s32, s32, s32, s32) { +void snd_AutoPitch(s32 sound_handle, s32 pitch, s32 delta_time, s32 delta_from) { // TODO - printf("snd_AutoPitch\n"); + lg::warn("Unimplemented snd_AutoPitch\n"); } -void snd_AutoPitchBend(s32, s32, s32, s32) { +void snd_AutoPitchBend(s32 sound_handle, s32 pitch, s32 delta_time, s32 delta_from) { // TODO - printf("snd_AutoPitchBend\n"); + lg::warn("Unimplemented snd_AutoPitchBend\n"); } -s32 snd_BankLoadEx(const char* filename, s32 offset, s32, s32) { +s32 snd_BankLoadEx(const char* filename, s32 offset, u32 spu_mem_loc, u32 spu_mem_size) { // printf("snd_BankLoadEx\n"); if (player) { fs::path path = filename; @@ -205,3 +222,26 @@ void snd_keyOffVoiceRaw(u32 core, u32 voice_id) { voice->key_off(); } } + +s32 snd_GetSoundUserData(s32 block_handle, + char* block_name, + s32 sound_id, + char* sound_name, + SFXUserData* dst) { + if (player) { + return player->get_sound_user_data(block_handle, block_name, sound_id, sound_name, + (snd::SFXUserData*)dst); + } + return 0; +} + +void snd_SetSoundReg(s32 sound_handle, s32 which, u8 val) { + if (player) { + player->set_sound_reg(sound_handle, which, val); + } +} + +void snd_SetGlobalExcite(u8 value) { + // TODO + lg::warn("Unimplemented snd_SetGlobalExcite\n"); +} diff --git a/game/sound/sndshim.h b/game/sound/sndshim.h index 5f19184b5..cd5a815e8 100644 --- a/game/sound/sndshim.h +++ b/game/sound/sndshim.h @@ -7,44 +7,70 @@ constexpr int SND_CORE_0 = 1; constexpr int SND_CORE_1 = 2; constexpr int SD_REV_MODE_OFF = 0; +struct SFXUserData { + u32 data[4]; +}; + typedef void* (*AllocFun)(); typedef void (*FreeFun)(void*); void snd_StartSoundSystem(); void snd_StopSoundSystem(); s32 snd_GetTick(); -void snd_RegisterIOPMemAllocator(AllocFun, FreeFun); -void snd_LockVoiceAllocator(s32); +void snd_RegisterIOPMemAllocator(AllocFun alloc, FreeFun free); +int snd_LockVoiceAllocator(bool block); void snd_UnlockVoiceAllocator(); -s32 snd_ExternVoiceVoiceAlloc(s32, s32); -u32 snd_SRAMMalloc(u32); -void snd_SetMixerMode(s32, s32); -void snd_SetGroupVoiceRange(s32, s32, s32); -void snd_SetReverbDepth(s32, s32, s32); -void snd_SetReverbType(s32, s32); -void snd_SetPanTable(s16*); -void snd_SetPlayBackMode(s32); -s32 snd_SoundIsStillPlaying(s32); -void snd_StopSound(s32); -void snd_SetSoundVolPan(s32, s32, s32); -void snd_SetMasterVolume(s32, s32); -void snd_UnloadBank(s32); +s32 snd_ExternVoiceAlloc(s32 vol_group, s32 priority); +u32 snd_SRAMMalloc(u32 size); +void snd_SetMixerMode(s32 channel_mode, s32 reverb_mode); +void snd_SetGroupVoiceRange(s32 group, s32 min, s32 max); +void snd_SetReverbDepth(s32 core, s32 left, s32 right); +void snd_SetReverbType(s32 core, s32 type); +void snd_SetPanTable(s16* table); +void snd_SetPlayBackMode(s32 mode); +s32 snd_SoundIsStillPlaying(s32 sound_handle); +void snd_StopSound(s32 sound_handle); +void snd_SetSoundVolPan(s32 sound_handle, s32 vol, s32 pan); +void snd_SetMasterVolume(s32 which, s32 volume); +void snd_UnloadBank(s32 bank_handle); void snd_ResolveBankXREFS(); -void snd_ContinueAllSoundsInGroup(u8); -void snd_PauseAllSoundsInGroup(u8); -void snd_SetMIDIRegister(s32, u8, u8); -s32 snd_PlaySoundVolPanPMPB(s32, s32, s32, s32, s32, s32); -void snd_SetSoundPitchModifier(s32, s32); -void snd_SetSoundPitchBend(s32, s32); -void snd_PauseSound(s32); -void snd_ContinueSound(s32); -void snd_AutoPitch(s32, s32, s32, s32); -void snd_AutoPitchBend(s32, s32, s32, s32); -s32 snd_BankLoadEx(const char* filepath, s32 data_offset, s32 unk1, s32 unk2); +void snd_ContinueAllSoundsInGroup(u8 groups); +void snd_PauseAllSoundsInGroup(u8 groups); +void snd_SetMIDIRegister(s32 handle, u8 reg, u8 value); +void snd_SetGlobalExcite(u8 value); + +s32 snd_PlaySoundVolPanPMPB(s32 bank_handle, + s32 sound_id, + s32 vol, + s32 pan, + s32 pitch_mod, + s32 pitch_bend); + +s32 snd_PlaySoundByNameVolPanPMPB(s32 bank_handle, + char* bank_name, + char* sound_name, + s32 vol, + s32 pan, + s32 pitch_mod, + s32 pitch_bend); + +void snd_SetSoundPitchModifier(s32 sound_handle, s32 pitch_mod); +void snd_SetSoundPitchBend(s32 sound_handle, s32 pitch_bend); +void snd_PauseSound(s32 sound_handle); +void snd_ContinueSound(s32 sound_handle); +void snd_AutoPitch(s32 sound_handle, s32 pitch, s32 delta_time, s32 delta_from); +void snd_AutoPitchBend(s32 sound_handle, s32 bend, s32 delta_time, s32 delta_from); +s32 snd_BankLoadEx(const char* filepath, s32 data_offset, u32 spu_mem_loc, u32 spu_mem_size); s32 snd_GetVoiceStatus(s32 voice); s32 snd_GetFreeSPUDMA(); void snd_FreeSPUDMA(s32 channel); -void snd_keyOnVoiceRaw(u32, u32); -void snd_keyOffVoiceRaw(u32, u32); +void snd_keyOnVoiceRaw(u32 core, u32 voice); +void snd_keyOffVoiceRaw(u32 core, u32 voice); +s32 snd_GetSoundUserData(s32 block_handle, + char* block_name, + s32 sound_id, + char* sound_name, + SFXUserData* dst); +void snd_SetSoundReg(s32 sound_handle, s32 which, u8 val); #endif // SNDSHIM_H_ diff --git a/goal_src/jak2/engine/game/main.gc b/goal_src/jak2/engine/game/main.gc index 78a6ea7b3..65c570c3b 100644 --- a/goal_src/jak2/engine/game/main.gc +++ b/goal_src/jak2/engine/game/main.gc @@ -644,7 +644,7 @@ ; ) ;; send sound commands to IOP - ; (swap-sound-buffers (ear-trans 0) (ear-trans 1) (camera-pos) (camera-angle)) + (swap-sound-buffers (ear-trans 0) (ear-trans 1) (camera-pos) (camera-angle)) ;; advance streaming animation ; (str-play-kick) diff --git a/goal_src/jak2/engine/sound/gsound.gc b/goal_src/jak2/engine/sound/gsound.gc index 271bc688b..24c2ba2ef 100644 --- a/goal_src/jak2/engine/sound/gsound.gc +++ b/goal_src/jak2/engine/sound/gsound.gc @@ -935,9 +935,6 @@ 0 ) -;; temporarily removed, will crash on startup -(format 0 "HACK: skipping check-irx-version and common soundbank load.~%") -#| (check-irx-version) (case (scf-get-territory) @@ -958,7 +955,6 @@ ) ) ) -|# diff --git a/goal_src/jak2/game.gp b/goal_src/jak2/game.gp index 85ed78559..523b28bbd 100644 --- a/goal_src/jak2/game.gp +++ b/goal_src/jak2/game.gp @@ -166,6 +166,24 @@ :dep stuff) ) + +(defun copy-iso-file (name subdir ext) + (let* ((path (string-append "$ISO/" subdir name ext)) + (out-name (string-append "$OUT/iso/" name ext))) + (defstep :in path + :tool 'copy + :out `(,out-name)) + out-name)) + +(defmacro copy-sbk-files (&rest files) + `(begin ,@(apply (lambda (x) `(set! *all-sbk* (cons (copy-iso-file ,x "SBK/" ".SBK") *all-sbk*))) files))) + +(defmacro copy-mus-files (&rest files) + `(begin ,@(apply (lambda (x) `(set! *all-mus* (cons (copy-iso-file ,x "MUS/" ".MUS") *all-mus*))) files))) + +(defmacro copy-vag-files (&rest files) + `(begin ,@(apply (lambda (x) `(set! *all-vag* (cons (copy-iso-file "VAGWAD" "VAG/" (string-append "." ,x)) *all-vag*))) files))) + ;;;;;;;;;;;;;;;;; ;; GOAL Kernel ;;;;;;;;;;;;;;;;; @@ -664,6 +682,159 @@ "blocking-plane-ag" ) +(copy-sbk-files "ASHTAN1" +"ASHTAN2" +"ATOLL1" +"ATOLL2" +"ATOLL3" +"ATOLL4" +"BBUSH1" +"BOARD" +"BOMBBOT1" +"CASBOSS1" +"CASBOSS2" +"CASBOSS3" +"CASTLE1" +"CASTLE2" +"CASTLE3" +"COMMON" +"COMMONJ" +"CONSITE1" +"CONSITE2" +"CONSITE3" +"CTYFARM1" +"CTYWIDE1" +"CTYWIDE2" +"CTYWIDE3" +"CTYWIDE4" +"CTYWIDE5" +"DEMO1" +"DIG1" +"DIG2" +"DIG3" +"DIG4" +"DIG5" +"DIG6" +"DIG7" +"DIG8" +"DRILL1" +"DRILL2" +"DRILL3" +"DRILL4" +"DRILL5" +"DRILL6" +"DRILL7" +"DRILL8" +"EMPTY0" +"EMPTY1" +"EMPTY2" +"ERLCHAL1" +"ESCKID1" +"FORDUMP1" +"FORDUMP2" +"FOREST1" +"FOREST2" +"FOREST3" +"FOREST4" +"FOREST5" +"FOREXIT1" +"FOREXIT2" +"FORRESC1" +"FORRESC2" +"GUN" +"GUNGAME1" +"HELLDOG1" +"HIDEOUT1" +"HIPHOG1" +"INTRO1" +"INTRO2" +"INTRO3" +"MECH" +"MECHWAT" +"MEETBRT1" +"MENU1" +"MOUNT1" +"MOUNT2" +"MOUNT3" +"NEST1" +"NEST2" +"NEST3" +"NEST4" +"NEST5" +"NEST6" +"ONIN1" +"ONIN2" +"ORACLE1" +"OUTRO1" +"PALCAB1" +"PALCAB2" +"PALCAB3" +"PALENT1" +"PALENT2" +"PALENT3" +"PALROOF1" +"PALROOF2" +"PALROOF3" +"PORTRUN1" +"PROTECT1" +"RUINS1" +"RUINS2" +"RUINS3" +"SACK1" +"SEWER1" +"SEWER2" +"SEWER3" +"SEWER4" +"SEWER5" +"SEWER6" +"SKATE1" +"STADIUM1" +"STRIP1" +"STRIP2" +"STRIP3" +"TOMB1" +"TOMB2" +"TOMB3" +"TOMB4" +"TOMB5" +"TOMB6" +"TOMB7" +"TOMB8" +"TOMB9" +"UNDER1" +"UNDER2" +"UNDER3" +"UNDER4" +"UNDER5" +"VINROOM1" +) + +(copy-mus-files "ATOLL" +"BATTLE" +"CITY1" +"CREDITS" +"DANGER" +"DANGER1" +"DANGER2" +"DANGER3" +"DANGER4" +"DANGER6" +"DANGER7" +"DANGER9" +"DANGER10" +"DANGER11" +"DIG" +"FOREST" +"FORTRESS" +"MOUNTAIN" +"PALCAB" +"RACE" +"RUINS" +"SEWER" +"STRIP" +"TOMB" +"TWEAKVAL") + ;;;;;;;;;;;;;;;;;;;;; ;; Text ;;;;;;;;;;;;;;;;;;;;; diff --git a/third-party/magic_enum.hpp b/third-party/magic_enum.hpp new file mode 100644 index 000000000..6bf10931c --- /dev/null +++ b/third-party/magic_enum.hpp @@ -0,0 +1,1387 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.8.1 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2022 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_HPP +#define NEARGYE_MAGIC_ENUM_HPP + +#define MAGIC_ENUM_VERSION_MAJOR 0 +#define MAGIC_ENUM_VERSION_MINOR 8 +#define MAGIC_ENUM_VERSION_PATCH 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MAGIC_ENUM_CONFIG_FILE) +#include MAGIC_ENUM_CONFIG_FILE +#endif + +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +#include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING) +#include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +#include +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 26495) // Variable 'static_string::chars_' is uninitialized. +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. +#endif + +// Checks magic_enum compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 +# undef MAGIC_ENUM_SUPPORTED +# define MAGIC_ENUM_SUPPORTED 1 +#endif + +// Checks magic_enum compiler aliases compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef MAGIC_ENUM_SUPPORTED_ALIASES +# define MAGIC_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. +#if !defined(MAGIC_ENUM_RANGE_MIN) +# define MAGIC_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128. +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. +#if !defined(MAGIC_ENUM_RANGE_MAX) +# define MAGIC_ENUM_RANGE_MAX 128 +#endif + +namespace magic_enum { + +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +MAGIC_ENUM_USING_ALIAS_OPTIONAL +#else +using std::optional; +#endif + +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +MAGIC_ENUM_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING) +MAGIC_ENUM_USING_ALIAS_STRING +#else +using std::string; +#endif + +namespace customize { + +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128. +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. +// If need another range for specific enum type, add specialization enum_range for necessary enum type. +template +struct enum_range { + static_assert(std::is_enum_v, "magic_enum::customize::enum_range requires enum type."); + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; + static_assert(max > min, "magic_enum::customize::enum_range requires max > min."); +}; + +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); +static_assert((MAGIC_ENUM_RANGE_MAX - MAGIC_ENUM_RANGE_MIN) < (std::numeric_limits::max)(), "MAGIC_ENUM_RANGE must be less than UINT16_MAX."); + +namespace detail { +enum class default_customize_tag {}; +enum class invalid_customize_tag {}; +} // namespace magic_enum::customize::detail + +using customize_t = std::variant; + +// Default customize. +inline constexpr auto default_tag = detail::default_customize_tag{}; +// Invalid customize. +inline constexpr auto invalid_tag = detail::invalid_customize_tag{}; + +// If need custom names for enum, add specialization enum_name for necessary enum type. +template +constexpr customize_t enum_name(E) noexcept { + return default_tag; +} + +// If need custom type name for enum, add specialization enum_type_name for necessary enum type. +template +constexpr customize_t enum_type_name() noexcept { + return default_tag; +} + +} // namespace magic_enum::customize + +namespace detail { + +template >>> +using enum_constant = std::integral_constant, V>; + +template +inline constexpr bool always_false_v = false; + +template +struct supported +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; + +template +struct range_min : std::integral_constant {}; + +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; + +template +struct range_max : std::integral_constant {}; + +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; + +template +class static_string { + public: + constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_index_sequence{}} { + assert(str.size() == N); + } + + constexpr const char* data() const noexcept { return chars_; } + + constexpr std::size_t size() const noexcept { return N; } + + constexpr operator string_view() const noexcept { return {data(), size()}; } + + private: + template + constexpr static_string(string_view str, std::index_sequence) noexcept : chars_{str[I]..., '\0'} {} + + char chars_[N + 1]; +}; + +template <> +class static_string<0> { + public: + constexpr explicit static_string() = default; + + constexpr explicit static_string(string_view) noexcept {} + + constexpr const char* data() const noexcept { return nullptr; } + + constexpr std::size_t size() const noexcept { return 0; } + + constexpr operator string_view() const noexcept { return {}; } +}; + +constexpr string_view pretty_name(string_view name) noexcept { + for (std::size_t i = name.size(); i > 0; --i) { + if (!((name[i - 1] >= '0' && name[i - 1] <= '9') || + (name[i - 1] >= 'a' && name[i - 1] <= 'z') || + (name[i - 1] >= 'A' && name[i - 1] <= 'Z') || +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + (name[i - 1] & 0x80) || +#endif + (name[i - 1] == '_'))) { + name.remove_prefix(i); + break; + } + } + + if (name.size() > 0 && ((name.front() >= 'a' && name.front() <= 'z') || + (name.front() >= 'A' && name.front() <= 'Z') || +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + (name.front() & 0x80) || +#endif + (name.front() == '_'))) { + return name; + } + + return {}; // Invalid name. +} + +class case_insensitive { + static constexpr char to_lower(char c) noexcept { + return (c >= 'A' && c <= 'Z') ? static_cast(c + ('a' - 'A')) : c; + } + + public: + template + constexpr auto operator()([[maybe_unused]] L lhs, [[maybe_unused]] R rhs) const noexcept -> std::enable_if_t, char> && std::is_same_v, char>, bool> { +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + static_assert(always_false_v, "magic_enum::case_insensitive not supported Non-ASCII feature."); + return false; +#else + return to_lower(lhs) == to_lower(rhs); +#endif + } +}; + +constexpr std::size_t find(string_view str, char c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find_first_of(c); + } +} + +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { + return {{a[I]...}}; +} + +template +constexpr bool is_default_predicate() noexcept { + return std::is_same_v, std::equal_to> || + std::is_same_v, std::equal_to<>>; +} + +template +constexpr bool is_nothrow_invocable() { + return is_default_predicate() || + std::is_nothrow_invocable_r_v; +} + +template +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable()) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (!is_default_predicate() || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } + + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } +} + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned). + return lhs < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; + } +} + +template +constexpr I log2(I value) noexcept { + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + + if constexpr (std::is_same_v) { // bool special case + return assert(false), value; + } else { + auto ret = I{0}; + for (; value > I{1}; value >>= I{1}, ++ret) {} + + return ret; + } +} + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.index() == 0) { + constexpr auto name = std::get(custom); + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_string{name}; + } else if constexpr (custom.index() == 1 && supported::value) { +#if defined(__clang__) || defined(__GNUC__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(_MSC_VER) + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); +#else + constexpr auto name = string_view{}; +#endif + return static_string{name}; + } else { + return static_string<0>{}; // Unsupported compiler or Invalid customize. + } +} + +template +inline constexpr auto type_name_v = n(); + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + [[maybe_unused]] constexpr auto custom = customize::enum_name(V); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.index() == 0) { + constexpr auto name = std::get(custom); + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_string{name}; + } else if constexpr (custom.index() == 1 && supported::value) { +#if defined(__clang__) || defined(__GNUC__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(_MSC_VER) + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); +#else + constexpr auto name = string_view{}; +#endif + return static_string{name}; + } else { + return static_string<0>{}; // Unsupported compiler or Invalid customize. + } +} + +template +inline constexpr auto enum_name_v = n(); + +template +constexpr bool is_valid() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_valid requires enum type."); + + return n(V)>().size() != 0; +} + +template > +constexpr E value(std::size_t i) noexcept { + static_assert(is_enum_v, "magic_enum::detail::value requires enum type."); + + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::value requires valid offset."); + + return static_cast(i); + } else if constexpr (IsFlags) { + return static_cast(U{1} << static_cast(static_cast(i) + O)); + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr int reflected_min() noexcept { + static_assert(is_enum_v, "magic_enum::detail::reflected_min requires enum type."); + + if constexpr (IsFlags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + static_assert(is_enum_v, "magic_enum::detail::reflected_max requires enum type."); + + if constexpr (IsFlags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template +inline constexpr auto reflected_min_v = reflected_min(); + +template +inline constexpr auto reflected_max_v = reflected_max(); + +template +constexpr std::size_t values_count(const bool (&valid)[N]) noexcept { + auto count = std::size_t{0}; + for (std::size_t i = 0; i < N; ++i) { + if (valid[i]) { + ++count; + } + } + + return count; +} + +template +constexpr auto values(std::index_sequence) noexcept { + static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); + constexpr bool valid[sizeof...(I)] = {is_valid(I)>()...}; + constexpr std::size_t count = values_count(valid); + + if constexpr (count > 0) { + E values[count] = {}; + for (std::size_t i = 0, v = 0; v < count; ++i) { + if (valid[i]) { + values[v++] = value(i); + } + } + + return to_array(values, std::make_index_sequence{}); + } else { + return std::array{}; + } +} + +template > +constexpr auto values() noexcept { + static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); + constexpr auto min = reflected_min_v; + constexpr auto max = reflected_max_v; + constexpr auto range_size = max - min + 1; + static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); + static_assert(range_size < (std::numeric_limits::max)(), "magic_enum::enum_range requires valid size."); + + return values>(std::make_index_sequence{}); +} + +template > +constexpr bool is_flags_enum() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_flags_enum requires enum type."); + + if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { +#if defined(MAGIC_ENUM_NO_CHECK_FLAGS) + return false; +#else + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { + return false; + } + for (std::size_t i = 0; i < default_values.size(); ++i) { + const auto v = static_cast(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return false; + } + } + return flags_values.size() > 0; +#endif + } +} + +template +inline constexpr bool is_flags_v = is_flags_enum(); + +template +inline constexpr std::array values_v = values>(); + +template > +using values_t = decltype((values_v)); + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +template +constexpr auto names(std::index_sequence) noexcept { + static_assert(is_enum_v, "magic_enum::detail::names requires enum type."); + + return std::array{{enum_name_v[I]>...}}; +} + +template +inline constexpr std::array names_v = names(std::make_index_sequence>{}); + +template > +using names_t = decltype((names_v)); + +template +constexpr auto entries(std::index_sequence) noexcept { + static_assert(is_enum_v, "magic_enum::detail::entries requires enum type."); + + return std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; +} + +template +inline constexpr std::array entries_v = entries(std::make_index_sequence>{}); + +template > +using entries_t = decltype((entries_v)); + +template > +constexpr bool is_sparse() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_sparse requires enum type."); + + if constexpr (count_v == 0) { + return false; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { + constexpr auto max = is_flags_v ? log2(max_v) : max_v; + constexpr auto min = is_flags_v ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; + + return range_size != count_v; + } +} + +template +inline constexpr bool is_sparse_v = is_sparse(); + +template > +constexpr U values_ors() noexcept { + static_assert(is_enum_v, "magic_enum::detail::values_ors requires enum type."); + + auto ors = U{0}; + for (std::size_t i = 0; i < count_v; ++i) { + ors |= static_cast(values_v[i]); + } + + return ors; +} + +template +struct enable_if_enum {}; + +template +struct enable_if_enum { + using type = R; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +}; + +template > +using enable_if_t = typename enable_if_enum> && std::is_invocable_r_v, R>::type; + +template >>> +using enum_concept = T; + +template > +struct is_scoped_enum : std::false_type {}; + +template +struct is_scoped_enum : std::bool_constant>> {}; + +template > +struct is_unscoped_enum : std::false_type {}; + +template +struct is_unscoped_enum : std::bool_constant>> {}; + +template >> +struct underlying_type {}; + +template +struct underlying_type : std::underlying_type> {}; + +template +struct constexpr_hash_t; + +template +struct constexpr_hash_t>> { + constexpr auto operator()(Value value) const noexcept { + using U = typename underlying_type::type; + if constexpr (std::is_same_v) { // bool special case + return static_cast(value); + } else { + return static_cast(value); + } + } + using secondary_hash = constexpr_hash_t; +}; + +template +struct constexpr_hash_t>> { + static constexpr std::uint32_t crc_table[256] { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL + }; + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto crc = static_cast(0xffffffffL); + for (const auto c : value) { + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; + } + return crc ^ 0xffffffffL; + } + + struct secondary_hash { + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto acc = static_cast(2166136261ULL); + for (const auto c : value) { + acc = ((acc ^ static_cast(c)) * static_cast(16777619ULL)) & (std::numeric_limits::max)(); + } + return static_cast(acc); + } + }; +}; + +template +constexpr static Hash hash_v{}; + +template +constexpr auto calculate_cases(std::size_t Page) noexcept { + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + + using switch_t = std::invoke_result_t; + static_assert(std::is_integral_v && !std::is_same_v); + const std::size_t values_to = (std::min)(static_cast(256), size - Page); + + std::array result{}; + auto fill = result.begin(); + { + auto first = values.begin() + static_cast(Page); + auto last = values.begin() + static_cast(Page + values_to); + while (first != last) { + *fill++ = hash_v(*first++); + } + } + + // dead cases, try to avoid case collisions + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits::max)(); *fill++ = ++last_value) { + } + + { + auto it = result.begin(); + auto last_value = (std::numeric_limits::min)(); + for (; fill != result.end(); *fill++ = last_value++) { + while (last_value == *it) { + ++last_value, ++it; + } + } + } + + return result; +} + +template +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { + if constexpr (std::is_void_v) { + std::forward(f)(std::forward(args)...); + } else { + return static_cast(std::forward(f)(std::forward(args)...)); + } +} + +enum class case_call_t { + index, value +}; + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr bool no_duplicate() noexcept { + using value_t = std::decay_t; + using hash_value_t = std::invoke_result_t; + std::arraysize()> hashes{}; + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size] = hash_v(elem); + for (auto i = size++; i > 0; --i) { + if (hashes[i] < hashes[i - 1]) { + auto tmp = hashes[i]; + hashes[i] = hashes[i - 1]; + hashes[i - 1] = tmp; + } else if (hashes[i] == hashes[i - 1]) { + return false; + } else { + break; + } + } + } + return true; +} + +#define MAGIC_ENUM_FOR_EACH_256(T) T(0)T(1)T(2)T(3)T(4)T(5)T(6)T(7)T(8)T(9)T(10)T(11)T(12)T(13)T(14)T(15)T(16)T(17)T(18)T(19)T(20)T(21)T(22)T(23)T(24)T(25)T(26)T(27)T(28)T(29)T(30)T(31) \ + T(32)T(33)T(34)T(35)T(36)T(37)T(38)T(39)T(40)T(41)T(42)T(43)T(44)T(45)T(46)T(47)T(48)T(49)T(50)T(51)T(52)T(53)T(54)T(55)T(56)T(57)T(58)T(59)T(60)T(61)T(62)T(63) \ + T(64)T(65)T(66)T(67)T(68)T(69)T(70)T(71)T(72)T(73)T(74)T(75)T(76)T(77)T(78)T(79)T(80)T(81)T(82)T(83)T(84)T(85)T(86)T(87)T(88)T(89)T(90)T(91)T(92)T(93)T(94)T(95) \ + T(96)T(97)T(98)T(99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + +#define MAGIC_ENUM_CASE(val) \ + case cases[val]: \ + if constexpr ((val) + Page < size) { \ + if (!pred(values[val + Page], searched)) { \ + break; \ + } \ + if constexpr (CallValue == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } else if constexpr (CallValue == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), enum_constant{}); \ + } else if constexpr (std::is_invocable_r_v>) { \ + assert(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } \ + break; \ + } else [[fallthrough]]; + +template ::value_type>, + typename Lambda, typename ResultGetterType = decltype(default_result_type_lambda<>), + typename BinaryPredicate = std::equal_to<>> +constexpr std::invoke_result_t constexpr_switch( + Lambda&& lambda, + typename std::decay_t::value_type searched, + ResultGetterType&& def = default_result_type_lambda<>, + BinaryPredicate&& pred = {}) { + using result_t = std::invoke_result_t; + using hash_t = std::conditional_t(), Hash, typename Hash::secondary_hash>; + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + constexpr std::array cases = calculate_cases(Page); + + switch (hash_v(searched)) { + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) + default: + if constexpr (size > 256 + Page) { + return constexpr_switch(std::forward(lambda), searched, std::forward(def)); + } + break; + } + return def(); +} + +#undef MAGIC_ENUM_FOR_EACH_256 +#undef MAGIC_ENUM_CASE + +template +constexpr auto for_each(Lambda&& lambda, std::index_sequence) { + static_assert(is_enum_v, "magic_enum::detail::for_each requires enum type."); + constexpr bool has_void_return = (std::is_void_v[I]>>> || ...); + constexpr bool all_same_return = (std::is_same_v[0]>>, std::invoke_result_t[I]>>> && ...); + + if constexpr (has_void_return) { + (lambda(enum_constant[I]>{}), ...); + } else if constexpr (all_same_return) { + return std::array{lambda(enum_constant[I]>{})...}; + } else { + return std::tuple{lambda(enum_constant[I]>{})...}; + } +} + +} // namespace magic_enum::detail + +// Checks is magic_enum supported compiler. +inline constexpr bool is_magic_enum_supported = detail::supported::value; + +template +using Enum = detail::enum_concept; + +// Checks whether T is an Unscoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false. +template +struct is_unscoped_enum : detail::is_unscoped_enum {}; + +template +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; + +// Checks whether T is an Scoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false. +template +struct is_scoped_enum : detail::is_scoped_enum {}; + +template +inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; + +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. +template +struct underlying_type : detail::underlying_type {}; + +template +using underlying_type_t = typename underlying_type::type; + +template +using enum_constant = detail::enum_constant; + +// Returns type name of enum. +template +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::type_name_v>; + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); + + return name; +} + +// Returns number of enum values. +template +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { + return detail::count_v>; +} + +// Returns enum value at specified index. +// No bounds checking is performed: the behavior is undefined if index >= number of enum values. +template +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + + if constexpr (detail::is_sparse_v) { + return assert((index < detail::count_v)), detail::values_v[index]; + } else { + constexpr bool is_flag = detail::is_flags_v; + constexpr auto min = is_flag ? detail::log2(detail::min_v) : detail::min_v; + + return assert((index < detail::count_v)), detail::value(index); + } +} + +// Returns enum value at specified index. +template +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); + + return enum_value(I); +} + +// Returns std::array with enum values, sorted by enum value. +template +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { + return detail::values_v>; +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Returns underlying value from enum value. +template +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::count_v == 0) { + return {}; // Empty enum. + } else if constexpr (detail::is_sparse_v || detail::is_flags_v) { + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{i}; }, + value, + detail::default_result_type_lambda>); + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains index in enum values from static storage enum variable. +template +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t { + constexpr auto index = enum_index>(V); + static_assert(index, "magic_enum::enum_index enum value does not have a index."); + + return *index; +} + +// Returns name from static storage enum variable. +// This version is much lighter on the compile times and is not restricted to the enum_range limitation. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::enum_name_v, V>; + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); + + return name; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + if (const auto i = enum_index(value)) { + return detail::names_v[*i]; + } + return {}; +} + +// Returns name from enum-flags value. +// If enum-flags value does not have name or value out of range, returns empty string. +template +[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_flags_v) { + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { + check_value |= v; + const auto n = detail::names_v[i]; + if (!name.empty()) { + name.append(1, '|'); + } + name.append(n.data(), n.size()); + } + } + + if (check_value != 0 && check_value == static_cast(value)) { + return name; + } + + return {}; // Invalid value or out of range. + } else { + return string{enum_name(value)}; + } +} + +// Returns std::array with names, sorted by enum value. +template +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { + return detail::names_v>; +} + +// Returns std::array with pairs (value, name), sorted by enum value. +template +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { + return detail::entries_v>; +} + +// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = detail::case_insensitive{}; + +// Obtains enum value from integer value. +// Returns optional with enum value. +template +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::count_v == 0) { + return {}; // Empty enum. + } else if constexpr (detail::is_sparse_v) { + if constexpr (detail::is_flags_v) { + constexpr auto count = detail::count_v; + auto check_value = U{0}; + for (std::size_t i = 0; i < count; ++i) { + if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { + check_value |= v; + } + } + + if (check_value != 0 && check_value == value) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } else { + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + [](D v) { return optional{v}; }, + static_cast(value), + detail::default_result_type_lambda>); + } + } else { + constexpr auto min = detail::min_v; + constexpr auto max = detail::is_flags_v ? detail::values_ors() : detail::max_v; + + if (value >= min && value <= max) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum value from name. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate&& p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + static_assert(std::is_invocable_r_v, "magic_enum::enum_cast requires bool(char, char) invocable predicate."); + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::count_v == 0) { + return {}; // Empty enum. + } else if constexpr (detail::is_flags_v) { + auto result = U{0}; + while (!value.empty()) { + const auto d = detail::find(value, '|'); + const auto s = (d == string_view::npos) ? value : value.substr(0, d); + auto f = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + f = static_cast(enum_value(i)); + result |= f; + break; + } + } + if (f == U{0}) { + return {}; // Invalid value or out of range. + } + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); + } + + if (result != U{0}) { + return static_cast(result); + } + return {}; // Invalid value or out of range. + } else if constexpr (detail::count_v > 0) { + if constexpr (detail::is_default_predicate()) { + return detail::constexpr_switch<&detail::names_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{detail::values_v[i]}; }, + value, + detail::default_result_type_lambda>, + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); + } else { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + return {}; // Invalid value or out of range. + } + } +} + +// Checks whether enum contains enumerator with such enum value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains enumerator with such integer value. +template +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value)); +} + +// Checks whether enum contains enumerator with such name. +template > +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate&& p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + static_assert(std::is_invocable_r_v, "magic_enum::enum_contains requires bool(char, char) invocable predicate."); + using D = std::decay_t; + + return static_cast(enum_cast(value, std::forward(p))); +} + +template +constexpr auto enum_switch(Lambda&& lambda, E value) -> detail::enable_if_t { + using D = std::decay_t; + + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(lambda), + value, + detail::default_result_type_lambda); +} + +template +constexpr auto enum_switch(Lambda&& lambda, E value, Result&& result) -> detail::enable_if_t { + using D = std::decay_t; + + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(lambda), + value, + [&result] { return std::forward(result); }); +} + +template , typename Lambda> +constexpr auto enum_switch(Lambda&& lambda, string_view name, BinaryPredicate&& p = {}) -> detail::enable_if_t { + static_assert(std::is_invocable_r_v, "magic_enum::enum_switch requires bool(char, char) invocable predicate."); + using D = std::decay_t; + + if (const auto v = enum_cast(name, std::forward(p))) { + return enum_switch(std::forward(lambda), *v); + } + return detail::default_result_type_lambda(); +} + +template , typename Lambda> +constexpr auto enum_switch(Lambda&& lambda, string_view name, Result&& result, BinaryPredicate&& p = {}) -> detail::enable_if_t { + static_assert(std::is_invocable_r_v, "magic_enum::enum_switch requires bool(char, char) invocable predicate."); + using D = std::decay_t; + + if (const auto v = enum_cast(name, std::forward(p))) { + return enum_switch(std::forward(lambda), *v, std::forward(result)); + } + return std::forward(result); +} + +template +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t value) -> detail::enable_if_t { + using D = std::decay_t; + + if (const auto v = enum_cast(value)) { + return enum_switch(std::forward(lambda), *v); + } + return detail::default_result_type_lambda(); +} + +template +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t value, Result&& result) -> detail::enable_if_t { + using D = std::decay_t; + + if (const auto v = enum_cast(value)) { + return enum_switch(std::forward(lambda), *v, std::forward(result)); + } + return std::forward(result); +} + +template +constexpr auto enum_for_each(Lambda&& lambda) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_for_each requires enum type."); + + return detail::for_each(std::forward(lambda), std::make_index_sequence>{}); +} + +namespace ostream_operators { + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, E value) { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::supported::value) { + if (const auto name = enum_flags_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } + return (os << static_cast(value)); +} + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { + return value ? (os << *value) : os; +} + +} // namespace magic_enum::ostream_operators + +namespace istream_operators { + +template = 0> +std::basic_istream& operator>>(std::basic_istream& is, E& value) { + using D = std::decay_t; + + std::basic_string s; + is >> s; + if (const auto v = enum_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + return is; +} + +} // namespace magic_enum::istream_operators + +namespace iostream_operators { + +using namespace ostream_operators; +using namespace istream_operators; + +} // namespace magic_enum::iostream_operators + +namespace bitwise_operators { + +template = 0> +constexpr E operator~(E rhs) noexcept { + return static_cast(~static_cast>(rhs)); +} + +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} + +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); +} + +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); +} + +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +} // namespace magic_enum::bitwise_operators + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // NEARGYE_MAGIC_ENUM_HPP