Merge pull request #2326 from NZJenkins/exp/dsound-buffer-streaming
DirectSoundBuffer streaming
This commit is contained in:
commit
dc1f93b120
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
bool cache_stats_general;
|
||||
bool cache_visualization;
|
||||
bool Reserved[3];
|
||||
} imgui_audio_windows;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<xbox::REFERENCE_TIME>(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;
|
||||
|
|
|
@ -27,11 +27,13 @@
|
|||
#define LOG_PREFIX CXBXR_MODULE::DSOUND
|
||||
|
||||
#include <imgui.h>
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui_internal.h" // For ImVec
|
||||
#include "core/common/imgui/ui.hpp"
|
||||
|
||||
#include <core\kernel\exports\xboxkrnl.h>
|
||||
#include <dsound.h>
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue