#include "Runtime/Audio/CStreamAudioManager.hpp" #include "Runtime/Audio/CAudioSys.hpp" #include "Runtime/CDvdFile.hpp" #include "Runtime/CDvdRequest.hpp" #include "Runtime/CStringExtras.hpp" #include #include #include #include #include #include namespace metaforce { class CDSPStreamManager; static u32 s_HandleCounter = 0; static u32 s_HandleCounter2 = 0; /* Standard DSPADPCM header */ struct dspadpcm_header { u32 x0_num_samples; u32 x4_num_nibbles; u32 x8_sample_rate; u16 xc_loop_flag; u16 xe_format; /* 0 for ADPCM */ u32 x10_loop_start_nibble; u32 x14_loop_end_nibble; u32 x18_ca; s16 x1c_coef[8][2]; s16 x3c_gain; s16 x3e_ps; s16 x40_hist1; s16 x42_hist2; s16 x44_loop_ps; s16 x46_loop_hist1; s16 x48_loop_hist2; std::array x4a_pad; }; struct SDSPStreamInfo { const char* x0_fileName; u32 x4_sampleRate; u32 x8_headerSize = sizeof(dspadpcm_header); u32 xc_adpcmBytes; bool x10_loopFlag; u32 x14_loopStartByte; u32 x18_loopEndByte; s16 x1c_coef[8][2]; SDSPStreamInfo() = default; explicit SDSPStreamInfo(const CDSPStreamManager& stream); }; struct SDSPStream : boo::IAudioVoiceCallback { bool x0_active; bool x1_oneshot; s32 x4_ownerId; SDSPStream* x8_stereoLeft; SDSPStream* xc_companionRight; SDSPStreamInfo x10_info; float x4c_vol; float m_leftgain, m_rightgain; // DVDFileInfo x50_dvdHandle1; // DVDFileInfo x8c_dvdHandle2; // u32 xc8_streamId = -1; // MusyX stream handle u32 xcc_fileCur = 0; std::unique_ptr xd4_ringBuffer; u32 xd8_ringBytes = 0x11DC0; // 73152 4sec in ADPCM bytes u32 xdc_ringSamples = 0x1f410; // 128016 4sec in samples s8 xe0_curBuffer = -1; bool xe8_silent = true; u8 xec_readState = 0; // 0: NoRead 1: Read 2: ReadWrap std::optional m_file; std::array, 2> m_readReqs; void ReadBuffer(int buf) { u32 halfSize = xd8_ringBytes / 2; u8* data = xd4_ringBuffer.get() + (buf ? halfSize : 0); if (x10_info.x10_loopFlag) { u32 remFileBytes = x10_info.x18_loopEndByte - xcc_fileCur; if (remFileBytes < halfSize) { // printf("Buffering %d from %d into %d\n", remFileBytes, xcc_fileCur + x10_info.x8_headerSize, buf); m_file->AsyncSeekRead(data, remFileBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur = x10_info.x14_loopStartByte; u32 remBytes = halfSize - remFileBytes; // printf("Loop Buffering %d from %d into %d\n", remBytes, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data + remFileBytes, remBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur += remBytes; } else { // printf("Buffering %d from %d into %d\n", halfSize, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data, halfSize, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur += halfSize; } } else { if (xcc_fileCur == x10_info.xc_adpcmBytes) { memset(data, 0, halfSize); return; } u32 remFileBytes = x10_info.xc_adpcmBytes - xcc_fileCur; if (remFileBytes < halfSize) { // printf("Buffering %d from %d into %d\n", remFileBytes, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data, remFileBytes, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); memset(data + remFileBytes, 0, halfSize - remFileBytes); xcc_fileCur = x10_info.xc_adpcmBytes; } else { // printf("Buffering %d from %d into %d\n", halfSize, xcc_fileCur + x10_info.x8_headerSize, buf); m_readReqs[buf] = m_file->AsyncSeekRead(data, halfSize, ESeekOrigin::Begin, xcc_fileCur + x10_info.x8_headerSize); xcc_fileCur += halfSize; } } } bool BufferStream() { if (xec_readState == 0) { ReadBuffer(0); ReadBuffer(1); xec_readState = 1; return false; } else if (xec_readState == 1) { if (m_readReqs[0]->IsComplete()) { xe0_curBuffer = 0; xec_readState = 2; return true; } else { return false; } } else if (xec_readState == 2) { if (xe0_curBuffer == 1 && m_readReqs[0]->IsComplete()) { xe0_curBuffer = 0; ReadBuffer(1); return true; } else if (xe0_curBuffer == 0 && m_readReqs[1]->IsComplete()) { xe0_curBuffer = 1; ReadBuffer(0); return true; } } return false; } unsigned m_curSample = 0; unsigned m_totalSamples = 0; s16 m_prev1 = 0; s16 m_prev2 = 0; void preSupplyAudio(boo::IAudioVoice&, double) override {} unsigned decompressChunk(unsigned readToSample, int16_t*& data) { unsigned startSamp = m_curSample; auto sampDiv = std::div(int(m_curSample), int(14)); if (sampDiv.rem) { unsigned samps = DSPDecompressFrameRanged(data, xd4_ringBuffer.get() + sampDiv.quot * 8, x10_info.x1c_coef, &m_prev1, &m_prev2, unsigned(sampDiv.rem), readToSample - m_curSample); m_curSample += samps; data += samps; ++sampDiv.quot; } while (m_curSample < readToSample) { unsigned samps = DSPDecompressFrame(data, xd4_ringBuffer.get() + sampDiv.quot * 8, x10_info.x1c_coef, &m_prev1, &m_prev2, readToSample - m_curSample); m_curSample += samps; data += samps; ++sampDiv.quot; } return m_curSample - startSamp; } size_t supplyAudio(boo::IAudioVoice&, size_t frames, int16_t* data) override { if (!x0_active) { memset(data, 0, frames * 2); return frames; } if (xe8_silent) { StopStream(); memset(data, 0, frames * 2); return frames; } unsigned halfRingSamples = xdc_ringSamples / 2; size_t remFrames = frames; while (remFrames) { if (xec_readState != 2 || (xe0_curBuffer == 0 && m_curSample >= halfRingSamples)) { if (!BufferStream()) { memset(data, 0, remFrames * 2); return frames; } } unsigned readToSample = std::min(m_curSample + unsigned(remFrames), (m_curSample / halfRingSamples + 1) * halfRingSamples); if (!x10_info.x10_loopFlag) { m_totalSamples += remFrames; size_t fileSamples = x10_info.xc_adpcmBytes * 14 / 8; if (m_totalSamples >= fileSamples) { size_t leftover = m_totalSamples - fileSamples; readToSample -= leftover; remFrames -= leftover; memset(data + remFrames, 0, leftover * 2); StopStream(); } } unsigned leftoverSamples = 0; if (readToSample > xdc_ringSamples) { leftoverSamples = readToSample - xdc_ringSamples; readToSample = xdc_ringSamples; } remFrames -= decompressChunk(readToSample, data); if (leftoverSamples) { BufferStream(); m_curSample = 0; remFrames -= decompressChunk(leftoverSamples, data); } } return frames; } boo::ObjToken m_booVoice; void DoAllocateStream() { xd4_ringBuffer.reset(new u8[0x11DC0]); m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewMonoVoice(32000.0, this); } static void Initialize() { for (size_t i = 0; i < g_Streams.size(); ++i) { SDSPStream& stream = g_Streams[i]; stream.x0_active = false; stream.xd4_ringBuffer.reset(); stream.xd8_ringBytes = 0x11DC0; stream.xdc_ringSamples = 0x1f410; if (i < 2) { stream.x1_oneshot = false; stream.DoAllocateStream(); } else { stream.x1_oneshot = true; } } } static void FreeAllStreams() { for (auto& stream : g_Streams) { stream.m_booVoice.reset(); stream.x0_active = false; for (auto& request : stream.m_readReqs) { if (request) { request->PostCancelRequest(); request.reset(); } } stream.xd4_ringBuffer.reset(); stream.m_file = std::nullopt; } } static s32 PickFreeStream(SDSPStream*& streamOut, bool oneshot) { for (auto& stream : g_Streams) { if (stream.x0_active || stream.x1_oneshot != oneshot) { continue; } stream.x0_active = true; stream.x4_ownerId = ++s_HandleCounter2; if (stream.x4_ownerId == -1) { stream.x4_ownerId = ++s_HandleCounter2; } stream.x8_stereoLeft = nullptr; stream.xc_companionRight = nullptr; streamOut = &stream; return stream.x4_ownerId; } return -1; } static s32 FindStreamIdx(s32 id) { for (size_t i = 0; i < g_Streams.size(); ++i) { const SDSPStream& stream = g_Streams[i]; if (stream.x4_ownerId == id) { return s32(i); } } return -1; } void UpdateStreamVolume(float vol) { x4c_vol = vol; if (!x0_active || xe8_silent) { return; } std::array coefs{}; coefs[size_t(boo::AudioChannel::FrontLeft)] = m_leftgain * vol; coefs[size_t(boo::AudioChannel::FrontRight)] = m_rightgain * vol; m_booVoice->setMonoChannelLevels(nullptr, coefs.data(), true); } static void UpdateVolume(s32 id, float vol) { s32 idx = FindStreamIdx(id); if (idx == -1) return; SDSPStream& stream = g_Streams[idx]; stream.UpdateStreamVolume(vol); if (SDSPStream* left = stream.x8_stereoLeft) left->UpdateStreamVolume(vol); if (SDSPStream* right = stream.xc_companionRight) right->UpdateStreamVolume(vol); } void SilenceStream() { if (!x0_active || xe8_silent) { return; } constexpr std::array coefs{}; m_booVoice->setMonoChannelLevels(nullptr, coefs.data(), true); xe8_silent = true; x0_active = false; } static void Silence(s32 id) { s32 idx = FindStreamIdx(id); if (idx == -1) return; SDSPStream& stream = g_Streams[idx]; stream.SilenceStream(); if (SDSPStream* left = stream.x8_stereoLeft) left->SilenceStream(); if (SDSPStream* right = stream.xc_companionRight) right->SilenceStream(); } void StopStream() { x0_active = false; m_booVoice->stop(); m_file = std::nullopt; } static bool IsStreamActive(s32 id) { s32 idx = FindStreamIdx(id); if (idx == -1) return false; SDSPStream& stream = g_Streams[idx]; return stream.x0_active; } static bool IsStreamAvailable(s32 id) { s32 idx = FindStreamIdx(id); if (idx == -1) return false; SDSPStream& stream = g_Streams[idx]; return !stream.x0_active; } static s32 AllocateMono(const SDSPStreamInfo& info, float vol, bool oneshot) { SDSPStream* stream; s32 id = PickFreeStream(stream, oneshot); if (id == -1) return -1; /* -3dB pan law for mono */ stream->AllocateStream(info, vol, 0.707f, 0.707f); return id; } static s32 AllocateStereo(const SDSPStreamInfo& linfo, const SDSPStreamInfo& rinfo, float vol, bool oneshot) { SDSPStream* lstream; s32 lid = PickFreeStream(lstream, oneshot); if (lid == -1) return -1; SDSPStream* rstream; if (PickFreeStream(rstream, oneshot) == -1) return -1; rstream->x8_stereoLeft = lstream; lstream->xc_companionRight = rstream; lstream->AllocateStream(linfo, vol, 1.f, 0.f); rstream->AllocateStream(rinfo, vol, 0.f, 1.f); return lid; } void AllocateStream(const SDSPStreamInfo& info, float vol, float left, float right) { x10_info = info; m_file.emplace(x10_info.x0_fileName); if (!xd4_ringBuffer) { DoAllocateStream(); } for (auto& request : m_readReqs) { if (request) { request->PostCancelRequest(); request.reset(); } } x4c_vol = vol; m_leftgain = left; m_rightgain = right; xe8_silent = false; xec_readState = 0; xe0_curBuffer = -1; xd8_ringBytes = 0x11DC0; xdc_ringSamples = 0x1f410; xcc_fileCur = 0; m_curSample = 0; m_totalSamples = 0; m_prev1 = 0; m_prev2 = 0; memset(xd4_ringBuffer.get(), 0, 0x11DC0); m_booVoice->resetSampleRate(info.x4_sampleRate); m_booVoice->start(); UpdateStreamVolume(vol); } static std::array g_Streams; }; std::array SDSPStream::g_Streams{}; class CDSPStreamManager { friend struct SDSPStreamInfo; public: enum class EState { Looping, Oneshot, Preparing }; private: dspadpcm_header x0_header; std::string x60_fileName; // arg1 bool x70_24_unclaimed : 1 = true; bool x70_25_headerReadCancelled : 1 = false; u8 x70_26_headerReadState : 2 = 0; // 0: not read 1: reading 2: read s8 x71_companionRight = -1; s8 x72_companionLeft = -1; float x73_volume = 0.f; bool x74_oneshot = false; s32 x78_handleId = -1; // arg2 s32 x7c_streamId = -1; std::shared_ptr m_dvdReq; // DVDFileInfo x80_dvdHandle; static std::array g_Streams; public: CDSPStreamManager() = default; CDSPStreamManager(std::string_view fileName, s32 handle, float volume, bool oneshot) : x60_fileName(fileName) , x70_24_unclaimed(!CDvdFile::FileExists(fileName)) , x73_volume(volume) , x74_oneshot(oneshot) , x78_handleId(handle) {} static s32 FindUnclaimedStreamIdx() { for (size_t i = 0; i < g_Streams.size(); ++i) { const CDSPStreamManager& stream = g_Streams[i]; if (stream.x70_24_unclaimed) { return s32(i); } } return -1; } static bool FindUnclaimedStereoPair(s32& left, s32& right) { const s32 idx = FindUnclaimedStreamIdx(); for (size_t i = 0; i < g_Streams.size(); ++i) { CDSPStreamManager& stream = g_Streams[i]; if (stream.x70_24_unclaimed && idx != s32(i)) { left = idx; right = s32(i); return true; } } return false; } static s32 FindClaimedStreamIdx(s32 handle) { for (size_t i = 0; i < g_Streams.size(); ++i) { const CDSPStreamManager& stream = g_Streams[i]; if (!stream.x70_24_unclaimed && stream.x78_handleId == handle) { return i; } } return -1; } static s32 GetFreeHandleId() { s32 handle; bool good; do { good = true; handle = ++s_HandleCounter; if (handle == -1) { good = false; continue; } for (auto& stream : g_Streams) { if (!stream.x70_24_unclaimed && stream.x78_handleId == handle) { good = false; break; } } } while (!good); return handle; } static EState GetStreamState(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return EState::Oneshot; CDSPStreamManager& stream = g_Streams[idx]; switch (stream.x70_26_headerReadState) { case 0: return EState::Oneshot; case 2: return EState(!stream.x0_header.xc_loop_flag); default: return EState::Preparing; } } static bool CanStop(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return true; CDSPStreamManager& stream = g_Streams[idx]; if (stream.x70_26_headerReadState == 1) return false; if (stream.x7c_streamId == -1) return true; return !SDSPStream::IsStreamActive(stream.x7c_streamId); } static bool IsStreamAvailable(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return false; CDSPStreamManager& stream = g_Streams[idx]; if (stream.x70_26_headerReadState == 1) return false; if (stream.x7c_streamId == -1) return false; return SDSPStream::IsStreamAvailable(stream.x7c_streamId); } static void AllocateStream(s32 idx) { CDSPStreamManager& stream = g_Streams[idx]; SDSPStreamInfo info(stream); if (stream.x71_companionRight == -1) { /* Mono */ if (!stream.x70_25_headerReadCancelled) stream.x7c_streamId = SDSPStream::AllocateMono(info, stream.x73_volume, stream.x74_oneshot); if (stream.x7c_streamId == -1) stream = CDSPStreamManager(); } else { /* Stereo */ CDSPStreamManager& rstream = g_Streams[stream.x71_companionRight]; SDSPStreamInfo rinfo(rstream); if (!stream.x70_25_headerReadCancelled) stream.x7c_streamId = SDSPStream::AllocateStereo(info, rinfo, stream.x73_volume, stream.x74_oneshot); if (stream.x7c_streamId == -1) { stream = CDSPStreamManager(); rstream = CDSPStreamManager(); } } } void HeaderReadComplete() { s32 selfIdx = -1; for (size_t i = 0; i < g_Streams.size(); ++i) { if (this == &g_Streams[i]) { selfIdx = s32(i); break; } } if (x70_24_unclaimed || selfIdx == -1) { *this = CDSPStreamManager(); return; } x70_26_headerReadState = 2; s32 companion = -1; if (x72_companionLeft != -1) companion = x72_companionLeft; else if (x71_companionRight != -1) companion = x71_companionRight; if (companion != -1) { /* Stereo */ CDSPStreamManager& companionStream = g_Streams[companion]; if (companionStream.x70_24_unclaimed || companionStream.x70_26_headerReadState == 0 || (companionStream.x71_companionRight != selfIdx && companionStream.x72_companionLeft != selfIdx)) { /* No consistent companion available */ *this = CDSPStreamManager(); return; } /* Companion is pending; its completion will continue */ if (companionStream.x70_26_headerReadState == 1) return; /* Use whichever stream is the left channel */ if (companionStream.x71_companionRight != -1) AllocateStream(companion); else AllocateStream(selfIdx); } else { /* Mono */ AllocateStream(selfIdx); } } static void PollHeaderReadCompletions() { for (auto& stream : g_Streams) { if (stream.m_dvdReq && stream.m_dvdReq->IsComplete()) { stream.m_dvdReq.reset(); stream.HeaderReadComplete(); } } } static bool StartMonoHeaderRead(CDSPStreamManager& stream) { if (stream.x70_26_headerReadState != 0 || stream.x70_24_unclaimed) return false; CDvdFile file(stream.x60_fileName); if (!file) return false; stream.x70_26_headerReadState = 1; stream.m_dvdReq = file.AsyncRead(&stream.x0_header, sizeof(dspadpcm_header)); return true; } static bool StartStereoHeaderRead(CDSPStreamManager& lstream, CDSPStreamManager& rstream) { if (lstream.x70_26_headerReadState != 0 || lstream.x70_24_unclaimed || rstream.x70_26_headerReadState != 0 || rstream.x70_24_unclaimed) return false; CDvdFile lfile(lstream.x60_fileName); if (!lfile) return false; CDvdFile rfile(rstream.x60_fileName); if (!rfile) return false; lstream.x70_26_headerReadState = 1; rstream.x70_26_headerReadState = 1; lstream.m_dvdReq = lfile.AsyncRead(&lstream.x0_header, sizeof(dspadpcm_header)); rstream.m_dvdReq = rfile.AsyncRead(&rstream.x0_header, sizeof(dspadpcm_header)); return true; } void WaitForReadCompletion() { if (std::shared_ptr req = m_dvdReq) req->WaitUntilComplete(); m_dvdReq.reset(); } static s32 StartStreaming(std::string_view fileName, float volume, bool oneshot) { auto pipePos = fileName.find('|'); if (pipePos == std::string::npos) { /* Mono stream */ s32 idx = FindUnclaimedStreamIdx(); if (idx == -1) return -1; s32 handle = GetFreeHandleId(); CDSPStreamManager tmpStream(fileName, handle, volume, oneshot); if (tmpStream.x70_24_unclaimed) return -1; CDSPStreamManager& stream = g_Streams[idx]; stream = tmpStream; if (!StartMonoHeaderRead(stream)) { stream.x70_25_headerReadCancelled = true; stream.WaitForReadCompletion(); stream = CDSPStreamManager(); return -1; } return handle; } else { /* Stereo stream */ s32 leftIdx = 0; s32 rightIdx = 0; if (!FindUnclaimedStereoPair(leftIdx, rightIdx)) return -1; std::string leftFile(fileName.begin(), fileName.begin() + pipePos); std::string rightFile(fileName.begin() + pipePos + 1, fileName.end()); s32 leftHandle = GetFreeHandleId(); s32 rightHandle = GetFreeHandleId(); CDSPStreamManager tmpLeftStream(leftFile, leftHandle, volume, oneshot); CDSPStreamManager tmpRightStream(rightFile, rightHandle, volume, oneshot); if (tmpLeftStream.x70_24_unclaimed || tmpRightStream.x70_24_unclaimed) return -1; tmpLeftStream.x71_companionRight = s8(rightIdx); tmpRightStream.x72_companionLeft = s8(leftIdx); CDSPStreamManager& leftStream = g_Streams[leftIdx]; CDSPStreamManager& rightStream = g_Streams[rightIdx]; leftStream = tmpLeftStream; rightStream = tmpRightStream; if (!StartStereoHeaderRead(leftStream, rightStream)) { leftStream.x70_25_headerReadCancelled = true; rightStream.x70_25_headerReadCancelled = true; leftStream.WaitForReadCompletion(); leftStream.WaitForReadCompletion(); leftStream = CDSPStreamManager(); rightStream = CDSPStreamManager(); return -1; } return leftHandle; } } static void StopStreaming(s32 handle) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return; CDSPStreamManager& stream = g_Streams[idx]; if (stream.x70_24_unclaimed) return; if (stream.x70_26_headerReadState == 1) { stream.x70_25_headerReadCancelled = true; return; } if (stream.x71_companionRight != -1) g_Streams[stream.x71_companionRight] = CDSPStreamManager(); SDSPStream::Silence(stream.x7c_streamId); stream = CDSPStreamManager(); } static void UpdateVolume(s32 handle, float volume) { s32 idx = FindClaimedStreamIdx(handle); if (idx == -1) return; CDSPStreamManager& stream = g_Streams[idx]; stream.x73_volume = volume; if (stream.x7c_streamId == -1) return; SDSPStream::UpdateVolume(stream.x7c_streamId, volume); } static void Initialize() { SDSPStream::Initialize(); for (auto& stream : g_Streams) { stream = CDSPStreamManager(); } } static void Shutdown() { SDSPStream::FreeAllStreams(); for (auto& stream : g_Streams) { stream = CDSPStreamManager(); } } }; std::array CDSPStreamManager::g_Streams{}; SDSPStreamInfo::SDSPStreamInfo(const CDSPStreamManager& stream) { x0_fileName = stream.x60_fileName.c_str(); x4_sampleRate = SBig(stream.x0_header.x8_sample_rate); xc_adpcmBytes = (SBig(stream.x0_header.x4_num_nibbles) / 2) & 0x7FFFFFE0; if (stream.x0_header.xc_loop_flag) { u32 loopStartNibble = SBig(stream.x0_header.x10_loop_start_nibble); u32 loopEndNibble = SBig(stream.x0_header.x14_loop_end_nibble); x10_loopFlag = true; x14_loopStartByte = (loopStartNibble / 2) & 0x7FFFFFE0; x18_loopEndByte = std::min((loopEndNibble / 2) & 0x7FFFFFE0, xc_adpcmBytes); } else { x10_loopFlag = false; x14_loopStartByte = 0; x18_loopEndByte = 0; } for (int i = 0; i < 8; ++i) { x1c_coef[i][0] = SBig(stream.x0_header.x1c_coef[i][0]); x1c_coef[i][1] = SBig(stream.x0_header.x1c_coef[i][1]); } } enum class EPlayerState { Stopped, FadeIn, Playing, FadeOut, FadeOutNoStop }; struct SDSPPlayer { std::string x0_fileName; EPlayerState x10_playState = EPlayerState::Stopped; float x14_volume = 0.f; float x18_fadeIn = 0.f; float x1c_fadeOut = 0.f; s32 x20_internalHandle = -1; float x24_fadeFactor = 0.f; bool x28_music = true; SDSPPlayer() = default; SDSPPlayer(EPlayerState playing, std::string_view fileName, float volume, float fadeIn, float fadeOut, s32 handle, bool music) : x0_fileName(fileName) , x10_playState(playing) , x14_volume(volume) , x18_fadeIn(fadeIn) , x1c_fadeOut(fadeOut) , x20_internalHandle(handle) , x28_music(music) {} }; using PlayerArray = std::array; static PlayerArray s_Players; // looping, oneshot static PlayerArray s_QueuedPlayers; // looping, oneshot float CStreamAudioManager::GetTargetDSPVolume(float fileVol, bool music) { if (music) return g_MusicUnmute ? (g_MusicVolume * fileVol / 127.f) : 0.f; else return g_SfxUnmute ? (g_SfxVolume * fileVol / 127.f) : 0.f; } void CStreamAudioManager::Start(bool oneshot, std::string_view fileName, float volume, bool music, float fadeIn, float fadeOut) { SDSPPlayer& p = s_Players[oneshot]; SDSPPlayer& qp = s_QueuedPlayers[oneshot]; if (p.x10_playState != EPlayerState::Stopped && !CStringExtras::CompareCaseInsensitive(fileName, p.x0_fileName)) { /* Enque new stream */ qp = SDSPPlayer(EPlayerState::FadeIn, fileName, volume, fadeIn, fadeOut, -1, music); Stop(oneshot, p.x0_fileName); } else if (p.x10_playState != EPlayerState::Stopped) { /* Fade existing stream back in */ p.x18_fadeIn = fadeIn; p.x1c_fadeOut = fadeOut; p.x14_volume = volume; if (p.x18_fadeIn <= FLT_EPSILON) { CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume, p.x28_music)); p.x24_fadeFactor = 1.f; p.x10_playState = EPlayerState::Playing; } else { p.x10_playState = EPlayerState::FadeIn; } } else { /* Start new stream */ EPlayerState state; float vol; if (fadeIn > 0.f) { state = EPlayerState::FadeIn; vol = 0.f; } else { state = EPlayerState::Playing; vol = volume; } s32 handle = CDSPStreamManager::StartStreaming(fileName, GetTargetDSPVolume(vol, music), oneshot); if (handle != -1) p = SDSPPlayer(state, fileName, volume, fadeIn, fadeOut, handle, music); } } void CStreamAudioManager::Stop(bool oneshot, std::string_view fileName) { SDSPPlayer& p = s_Players[oneshot]; SDSPPlayer& qp = s_QueuedPlayers[oneshot]; if (CStringExtras::CompareCaseInsensitive(fileName, qp.x0_fileName)) { /* Cancel enqueued file */ qp = SDSPPlayer(); } else if (CStringExtras::CompareCaseInsensitive(fileName, p.x0_fileName) && p.x20_internalHandle != -1 && p.x10_playState != EPlayerState::Stopped) { /* Fade out or stop */ if (p.x1c_fadeOut <= FLT_EPSILON) StopStreaming(oneshot); else p.x10_playState = EPlayerState::FadeOut; } } void CStreamAudioManager::FadeBackIn(bool oneshot, float fadeTime) { SDSPPlayer& p = s_Players[oneshot]; if (p.x10_playState == EPlayerState::Stopped || p.x10_playState == EPlayerState::Playing) return; p.x18_fadeIn = fadeTime; p.x10_playState = EPlayerState::FadeIn; } void CStreamAudioManager::TemporaryFadeOut(bool oneshot, float fadeTime) { SDSPPlayer& p = s_Players[oneshot]; if (p.x10_playState == EPlayerState::FadeOut || p.x10_playState == EPlayerState::Stopped) return; p.x1c_fadeOut = fadeTime; p.x10_playState = EPlayerState::FadeOutNoStop; } void CStreamAudioManager::StopStreaming(bool oneshot) { SDSPPlayer& p = s_Players[oneshot]; p.x10_playState = EPlayerState::Stopped; CDSPStreamManager::StopStreaming(p.x20_internalHandle); p.x24_fadeFactor = 0.f; p.x20_internalHandle = -1; } void CStreamAudioManager::UpdateDSP(bool oneshot, float dt) { SDSPPlayer& p = s_Players[oneshot]; if (p.x10_playState == EPlayerState::Stopped) { SDSPPlayer& qp = s_QueuedPlayers[oneshot]; if (qp.x10_playState != EPlayerState::Stopped) { Start(oneshot, qp.x0_fileName, qp.x14_volume, qp.x28_music, qp.x18_fadeIn, qp.x1c_fadeOut); qp = SDSPPlayer(); } } else { if (p.x10_playState != EPlayerState::Stopped && CDSPStreamManager::GetStreamState(p.x20_internalHandle) == CDSPStreamManager::EState::Oneshot && CDSPStreamManager::CanStop(p.x20_internalHandle)) { StopStreaming(oneshot); return; } if ((p.x10_playState != EPlayerState::FadeIn && p.x10_playState != EPlayerState::FadeOut && p.x10_playState != EPlayerState::FadeOutNoStop)) { if (p.x10_playState == EPlayerState::Playing) CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume, p.x28_music)); return; } if (p.x10_playState == EPlayerState::FadeIn) { float newFadeFactor = p.x24_fadeFactor + dt / p.x18_fadeIn; if (newFadeFactor >= 1.f) { p.x24_fadeFactor = 1.f; p.x10_playState = EPlayerState::Playing; } else { p.x24_fadeFactor = newFadeFactor; } } else if (p.x10_playState == EPlayerState::FadeOut || p.x10_playState == EPlayerState::FadeOutNoStop) { float newFadeFactor = p.x24_fadeFactor - dt / p.x1c_fadeOut; if (newFadeFactor <= 0.f) { if (p.x10_playState == EPlayerState::FadeOutNoStop) { p.x24_fadeFactor = 0.f; } else { StopStreaming(oneshot); return; } } else { p.x24_fadeFactor = newFadeFactor; } } CDSPStreamManager::UpdateVolume(p.x20_internalHandle, GetTargetDSPVolume(p.x14_volume * p.x24_fadeFactor, p.x28_music)); } } void CStreamAudioManager::UpdateDSPStreamers(float dt) { UpdateDSP(false, dt); UpdateDSP(true, dt); } void CStreamAudioManager::StopAllStreams() { for (size_t i = 0; i < s_Players.size(); ++i) { StopStreaming(bool(i)); SDSPPlayer& p = s_Players[i]; SDSPPlayer& qp = s_QueuedPlayers[i]; p = SDSPPlayer(); qp = SDSPPlayer(); } } void CStreamAudioManager::Update(float dt) { CDSPStreamManager::PollHeaderReadCompletions(); UpdateDSPStreamers(dt); } void CStreamAudioManager::StopAll() { StopAllStreams(); } void CStreamAudioManager::SetMusicUnmute(bool unmute) { g_MusicUnmute = unmute; } void CStreamAudioManager::SetSfxVolume(u8 volume) { g_SfxVolume = std::min(volume, u8(127)); } void CStreamAudioManager::SetMusicVolume(u8 volume) { g_MusicVolume = std::min(volume, u8(127)); } void CStreamAudioManager::Initialize() { CDSPStreamManager::Initialize(); } void CStreamAudioManager::StopOneShot() { CStreamAudioManager::StopStreaming(true); SDSPPlayer& p = s_Players[1]; p = SDSPPlayer(); SDSPPlayer& qp = s_QueuedPlayers[1]; qp = SDSPPlayer(); } void CStreamAudioManager::Shutdown() { CDSPStreamManager::Shutdown(); } u8 CStreamAudioManager::g_MusicVolume = 0x7f; u8 CStreamAudioManager::g_SfxVolume = 0x7f; bool CStreamAudioManager::g_MusicUnmute = true; bool CStreamAudioManager::g_SfxUnmute = true; } // namespace metaforce