diff --git a/src/core/common/imgui/audio.cpp b/src/core/common/imgui/audio.cpp index ba7637ca8..40e949cdd 100644 --- a/src/core/common/imgui/audio.cpp +++ b/src/core/common/imgui/audio.cpp @@ -25,6 +25,7 @@ void ImGuiAudio::DrawMenu() { if (ImGui::BeginMenu("Audio")) { ImGui::MenuItem("Debug General Cache Stats", NULL, &m_windows.cache_stats_general); + ImGui::MenuItem("SoundBuffer Visualization", NULL, &m_windows.cache_visualization); ImGui::EndMenu(); } } @@ -32,6 +33,9 @@ void ImGuiAudio::DrawMenu() void ImGuiAudio::DrawWidgets(bool is_focus, ImGuiWindowFlags input_handler) { //TODO: In need of make interface class to return generic info in some way. - extern void DSound_PrintStats(bool, ImGuiWindowFlags, bool m_show_audio_stats); + extern void DSound_PrintStats(bool, ImGuiWindowFlags, bool m_show_audio_stats); DSound_PrintStats(is_focus, input_handler, m_windows.cache_stats_general); + + extern void DSound_DrawBufferVisualization(bool, bool *p_show, ImGuiWindowFlags); + DSound_DrawBufferVisualization(is_focus, &m_windows.cache_visualization, input_handler); } diff --git a/src/core/common/imgui/settings.h b/src/core/common/imgui/settings.h index a1790545a..9b9e56682 100644 --- a/src/core/common/imgui/settings.h +++ b/src/core/common/imgui/settings.h @@ -44,6 +44,7 @@ typedef struct { typedef struct { bool cache_stats_general; + bool cache_visualization; bool Reserved[3]; } imgui_audio_windows; diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index f0e44fedd..d370ff706 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -365,22 +365,138 @@ xbox::void_xt WINAPI xbox::EMUPATCH(DirectSoundDoWork)() return; } -// For Async process purpose only + +void StreamBufferAudio(xbox::XbHybridDSBuffer* pHybridBuffer, float msToCopy) { + auto pThis = pHybridBuffer->emuDSBuffer; + auto dsb = pThis->EmuDirectSoundBuffer8; + bool isAdpcm = pThis->EmuFlags & DSE_FLAG_XADPCM; + + DWORD xBufferRangeStart; + DWORD xBufferRangeSize; + DSoundBufferRegionCurrentLocation(pHybridBuffer, pThis->EmuPlayFlags, xBufferRangeStart, xBufferRangeSize); + + DWORD hostBufferSize = pThis->EmuBufferDesc.dwBufferBytes; + + DWORD playCursor; + DWORD writeCursor; + dsb->GetCurrentPosition(&playCursor, &writeCursor); + + DWORD cursorGap = writeCursor >= playCursor + ? (writeCursor - playCursor) + : hostBufferSize - playCursor + writeCursor; + + // Determine where to copy data from. + // Note: The DirectSound write cursor can sit quite far ahead of the play cursor, + // but copying closer to the play cursor can introduce weird looping or + // latency issues + // Test case: NBA Live 2005 (writes to a very small buffer expecting low latency, can crackle at > 1ms stream interval) + // Test case: Halo (intro video delay when writing from play cursor) + DWORD writeOffset = writeCursor + cursorGap * g_dsBufferStreaming.tweakCopyOffset; + DWORD writeSize = std::min( + (DWORD)(pThis->EmuBufferDesc.lpwfxFormat->nAvgBytesPerSec * msToCopy / 1000), + hostBufferSize + ); + + DWORD blockSize = isAdpcm + ? XBOX_ADPCM_DSTSIZE * pThis->EmuBufferDesc.lpwfxFormat->nChannels + : pThis->EmuBufferDesc.lpwfxFormat->nBlockAlign; + + // ADPCM block alignment + writeOffset = ((writeOffset + blockSize / 2) / blockSize) * blockSize; + writeSize = ((writeSize + blockSize / 2) / blockSize) * blockSize; + writeOffset %= hostBufferSize; + + DWORD xWriteOffset = DSoundBufferGetXboxBufferSize(pThis->EmuFlags, writeOffset); + + assert(xBufferRangeStart + xBufferRangeSize > xWriteOffset); + if (isAdpcm) { + assert(writeOffset % XBOX_ADPCM_DSTSIZE == 0); + assert(xWriteOffset % XBOX_ADPCM_SRCSIZE == 0); + } + + LPVOID lplpvAudioPtr1, lplpvAudioPtr2; + DWORD lplpvAudioBytes1, lplpvAudioBytes2; + HRESULT hRet = pThis->EmuDirectSoundBuffer8->Lock(writeOffset, writeSize, + &lplpvAudioPtr1, &lplpvAudioBytes1, + &lplpvAudioPtr2, &lplpvAudioBytes2, + 0); + + if (hRet != 0) { + CxbxrKrnlAbort("DirectSoundBuffer Lock Failed!"); + } + + if (lplpvAudioPtr1 && pThis->X_BufferCache != nullptr) { + DSoundBufferOutputXBtoHost( + pThis->EmuFlags, + pThis->EmuBufferDesc, + ((PBYTE)pThis->X_BufferCache + xBufferRangeStart + xWriteOffset), + DSoundBufferGetXboxBufferSize(pThis->EmuFlags, lplpvAudioBytes1), + lplpvAudioPtr1, + lplpvAudioBytes1 + ); + + if (lplpvAudioPtr2) { + DSoundBufferOutputXBtoHost( + pThis->EmuFlags, + pThis->EmuBufferDesc, + ((PBYTE)pThis->X_BufferCache + xBufferRangeStart + 0), + DSoundBufferGetXboxBufferSize(pThis->EmuFlags, lplpvAudioBytes2), + lplpvAudioPtr2, + lplpvAudioBytes2 + ); + } + + HRESULT hRet = dsb->Unlock(lplpvAudioPtr1, lplpvAudioBytes1, lplpvAudioPtr2, lplpvAudioBytes2); + + if (hRet != DS_OK) { + CxbxrKrnlAbort("DirectSoundBuffer Unlock Failed!"); + } + } +} + static void dsound_thread_worker(LPVOID nullPtr) { g_AffinityPolicy->SetAffinityOther(); + const int dsStreamInterval = 300; + int waitCounter = 0; + while (true) { + // FIXME time this loop more accurately + // and account for variation in the length of Sleep calls + // Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often // unless console is open with logging enabled. This is the cause of stopping intro videos often. - Sleep(300); + Sleep(g_dsBufferStreaming.streamInterval); + waitCounter += g_dsBufferStreaming.streamInterval; + // Enforce mutex guard lock only occur inside below bracket for proper compile build. { DSoundMutexGuardLock; - xbox::LARGE_INTEGER getTime; - xbox::KeQuerySystemTime(&getTime); - DirectSoundDoWork_Stream(getTime); + if (waitCounter > dsStreamInterval) { + waitCounter = 0; + + // For Async process purpose only + xbox::LARGE_INTEGER getTime; + xbox::KeQuerySystemTime(&getTime); + DirectSoundDoWork_Stream(getTime); + } + + // Stream sound buffer audio + // because the title may change the content of sound buffers at any time + for (auto& pBuffer : g_pDSoundBufferCache) { + // Avoid expensive calls to DirectSound on buffers unless they've been played at least once + // Since some titles create a large amount of buffers, but only use a few + if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) { + DWORD status; + HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); + if (hRet == 0 && status & DSBSTATUS_PLAYING) { + auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; + StreamBufferAudio(pBuffer, streamMs); + } + } + } } } } @@ -945,6 +1061,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(CDirectSound_SynchPlayback) DSoundBufferSynchPlaybackFlagRemove(pDSBuffer->EmuFlags); EmuLog(LOG_LEVEL::DEBUG, "SynchPlayback - pDSBuffer: %08X; EmuPlayFlags: %08X", *ppDSBuffer, pDSBuffer->EmuPlayFlags); pDSBuffer->EmuDirectSoundBuffer8->Play(0, 0, pDSBuffer->EmuPlayFlags); + pDSBuffer->EmuStreamingInfo.playRequested = true; } } diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.hpp b/src/core/hle/DSOUND/DirectSound/DirectSound.hpp index cefdaf186..abb56bd77 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.hpp @@ -110,6 +110,10 @@ struct EmuDirectSoundBuffer X_DSENVOLOPEDESC Xb_EnvolopeDesc; X_DSVOICEPROPS Xb_VoiceProperties; DWORD Xb_Flags; + struct { + // True if the buffer has been played, and should be considered for streaming + bool playRequested = false; + } EmuStreamingInfo; }; struct XbHybridDSBuffer : DSBUFFER_S::DSBUFFER_I { diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp index 1f9634a88..5952b77cc 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp @@ -80,6 +80,7 @@ void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER &time) pThis->Xb_rtPauseEx = 0LL; pThis->EmuFlags &= ~DSE_FLAG_PAUSE; pThis->EmuDirectSoundBuffer8->Play(0, 0, pThis->EmuPlayFlags); + pThis->EmuStreamingInfo.playRequested = true; } if (pThis->Xb_rtStopEx != 0LL && pThis->Xb_rtStopEx <= time.QuadPart) { @@ -437,55 +438,26 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Lock) LOG_FUNC_END; EmuDirectSoundBuffer* pThis = pHybridThis->emuDSBuffer; - HRESULT hRet = D3D_OK; - DWORD pcmSize = DSoundBufferGetPCMBufferSize(pThis->EmuFlags, dwBytes); - DWORD pcmOffset = DSoundBufferGetPCMBufferSize(pThis->EmuFlags, dwOffset); - DSoundGenericUnlock(pThis->EmuFlags, - pThis->EmuDirectSoundBuffer8, - pThis->EmuBufferDesc, - pThis->Host_lock, - pThis->X_BufferCache, - pThis->X_lock.dwLockOffset, - pThis->X_lock.dwLockBytes1, - pThis->X_lock.dwLockBytes2); + // Xbox directsound doesn't require locking buffers + // This Xbox api only exists to match PC - if (ppvAudioPtr2 == xbox::zeroptr) { - hRet = pThis->EmuDirectSoundBuffer8->Lock(pcmOffset, pcmSize, &pThis->Host_lock.pLockPtr1, &pThis->Host_lock.dwLockBytes1, - nullptr, 0, dwFlags); - pThis->Host_lock.pLockPtr2 = nullptr; - } else { - hRet = pThis->EmuDirectSoundBuffer8->Lock(pcmOffset, pcmSize, &pThis->Host_lock.pLockPtr1, &pThis->Host_lock.dwLockBytes1, - &pThis->Host_lock.pLockPtr2, &pThis->Host_lock.dwLockBytes2, dwFlags); - } - - if (hRet != DS_OK) { - CxbxrKrnlAbort("IDirectSoundBuffer_Lock Failed!"); - } - - // Host lock position - pThis->Host_lock.dwLockOffset = pcmOffset; - pThis->Host_lock.dwLockFlags = dwFlags; - pThis->X_lock.dwLockFlags = dwFlags; - - // Emulate to xbox's lock position - pThis->X_lock.dwLockOffset = dwOffset; - *ppvAudioPtr1 = pThis->X_lock.pLockPtr1 = ((LPBYTE)pThis->X_BufferCache + dwOffset); - *pdwAudioBytes1 = pThis->X_lock.dwLockBytes1 = DSoundBufferGetXboxBufferSize(pThis->EmuFlags, pThis->Host_lock.dwLockBytes1); - if (pThis->Host_lock.pLockPtr2 != nullptr) { - *ppvAudioPtr2 = pThis->X_lock.pLockPtr2 = pThis->X_BufferCache; - *pdwAudioBytes2 = pThis->X_lock.dwLockBytes2 = DSoundBufferGetXboxBufferSize(pThis->EmuFlags, pThis->Host_lock.dwLockBytes2); - } else { - // If secondary pointers are not used, then set them as zero. - // There are applications bug didn't check for audio pointer that is null pointer which should not use invalid audio bytes. - // Since internal functions do set them zero. We'll set them here as well. - if (ppvAudioPtr2 != xbox::zeroptr) { - *ppvAudioPtr2 = xbox::zeroptr; - } - if (pdwAudioBytes2 != xbox::zeroptr) { - *pdwAudioBytes2 = 0; - } - } + if (dwOffset + dwBytes <= pThis->X_BufferCacheSize) { + *pdwAudioBytes1 = dwBytes; + *ppvAudioPtr1 = (PBYTE)pThis->X_BufferCache + dwOffset; + if (ppvAudioPtr2 != nullptr) { + *ppvAudioPtr2 = nullptr; + *pdwAudioBytes2 = 0; + } + } + else { + *pdwAudioBytes1 = pThis->X_BufferCacheSize - dwOffset; + *ppvAudioPtr1 = (PBYTE)pThis->X_BufferCache + dwOffset; + if (ppvAudioPtr2 != nullptr) { + *pdwAudioBytes2 = dwBytes - *pdwAudioBytes1; + *ppvAudioPtr2 = (PBYTE)pThis->X_BufferCache; + } + } LOG_FUNC_BEGIN_ARG_RESULT LOG_FUNC_ARG_RESULT(ppvAudioPtr1) @@ -494,7 +466,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Lock) LOG_FUNC_ARG_RESULT(pdwAudioBytes2) LOG_FUNC_END_ARG_RESULT; - RETURN_RESULT_CHECK(hRet); + RETURN(DS_OK); } // ****************************************************************** @@ -509,7 +481,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Unlock) dword_xt pdwAudioBytes2 ) { - DSoundMutexGuardLock; + // DSoundMutexGuardLock; LOG_FUNC_BEGIN LOG_FUNC_ARG(pHybridThis) @@ -519,28 +491,8 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Unlock) LOG_FUNC_ARG(pdwAudioBytes2) LOG_FUNC_END; - EmuDirectSoundBuffer* pThis = pHybridThis->emuDSBuffer; - // TODO: Find out why pThis->EmuLockPtr1 is nullptr... (workaround atm is to check if it is not a nullptr.) - if (pThis->X_BufferCache != xbox::zeroptr && pThis->Host_lock.pLockPtr1 != nullptr) { - - memcpy_s((PBYTE)pThis->X_BufferCache + pThis->X_lock.dwLockOffset, - pThis->X_BufferCacheSize - pThis->X_lock.dwLockOffset, - pThis->X_lock.pLockPtr1, - pThis->X_lock.dwLockBytes1); - - if (pThis->Host_lock.pLockPtr2 != nullptr) { - memcpy_s(pThis->X_BufferCache, pThis->X_BufferCacheSize, pThis->X_lock.pLockPtr2, pThis->X_lock.dwLockBytes2); - } - } - - DSoundGenericUnlock(pThis->EmuFlags, - pThis->EmuDirectSoundBuffer8, - pThis->EmuBufferDesc, - pThis->Host_lock, - pThis->X_BufferCache, - pThis->X_lock.dwLockOffset, - pThis->X_lock.dwLockBytes1, - pThis->X_lock.dwLockBytes2); + // Xbox directsound doesn't require locking buffers + // This Xbox api only exists to match PC return DS_OK; } @@ -660,6 +612,7 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_Play) } if ((pThis->EmuFlags & DSE_FLAG_SYNCHPLAYBACK_CONTROL) == 0) { hRet = pThis->EmuDirectSoundBuffer8->Play(0, 0, pThis->EmuPlayFlags); + pThis->EmuStreamingInfo.playRequested = true; } } @@ -1594,52 +1547,54 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_StopEx) hRet = pThis->EmuDirectSoundBuffer8->Stop(); pThis->Xb_rtStopEx = 0LL; } - else { - bool isLooping; - if ((pThis->EmuPlayFlags & X_DSBPLAY_LOOPING) > 0) { - isLooping = true; - } - else { - isLooping = false; - } + else if(dwFlags & X_DSBSTOPEX_ENVELOPE) { + bool isLooping = pThis->EmuPlayFlags & X_DSBPLAY_LOOPING; - if ((dwFlags & X_DSBSTOPEX_ENVELOPE) > 0) { - if (rtTimeStamp == 0LL) { - xbox::LARGE_INTEGER getTime; - xbox::KeQuerySystemTime(&getTime); - pThis->Xb_rtStopEx = getTime.QuadPart; - } - else { - pThis->Xb_rtStopEx = rtTimeStamp; - } - pThis->Xb_rtStopEx += (pThis->Xb_EnvolopeDesc.dwRelease * 512) / 48000; + double releaseSamples = pThis->Xb_EnvolopeDesc.dwRelease * 512.0; + + if (rtTimeStamp == 0LL) { + xbox::LARGE_INTEGER getTime; + xbox::KeQuerySystemTime(&getTime); + pThis->Xb_rtStopEx = getTime.QuadPart; } else { pThis->Xb_rtStopEx = rtTimeStamp; } + const double samplesToTicks = 10000000 / 48000.0; + xbox::REFERENCE_TIME releaseTicks = static_cast(releaseSamples * samplesToTicks); + pThis->Xb_rtStopEx += releaseTicks; - if ((dwFlags & X_DSBSTOPEX_RELEASEWAVEFORM) > 0) { - // Release from loop region. - pThis->EmuPlayFlags &= ~X_DSBPLAY_LOOPING; - } - - DWORD dwValue, dwStatus; + DWORD currentPos, dwStatus; pThis->EmuDirectSoundBuffer8->GetStatus(&dwStatus); if (pThis->EmuBufferToggle != X_DSB_TOGGLE_DEFAULT) { - pThis->EmuDirectSoundBuffer8->GetCurrentPosition(nullptr, &dwValue); + pThis->EmuDirectSoundBuffer8->GetCurrentPosition(nullptr, ¤tPos); hRet = pThis->EmuDirectSoundBuffer8->Stop(); - DSoundBufferResizeUpdate(pHybridThis, pThis->EmuPlayFlags, hRet, 0, pThis->X_BufferCacheSize); + // Determine the range of bytes we need to play + // Test case: Outrun 2006 - converting large buffers tanks the FPS + // Is set within DSoundBufferRegionCurrentLocation function + DWORD bufferRangeStart; + DWORD bufferRangeSize; + DSoundBufferRegionCurrentLocation(pHybridThis, pThis->EmuPlayFlags, bufferRangeStart, bufferRangeSize); - dwValue += pThis->EmuRegionPlayStartOffset; - if (isLooping) { - dwValue += pThis->EmuRegionLoopStartOffset; + if (pThis->EmuBufferToggle == X_DSB_TOGGLE_LOOP) { + // if we are to release from loop region, then we need change the size to end of actual buffer cache. + if (dwFlags & X_DSBSTOPEX_RELEASEWAVEFORM) { + bufferRangeSize = pThis->X_BufferCacheSize - bufferRangeStart; + } } + DSoundBufferResizeUpdate(pHybridThis, pThis->EmuPlayFlags, hRet, bufferRangeStart, bufferRangeSize); + pThis->EmuBufferToggle = X_DSB_TOGGLE_DEFAULT; - pThis->EmuDirectSoundBuffer8->SetCurrentPosition(dwValue); + pThis->EmuDirectSoundBuffer8->SetCurrentPosition(currentPos); + } + + if (dwFlags & X_DSBSTOPEX_RELEASEWAVEFORM) { + // Release from loop region. + pThis->EmuPlayFlags &= ~X_DSBPLAY_LOOPING; } if (dwStatus & DSBSTATUS_PLAYING && rtTimeStamp != 0LL) { @@ -1650,6 +1605,9 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(IDirectSoundBuffer_StopEx) pThis->Xb_rtStopEx = 0LL; } } + else { + LOG_TEST_CASE("Expected X_DSBSTOPEX_ENVELOPE"); + } } return hRet; diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp index 3e367ef68..29f1b86aa 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.cpp @@ -27,11 +27,13 @@ #define LOG_PREFIX CXBXR_MODULE::DSOUND #include +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" // For ImVec #include "core/common/imgui/ui.hpp" -#include #include #include "DirectSoundGlobal.hpp" +#include "DirectSoundInline.hpp" // For GetCurrentPosition, RegionCurrentLocation Settings::s_audio g_XBAudio = { 0 }; std::recursive_mutex g_DSoundMutex; @@ -54,6 +56,105 @@ DWORD g_dwXbMemAllocated = 0; DWORD g_dwFree2DBuffers = 0; DWORD g_dwFree3DBuffers = 0; +DsBufferStreaming g_dsBufferStreaming; + +void DrawAudioProgress(xbox::XbHybridDSBuffer* pHybrid, float scaleWidth, ImDrawList* drawList) { + const auto& pBuffer = pHybrid->emuDSBuffer; + + auto cursor = ImGui::GetCursorScreenPos(); + auto width = ImGui::GetContentRegionAvail().x; + + DWORD rawCursor; + HybridDirectSoundBuffer_GetCurrentPosition(pBuffer->EmuDirectSoundBuffer8, &rawCursor, nullptr, pBuffer->EmuFlags); + float scale = (width / pBuffer->X_BufferCacheSize) / scaleWidth; + float playCursor = rawCursor * scale; + + bool isLooping = pBuffer->EmuPlayFlags & X_DSBPLAY_LOOPING; + + auto colSpan = ImColor(0.8f, 0.1f, 0.1f, 0.3f); + auto colRegion = ImColor(0.1f, 0.8f, 0.1, 0.3f); + auto colRegionLoop = ImColor(0.1f, 0.2f, 0.8, 0.3f); + auto colPlay = ImColor(0.8f, 0.8f, 0.1f, 0.6); + float height = 8; + + float sBuf = height * 0.4; + float sReg = height * 1; + float sPlay = height * 0.4; + + // Buffer + auto start = cursor + ImVec2(0, (height - sBuf) / 2); + drawList->AddRectFilled(start, start + ImVec2(pBuffer->X_BufferCacheSize * scale, sBuf), colSpan, 0); + + DWORD bufferRangeStart; + DWORD bufferRangeSize; + DSoundBufferRegionCurrentLocation(pHybrid, pBuffer->EmuPlayFlags, bufferRangeStart, bufferRangeSize); + + bufferRangeStart *= scale; + bufferRangeSize *= scale; + + // Region + start = cursor + ImVec2(bufferRangeStart, (height - sReg) / 2); + drawList->AddRectFilled(start, start + ImVec2(bufferRangeSize, sReg), isLooping ? colRegionLoop : colRegion); + + // Play area + start = cursor + ImVec2(bufferRangeStart, (height - sPlay) / 2); + drawList->AddRectFilled(start, start + ImVec2(playCursor, sPlay), colPlay); + // Play cursor + start = cursor + ImVec2(bufferRangeStart + playCursor, 0); + drawList->AddLine(start, start + ImVec2(0, height), colPlay); + + ImGui::Dummy(ImVec2(pBuffer->X_BufferCacheSize * scale, height)); +} + +void DSound_DrawBufferVisualization(bool is_focus, bool* p_show, ImGuiWindowFlags input_handler) { + if (!*p_show) return; + + DSoundMutexGuardLock; + + ImGui::SetNextWindowPos(ImVec2(IMGUI_MIN_DIST_SIDE, IMGUI_MIN_DIST_TOP), ImGuiCond_FirstUseEver, ImVec2(0.0f, 0.0f)); + ImGui::SetNextWindowSize(ImVec2(200, 275), ImGuiCond_FirstUseEver); + if (ImGui::Begin("DSBuffer Visualization", p_show, input_handler)) { + + static bool showPlayingOnly = true; + ImGui::Checkbox("Show playing only", &showPlayingOnly); + + static float bufferScale = 1; + ImGui::PushItemWidth(100); + ImGui::DragFloat("Audio Scale", &bufferScale, 1 / 1000.f, 1 / 24000.f, 1.f, "%.7f", ImGuiSliderFlags_Logarithmic); + + if (ImGui::CollapsingHeader("Buffering Controls")) { + ImGui::SliderInt("Stream interval (ms)", (int*)&g_dsBufferStreaming.streamInterval, 0, 50); + ImGui::SliderInt("Stream ahead (ms)", (int*)&g_dsBufferStreaming.streamAhead, 0, 1000); + ImGui::SliderFloat("Tweak copy offset", &g_dsBufferStreaming.tweakCopyOffset, -1, 1); + } + + if (ImGui::BeginChild("DSBuffer Graph", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) { + + auto drawList = ImGui::GetWindowDrawList(); + + int index = 0; + for (const auto& i : g_pDSoundBufferCache) { + if (showPlayingOnly) { + DWORD dwStatus; + auto hRet = i->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&dwStatus); + if (hRet != DS_OK || !(dwStatus & DSBSTATUS_PLAYING)) { + continue; + } + } + + // Required to add controls inside the loop + ImGui::PushID(index++); + + DrawAudioProgress(i, bufferScale, drawList); + + ImGui::PopID(); + } + } + ImGui::EndChild(); + } + ImGui::End(); +} + void DSound_PrintStats(bool is_focus, ImGuiWindowFlags input_handler, bool m_show_audio_stats) { DSoundMutexGuardLock; @@ -149,7 +250,7 @@ void DSound_PrintStats(bool is_focus, ImGuiWindowFlags input_handler, bool m_sho ImGui::Text("Total active DSStream = %u", isActive); } } - ImGui::End(); } + ImGui::End(); } } diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp index b5e37c6c7..23e650ed5 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp @@ -61,6 +61,13 @@ extern DWORD g_dwXbMemAllocated; extern DWORD g_dwFree2DBuffers; extern DWORD g_dwFree3DBuffers; +struct DsBufferStreaming { + DWORD streamInterval = 1; + DWORD streamAhead = 50; + float tweakCopyOffset = 0; +}; +extern DsBufferStreaming g_dsBufferStreaming; + // size of DirectSound cache max size #define X_DIRECTSOUND_CACHE_MAX 0x800 diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp index 3c42651b6..1b4aad730 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp @@ -569,7 +569,6 @@ static inline void DSoundBufferResizeUpdate( static inline void DSoundBufferRegionCurrentLocation( xbox::XbHybridDSBuffer* pHybridThis, DWORD dwPlayFlags, - HRESULT &hRet, DWORD &Xb_dwStartOffset, DWORD &Xb_dwByteLength) { @@ -608,7 +607,7 @@ static inline void DSoundBufferUpdate( DWORD Xb_dwByteLength; DWORD Xb_dwStartOffset; - DSoundBufferRegionCurrentLocation(pHybridThis, dwPlayFlags, hRet, Xb_dwStartOffset, Xb_dwByteLength); + DSoundBufferRegionCurrentLocation(pHybridThis, dwPlayFlags, Xb_dwStartOffset, Xb_dwByteLength); DSoundBufferResizeUpdate(pHybridThis, dwPlayFlags, hRet, Xb_dwStartOffset, Xb_dwByteLength); }