mirror of
https://github.com/open-goal/jak-project
synced 2026-08-30 09:21:48 -04:00
c08118509b
Two changes that fix some Jak 3 audio bugs. In some areas, sound effects would seem to cut off randomly. This was caused by the sound code reusing IDs, but overlord didn't realize. It would try to kill a sound effect by ID that had already died, and the ID was reassigned to a new sound. To fix this, I made the handle IDs always increment. I also tested starting the ID at large numbers and confirmed that worked. I also added support for the BRANCH grain. This makes the `wastelander-shot` and possibly other sound effects work. It doesn't support all the features, but this is probably enough. Fun fact: the wastelander shot is supposed to have 7 variations, but you always get the same one because they set the sound mask wrong. I added a line you can uncomment if you want to experience the other 6 variations. --------- Co-authored-by: water111 <awaterford1111445@gmail.com>
402 lines
9.6 KiB
C++
402 lines
9.6 KiB
C++
// Copyright: 2021 - 2024, Ziemas
|
|
// SPDX-License-Identifier: ISC
|
|
#include "player.h"
|
|
|
|
#include <fstream>
|
|
|
|
#include "sfxblock.h"
|
|
|
|
#include "fmt/format.h"
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <combaseapi.h>
|
|
#include <windows.h>
|
|
#endif
|
|
#include "common/log/log.h"
|
|
|
|
namespace snd {
|
|
|
|
u8 g_global_excite = 0;
|
|
|
|
Player::Player() : mVmanager(mSynth) {
|
|
InitCubeb();
|
|
}
|
|
|
|
Player::~Player() {
|
|
DestroyCubeb();
|
|
}
|
|
|
|
void Player::InitCubeb() {
|
|
#ifdef _WIN32
|
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
m_coinitialized = SUCCEEDED(hr);
|
|
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
|
|
lg::error("Couldn't initialize COM");
|
|
lg::error("Cubeb init failed");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
cubeb_init(&mCtx, "OpenGOAL", nullptr);
|
|
|
|
cubeb_stream_params outparam = {};
|
|
outparam.channels = 2;
|
|
outparam.format = CUBEB_SAMPLE_S16LE;
|
|
outparam.rate = 48000;
|
|
outparam.layout = CUBEB_LAYOUT_STEREO;
|
|
outparam.prefs = CUBEB_STREAM_PREF_NONE;
|
|
|
|
s32 err = 0;
|
|
u32 latency = 0;
|
|
err = cubeb_get_min_latency(mCtx, &outparam, &latency);
|
|
if (err != CUBEB_OK) {
|
|
lg::error("Cubeb init failed");
|
|
return;
|
|
}
|
|
|
|
err = cubeb_stream_init(mCtx, &mStream, "OpenGOAL", nullptr, nullptr, nullptr, &outparam, latency,
|
|
&sound_callback, &state_callback, this);
|
|
if (err != CUBEB_OK) {
|
|
lg::error("Cubeb init failed");
|
|
return;
|
|
}
|
|
|
|
err = cubeb_stream_start(mStream);
|
|
if (err != CUBEB_OK) {
|
|
lg::error("Cubeb init failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Player::DestroyCubeb() {
|
|
cubeb_stream_stop(mStream);
|
|
cubeb_stream_destroy(mStream);
|
|
cubeb_destroy(mCtx);
|
|
#ifdef _WIN32
|
|
if (m_coinitialized) {
|
|
CoUninitialize();
|
|
m_coinitialized = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
long Player::sound_callback([[maybe_unused]] cubeb_stream* stream,
|
|
void* user,
|
|
[[maybe_unused]] const void* input,
|
|
void* output_buffer,
|
|
long nframes) {
|
|
((Player*)user)->Tick((s16Output*)output_buffer, nframes);
|
|
return nframes;
|
|
}
|
|
|
|
void Player::state_callback([[maybe_unused]] cubeb_stream* stream,
|
|
[[maybe_unused]] void* user,
|
|
[[maybe_unused]] cubeb_state state) {}
|
|
|
|
void Player::Tick(s16Output* stream, int samples) {
|
|
std::scoped_lock lock(mTickLock);
|
|
static int htick = 200;
|
|
static int stick = 48000;
|
|
for (int i = 0; i < samples; i++) {
|
|
// The handlers expect to tick at 240hz
|
|
// 48000/240 = 200
|
|
if (htick == 200) {
|
|
mTick++;
|
|
|
|
for (auto it = mHandlers.begin(); it != mHandlers.end();) {
|
|
bool done = it->second->Tick();
|
|
if (done) {
|
|
// fmt::print("erasing handler\n");
|
|
mHandleAllocator.FreeId(it->first);
|
|
it = mHandlers.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
htick = 0;
|
|
}
|
|
|
|
if (stick == 48000) {
|
|
// fmt::print("{} handlers active\n", m_handlers.size());
|
|
stick = 0;
|
|
}
|
|
|
|
stick++;
|
|
htick++;
|
|
*stream++ = mSynth.Tick();
|
|
}
|
|
}
|
|
|
|
u32 Player::PlaySound(BankHandle bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto bank = mLoader.GetBankByHandle(bank_id);
|
|
if (bank == nullptr) {
|
|
lg::error("play_sound: Bank {} does not exist", static_cast<void*>(bank_id));
|
|
return 0;
|
|
}
|
|
|
|
auto handler = bank->MakeHandler(mVmanager, sound_id, vol, pan, pm, pb, GetTick());
|
|
if (!handler.has_value()) {
|
|
return 0;
|
|
}
|
|
|
|
auto handler_to_stop = handler.value()->CheckInstanceLimit(mHandlers, vol, true);
|
|
if (handler_to_stop) {
|
|
handler_to_stop->Stop();
|
|
if (handler_to_stop == handler.value().get()) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
u32 handle = mHandleAllocator.GetId();
|
|
mHandlers.emplace(handle, std::move(handler.value()));
|
|
// fmt::print("play_sound {}:{} - {}\n", bank_id, sound_id, handle);
|
|
|
|
return handle;
|
|
}
|
|
|
|
void Player::DebugPrintAllSoundsInBank(BankHandle bank_id) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto* bank = mLoader.GetBankByHandle(bank_id);
|
|
if (!bank) {
|
|
lg::error("DebugPrintAllSoundsInBank: invalid bank");
|
|
return;
|
|
}
|
|
bank->DebugPrintAllSounds();
|
|
}
|
|
|
|
u32 Player::PlaySoundByName(BankHandle bank_id,
|
|
char* bank_name,
|
|
char* sound_name,
|
|
s32 vol,
|
|
s32 pan,
|
|
s32 pm,
|
|
s32 pb) {
|
|
std::scoped_lock lock(mTickLock);
|
|
SoundBank* bank = nullptr;
|
|
if (bank_id == 0 && bank_name != nullptr) {
|
|
bank = mLoader.GetBankByName(bank_name);
|
|
} else if (bank_id != 0) {
|
|
bank = mLoader.GetBankByHandle(bank_id);
|
|
} else {
|
|
bank = mLoader.GetBankWithSound(sound_name);
|
|
}
|
|
|
|
if (bank == nullptr) {
|
|
// lg::error("play_sound_by_name: failed to find bank for sound {}", sound_name);
|
|
return 0;
|
|
}
|
|
|
|
auto sound = bank->GetSoundByName(sound_name);
|
|
if (sound.has_value()) {
|
|
return PlaySound(bank, sound.value(), vol, pan, pm, pb);
|
|
}
|
|
|
|
// lg::error("play_sound_by_name: failed to find sound {}", sound_name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Player::StopSound(u32 sound_id) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_id);
|
|
if (handler == mHandlers.end())
|
|
return;
|
|
|
|
handler->second->Stop();
|
|
|
|
// m_handle_allocator.free_id(sound_id);
|
|
// m_handlers.erase(sound_id);
|
|
}
|
|
|
|
u32 Player::GetSoundID(u32 sound_handle) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_handle);
|
|
if (handler == mHandlers.end())
|
|
return -1;
|
|
return handler->second->SoundID();
|
|
}
|
|
|
|
void Player::SetSoundReg(u32 sound_id, u8 reg, u8 value) {
|
|
std::scoped_lock lock(mTickLock);
|
|
if (mHandlers.find(sound_id) == mHandlers.end()) {
|
|
// fmt::print("set_midi_reg: Handler {} does not exist\n", sound_id);
|
|
return;
|
|
}
|
|
|
|
auto* handler = mHandlers.at(sound_id).get();
|
|
handler->SetRegister(reg, value);
|
|
}
|
|
|
|
bool Player::SoundStillActive(u32 sound_id) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_id);
|
|
if (handler == mHandlers.end())
|
|
return false;
|
|
|
|
// fmt::print("sound_still_active {}\n", sound_id);
|
|
return true;
|
|
}
|
|
|
|
void Player::SetMasterVolume(u32 group, s32 volume) {
|
|
std::scoped_lock lock(mTickLock);
|
|
if (volume > 0x400)
|
|
volume = 0x400;
|
|
|
|
if (volume < 0)
|
|
volume = 0;
|
|
|
|
if (group == 15)
|
|
return;
|
|
|
|
mVmanager.SetMasterVol(group, volume);
|
|
|
|
// Master volume
|
|
if (group == 16) {
|
|
mSynth.SetMasterVol(0x3ffff * volume / 0x400);
|
|
}
|
|
}
|
|
|
|
BankHandle Player::LoadBank(std::span<u8> bank) {
|
|
std::scoped_lock lock(mTickLock);
|
|
return mLoader.BankLoad(bank);
|
|
}
|
|
|
|
void Player::UnloadBank(BankHandle bank_handle) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto* bank = mLoader.GetBankByHandle(bank_handle);
|
|
if (bank == nullptr)
|
|
return;
|
|
|
|
for (auto it = mHandlers.begin(); it != mHandlers.end();) {
|
|
if (&it->second->Bank() == bank_handle) {
|
|
mHandleAllocator.FreeId(it->first);
|
|
it = mHandlers.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
mLoader.UnloadBank(bank_handle);
|
|
}
|
|
|
|
void Player::SetPanTable(VolPair* pantable) {
|
|
std::scoped_lock lock(mTickLock);
|
|
mVmanager.SetPanTable(pantable);
|
|
}
|
|
|
|
void Player::SetPlaybackMode(s32 mode) {
|
|
std::scoped_lock lock(mTickLock);
|
|
mVmanager.SetPlaybackMode(mode);
|
|
}
|
|
|
|
void Player::PauseSound(s32 sound_id) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_id);
|
|
if (handler == mHandlers.end())
|
|
return;
|
|
|
|
handler->second->Pause();
|
|
}
|
|
|
|
void Player::ContinueSound(s32 sound_id) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_id);
|
|
if (handler == mHandlers.end())
|
|
return;
|
|
|
|
handler->second->Unpause();
|
|
}
|
|
|
|
void Player::PauseAllSoundsInGroup(u8 group) {
|
|
std::scoped_lock lock(mTickLock);
|
|
|
|
for (auto& h : mHandlers) {
|
|
if ((1 << h.second->Group()) & group) {
|
|
h.second->Pause();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::ContinueAllSoundsInGroup(u8 group) {
|
|
std::scoped_lock lock(mTickLock);
|
|
|
|
for (auto& h : mHandlers) {
|
|
if ((1 << h.second->Group()) & group) {
|
|
h.second->Unpause();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::SetSoundVolPan(s32 sound_id, s32 vol, s32 pan) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_id);
|
|
if (handler == mHandlers.end())
|
|
return;
|
|
|
|
handler->second->SetVolPan(vol, pan);
|
|
}
|
|
|
|
void Player::SetSoundPmod(s32 sound_handle, s32 mod) {
|
|
std::scoped_lock lock(mTickLock);
|
|
auto handler = mHandlers.find(sound_handle);
|
|
if (handler == mHandlers.end())
|
|
return;
|
|
|
|
handler->second->SetPMod(mod);
|
|
}
|
|
|
|
void Player::StopAllSounds() {
|
|
std::scoped_lock lock(mTickLock);
|
|
for (auto it = mHandlers.begin(); it != mHandlers.end();) {
|
|
mHandleAllocator.FreeId(it->first);
|
|
it = mHandlers.erase(it);
|
|
}
|
|
}
|
|
|
|
s32 Player::GetSoundUserData(BankHandle block_handle,
|
|
char* block_name,
|
|
s32 sound_id,
|
|
char* sound_name,
|
|
SFXUserData* dst) {
|
|
std::scoped_lock lock(mTickLock);
|
|
SoundBank* bank = nullptr;
|
|
if (block_handle == nullptr && block_name != nullptr) {
|
|
bank = mLoader.GetBankByName(block_name);
|
|
} else if (block_handle != nullptr) {
|
|
bank = mLoader.GetBankByHandle(block_handle);
|
|
} else {
|
|
bank = mLoader.GetBankWithSound(sound_name);
|
|
}
|
|
|
|
if (bank == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
if (sound_id == -1) {
|
|
auto sound = bank->GetSoundByName(sound_name);
|
|
if (sound.has_value()) {
|
|
sound_id = sound.value();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
auto ud = bank->GetSoundUserData(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
|