windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dmsynth/dslink.cpp
2020-09-26 16:20:57 +08:00

1570 lines
45 KiB
C++

//
// Copyright (c) 1996-2001 Microsoft Corporation
// DSLink.cpp
//
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX
//
// We disable this because we use exceptions and do *not* specify -GX (USE_NATIVE_EH in
// sources).
//
// The one place we use exceptions is around construction of objects that call
// InitializeCriticalSection. We guarantee that it is safe to use in this case with
// the restriction given by not using -GX (automatic objects in the call chain between
// throw and handler are not destructed). Turning on -GX buys us nothing but +10% to code
// size because of the unwind code.
//
// Any other use of exceptions must follow these restrictions or -GX must be turned on.
//
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
#pragma warning(disable:4530)
#include <objbase.h>
#include <ks.h>
#include <ksproxy.h>
#include "debug.h"
#include "dmusicc.h"
#include "dmusics.h"
#include "..\shared\validate.h"
#include "synth.h"
#include "DSLink.h"
#include "float.h"
#include "misc.h"
#include "dmksctrl.h"
#define DSBUFFER_LENGTH_SEC 2
extern long g_cComponent;
CDSLinkList g_DSLinkList; // Master list of DSLinks.
void CDSLink::SynthProc()
{
HRESULT hr;
DWORD dwPlayCursor; // current play head (driven by streaming wave crystal)
DWORD dwWriteFromCursor; // current write head
::EnterCriticalSection(&m_CriticalSection);
if (!m_fActive || !m_pBuffer || !m_pIMasterClock)
{
Trace(2, "Warning: SynthSink - Thread in invalid state\n");
::LeaveCriticalSection(&m_CriticalSection);
return;
}
hr = m_pBuffer->GetCurrentPosition(&dwPlayCursor, &dwWriteFromCursor);
if (hr == DS_OK)
{
DWORD dwDeltaFilter = m_dwBufferSize >> 1;
DWORD dwCursorDelta;
if (dwWriteFromCursor >= dwPlayCursor)
dwCursorDelta = dwWriteFromCursor - dwPlayCursor;
else
dwCursorDelta = (dwWriteFromCursor + m_dwBufferSize) - dwPlayCursor;
if (dwCursorDelta > m_dwWriteFromMax)
{
if (dwCursorDelta < dwDeltaFilter)
{
TraceI(2, "Warning: SynthSink - Play to Write cursor distance increased from %lu to %lu\n", m_dwWriteFromMax, dwCursorDelta);
m_dwWriteFromMax = dwCursorDelta;
}
else
{
TraceI(2, "Warning: SynthSink - Play to Write cursor delta value rejected:%lu\n", dwCursorDelta);
SetEvent(g_DSLinkList.m_hEvent);
::LeaveCriticalSection(&m_CriticalSection);
return;
}
}
else
{
m_dwWriteFromMax -= ((m_dwWriteFromMax - dwCursorDelta) / 100);
m_dwWriteFromMax = SampleAlign(m_dwWriteFromMax);
dwCursorDelta = m_dwWriteFromMax;
}
dwWriteFromCursor = (dwPlayCursor + dwCursorDelta) % m_dwBufferSize;
if (m_llAbsWrite == 0)
{
// we just started
m_dwLastPlay = dwPlayCursor;
m_dwLastWrite = dwWriteFromCursor;
m_llAbsWrite = dwCursorDelta;
m_SampleClock.Start(m_pIMasterClock, m_wfSynth.nSamplesPerSec, 0);
m_Clock.Start(); // don't want anybody getting latency time until this thread is running
}
// check for overrun with master clock
REFERENCE_TIME rtMaster;
LONGLONG llMasterSampleTime;
LONGLONG llMasterBytes;
LONGLONG llMasterAhead; // how far master clock is ahead of last known play time
LONGLONG llAbsWriteFrom;
m_pIMasterClock->GetTime(&rtMaster);
RefTimeToSample(rtMaster, &llMasterSampleTime);
llMasterBytes = SampleToByte(llMasterSampleTime);
llMasterAhead = (llMasterBytes > m_llAbsPlay) ? llMasterBytes - m_llAbsPlay : 0;
// check for half-buffer underruns, so backward-moving play cursors can be detected
if (llMasterAhead > dwDeltaFilter)
{
Trace(2, "Warning: SynthSink - Buffer underrun by %lu\n", (long) llMasterAhead - dwDeltaFilter);
m_llAbsPlay = llMasterBytes;
m_dwLastWrite = dwWriteFromCursor;
m_llAbsWrite = llAbsWriteFrom = m_llAbsPlay + dwCursorDelta;
}
else
{
DWORD dwPlayed;
if (dwPlayCursor >= m_dwLastPlay)
dwPlayed = dwPlayCursor - m_dwLastPlay;
else
dwPlayed = (dwPlayCursor + m_dwBufferSize) - m_dwLastPlay;
if (dwPlayed > dwDeltaFilter)
{
Trace(2, "Warning: SynthSink - Play Cursor %lu looks invalid, rejecting it.\n", dwPlayed);
SetEvent(g_DSLinkList.m_hEvent);
::LeaveCriticalSection(&m_CriticalSection);
return;
}
m_llAbsPlay += dwPlayed;
llAbsWriteFrom = m_llAbsPlay + dwCursorDelta;
// how far ahead of the write head are we?
if (llAbsWriteFrom > m_llAbsWrite)
{
DWORD dwWriteMissed;
// we are behind-- let's catch up
dwWriteMissed = DWORD(llAbsWriteFrom - m_llAbsWrite);
Trace(2, "Warning: SynthSink - Write underrun, missed %lu bytes\n", dwWriteMissed);
m_dwLastWrite = dwWriteFromCursor;
m_llAbsWrite += dwWriteMissed;
}
}
m_dwLastPlay = dwPlayCursor;
m_SampleClock.SyncToMaster(ByteToSample(m_llAbsPlay), m_pIMasterClock);
// how much to write?
LONGLONG llAbsWriteTo;
DWORD dwBytesToFill;
llAbsWriteTo = llAbsWriteFrom + m_dwWriteTo;
if (llAbsWriteTo > m_llAbsWrite)
{
dwBytesToFill = DWORD(llAbsWriteTo - m_llAbsWrite);
}
else
{
dwBytesToFill = 0;
}
if (dwBytesToFill)
{
LPVOID lpStart, lpEnd; // Buffer pointers, filled by Lock command.
DWORD dwStart, dwEnd; // For Lock.
hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
if (hr == DSERR_BUFFERLOST)
{
Trace(2, "Warning: SynthSink - Buffer lost\n");
hr = m_pBuffer->Restore();
if (hr == DS_OK)
{
Trace(2, "Warning: SynthSink - Buffer restored\n");
hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
if (hr == DS_OK)
{
Trace(2, "Warning: SynthSink - Play restarted\n");
hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
}
}
}
if (hr == DS_OK)
{
if (dwStart)
{
memset(lpStart, 0, dwStart);
if (m_pSynth)
{
m_pSynth->Render((short*)lpStart, ByteToSample(dwStart), ByteToSample(m_llAbsWrite));
}
m_dwLastWrite += dwStart;
m_llAbsWrite += dwStart;
if (m_dwLastWrite == m_dwBufferSize)
{
m_dwLastWrite = 0;
}
}
if (dwEnd)
{
memset(lpEnd, 0, dwEnd);
if (m_pSynth)
{
m_pSynth->Render((short*)lpEnd, ByteToSample(dwEnd), ByteToSample(m_llAbsWrite));
}
m_dwLastWrite = dwEnd;
m_llAbsWrite += dwEnd;
}
m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);
// write silence into unplayed buffer
if (m_dwLastWrite >= dwPlayCursor)
dwBytesToFill = m_dwBufferSize - m_dwLastWrite + dwPlayCursor;
else
dwBytesToFill = dwPlayCursor - m_dwLastWrite;
hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
if (hr == DSERR_BUFFERLOST)
{
Trace(2, "Warning: SynthSink - Buffer lost\n");
hr = m_pBuffer->Restore();
if (hr == DS_OK)
{
Trace(2, "Warning: SynthSink - Buffer restored\n");
hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
if (hr == DS_OK)
{
Trace(2, "Warning: SynthSink - Play restarted\n");
hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
}
}
}
if (hr == DS_OK)
{
if (dwStart)
{
memset(lpStart, 0, dwStart);
}
if (dwEnd)
{
memset(lpEnd, 0, dwEnd);
}
m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);
}
else
{
Trace(2, "Warning: SynthSink - Failed to lock DS buffer: %x\n", hr);
}
}
else
{
Trace(2, "Warning: SynthSink - Failed to lock DS buffer: %x\n", hr);
}
}
}
else
{
if (hr == DSERR_BUFFERLOST)
{
Trace(2, "Warning: SynthSink - Buffer lost on GetCurrentPosition\n");
hr = m_pBuffer->Restore();
if (hr == DS_OK)
{
Trace(2, "Warning: SynthSink - Buffer restored\n");
hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
if (hr == DS_OK)
{
Trace(2, "Warning: SynthSink - Play restarted\n");
}
}
}
else
{
Trace(0, "Error: SynthSink - Failed to get DS buffer position, error code: %lx\n", hr);
}
}
::LeaveCriticalSection(&m_CriticalSection);
}
void CDSLinkList::SynthProc()
{
for (;;)
{
if (m_fPleaseDie)
{
m_fPleaseDie = FALSE;
break;
}
for (DWORD dwX = 0; dwX < m_dwCount; dwX++)
{
::EnterCriticalSection(&m_CriticalSection);
CDSLink *pLink = GetItem(dwX);
::LeaveCriticalSection(&m_CriticalSection);
if (pLink)
{
if (pLink->m_fActive)
{
pLink->SynthProc();
}
}
}
if (m_dwResolution < 2) m_dwResolution = 2;
if (m_dwResolution > 100) m_dwResolution = 100;
WaitForSingleObject(m_hEvent, m_dwResolution);
}
}
static DWORD WINAPI SynthThread (LPVOID lpThreadParameter)
{
CDSLinkList *pLinkList = (CDSLinkList *) lpThreadParameter;
pLinkList->SynthProc();
return 0;
}
HRESULT CDSLink::Connect()
{
if (!m_pSynth)
{
Trace(0, "Error: SynthSink - Activation failed, SynthSink not initialized\n");
return DMUS_E_SYNTHNOTCONFIGURED;
}
if (!m_pDSound)
{
Trace(0, "Error: SynthSink - Activation failed, IDirectSound not set\n");
return DMUS_E_DSOUND_NOT_SET;
}
if (!IsValidFormat(&m_wfSynth))
{
Trace(0, "Error: SynthSink - Activation failed, format not initialized/valid\n");
return DMUS_E_SYNTHNOTCONFIGURED;
}
if (!m_pIMasterClock)
{
Trace(0, "Error: SynthSink - Activation failed, master clock not set\n");
return DMUS_E_NO_MASTER_CLOCK;
}
if (m_fActive)
{
Trace(0, "Error: SynthSink - Activation failed, already active\n");
return DMUS_E_SYNTHACTIVE;
}
assert(!m_pBuffer);
HRESULT hr = E_FAIL;
::EnterCriticalSection(&m_CriticalSection);
if (!m_pExtBuffer)
{
DSBUFFERDESC dsbdesc;
memset(&dsbdesc, 0, sizeof(dsbdesc));
dsbdesc.dwSize = sizeof(dsbdesc);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
// create primary buffer
if (SUCCEEDED(m_pDSound->CreateSoundBuffer(&dsbdesc, &m_pPrimary, NULL)))
{
WAVEFORMATEX wfPrimary;
memset(&wfPrimary, 0, sizeof(wfPrimary));
if (SUCCEEDED(m_pPrimary->GetFormat(&wfPrimary, sizeof(wfPrimary), NULL)))
{
assert(wfPrimary.wFormatTag == WAVE_FORMAT_PCM);
BOOL fUpgrade = FALSE;
if (wfPrimary.nChannels < m_wfSynth.nChannels)
{
wfPrimary.nChannels = m_wfSynth.nChannels;
fUpgrade = TRUE;
}
if (wfPrimary.nSamplesPerSec < m_wfSynth.nSamplesPerSec)
{
wfPrimary.nSamplesPerSec = m_wfSynth.nSamplesPerSec;
fUpgrade = TRUE;
}
if (wfPrimary.wBitsPerSample < m_wfSynth.wBitsPerSample)
{
wfPrimary.wBitsPerSample = m_wfSynth.wBitsPerSample;
fUpgrade = TRUE;
}
if (fUpgrade)
{
wfPrimary.nBlockAlign = wfPrimary.nChannels * (wfPrimary.wBitsPerSample / 8);
wfPrimary.nAvgBytesPerSec = wfPrimary.nSamplesPerSec * wfPrimary.nBlockAlign;
// the existing format is of lesser quality than we desire, so let's upgrade it
if (FAILED(hr = m_pPrimary->SetFormat( &wfPrimary )))
{
if (hr == DSERR_PRIOLEVELNEEDED)
{
// okay, so maybe the app doen't want us changing primary buffer
Trace(2, "Error: SynthSink - SetFormat on primary buffer failed, lacking priority\n");
hr = S_OK;
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't set primary buffer format\n");
m_pPrimary->Release();
m_pPrimary = NULL;
m_pBuffer = NULL;
hr = E_UNEXPECTED;
}
}
}
else
{
hr = S_OK;
}
if (SUCCEEDED(hr))
{
hr = E_FAIL;
memset(&dsbdesc, 0, sizeof(dsbdesc));
dsbdesc.dwSize = sizeof(dsbdesc);
// need default controls (pan, volume, frequency).
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
// N-second buffer.
dsbdesc.dwBufferBytes = DSBUFFER_LENGTH_SEC * m_wfSynth.nAvgBytesPerSec;
dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&m_wfSynth;
if (SUCCEEDED(m_pDSound->CreateSoundBuffer(&dsbdesc, &m_pBuffer, NULL)))
{
hr = S_OK;
}
else
{
m_pBuffer = NULL;
if (m_pPrimary)
{
m_pPrimary->Release(); m_pPrimary = NULL;
}
Trace(0, "Error: SynthSink - Activation failed, couldn't create secondary buffer\n");
hr = E_UNEXPECTED;
}
}
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't get primary buffer format\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't create primary buffer\n");
hr = E_UNEXPECTED;
}
}
else
{
m_pBuffer = m_pExtBuffer;
m_pBuffer->AddRef();
}
if (m_pBuffer)
{
DSBCAPS dsbcaps;
memset(&dsbcaps, 0, sizeof(dsbcaps));
dsbcaps.dwSize = sizeof(dsbcaps);
if (SUCCEEDED(m_pBuffer->GetCaps(&dsbcaps)))
{
DSCAPS dsCaps ;
memset( &dsCaps, 0, sizeof(DSCAPS) );
dsCaps.dwSize = sizeof(DSCAPS);
if (SUCCEEDED(m_pDSound->GetCaps(&dsCaps)))
{
DWORD dwMinLatency; // ms
// Check for Dsound on top of Wave...
if (dsCaps.dwFlags & DSCAPS_EMULDRIVER)
{
dwMinLatency = 240;
}
else
{
dwMinLatency = 80;
}
DWORD dwGetLatency = dwMinLatency;
if (GetRegValueDword(TEXT("Software\\Microsoft\\DirectMusic"),
TEXT("DSLMinLatency"),
&dwGetLatency))
{
Trace(4, "SynthSink: Registry set to change latency to %ld\n", dwGetLatency);
dwMinLatency = dwGetLatency;
}
m_dwWriteTo = SampleAlign((500 + (m_wfSynth.nAvgBytesPerSec * dwMinLatency)) / 1000);
Trace(4, "SynthSink: Set Latency to %lu\n", dwMinLatency);
m_dwBufferSize = dsbcaps.dwBufferBytes;
m_dwLastWrite = 0;
m_dwLastPlay = 0;
m_llAbsPlay = 0;
// fill initial buffer with silence
LPVOID lpStart, lpEnd;
DWORD dwStart, dwEnd;
if (SUCCEEDED(m_pBuffer->Lock(0, m_dwBufferSize, &lpStart, &dwStart, &lpEnd, &dwEnd, 0)))
{
if (dwStart)
{
memset(lpStart, 0, dwStart);
}
if (dwEnd)
{
memset(lpEnd, 0, dwEnd);
}
m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);
if (SUCCEEDED(m_pBuffer->Play(0, 0, DSBPLAY_LOOPING)))
{
g_DSLinkList.ActivateLink(this);
hr = S_OK;
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't start buffer\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't lock buffer\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't get DS caps\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - Activation failed, couldn't get buffer caps\n");
hr = E_UNEXPECTED;
}
}
if (FAILED(hr))
{
// Clean up
//
if (m_pBuffer)
{
m_pBuffer->Stop();
m_pBuffer->Release();
m_pBuffer = NULL;
}
if (m_pPrimary)
{
m_pPrimary->Release();
m_pPrimary = NULL;
}
m_Clock.Stop();
Clear();
}
::LeaveCriticalSection(&m_CriticalSection);
if (SUCCEEDED(hr))
{
// wait until the pump is primed
for (WORD wRetry = 0; wRetry < 10 && !m_llAbsWrite; wRetry++)
{
Sleep(10);
}
if (m_llAbsWrite)
{
Trace(3, "Warning: SynthSink - Pump is primed\n");
}
else
{
Trace(0, "Error: SynthSink - Pump is NOT primed\n");
}
}
return hr;
}
HRESULT CDSLink::Disconnect()
{
// stop the buffer right away!
::EnterCriticalSection(&m_CriticalSection);
if (m_pBuffer)
{
// write silence to prevent DSound blip bug if reactivated
LPVOID lpStart, lpEnd;
DWORD dwStart, dwEnd;
if (SUCCEEDED(m_pBuffer->Lock(0, m_dwBufferSize, &lpStart, &dwStart, &lpEnd, &dwEnd, 0))) // REVIEW: don't need full buffer size
{
if (dwStart)
{
memset(lpStart, 0, dwStart);
}
if (dwEnd)
{
memset(lpEnd, 0, dwEnd);
}
m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);
Sleep(50); // found experimentally
}
m_pBuffer->Stop();
}
m_Clock.Stop();
::LeaveCriticalSection(&m_CriticalSection);
g_DSLinkList.DeactivateLink(this);
::EnterCriticalSection(&m_CriticalSection);
if (m_pBuffer)
{
m_pBuffer->Release(); m_pBuffer = NULL;
}
if (m_pPrimary)
{
m_pPrimary->Release(); m_pPrimary = NULL;
}
Clear();
::LeaveCriticalSection(&m_CriticalSection);
return S_OK;
}
void CDSLink::Clear()
{
m_llAbsPlay = 0; // Absolute point where play head is.
m_dwLastPlay = 0; // Last point where play head was.
m_llAbsWrite = 0; // Absolute point we've written up to.
m_dwBufferSize = 0; // Size of buffer.
m_dwLastWrite = 0; // Last position we wrote to in buffer.
m_dwWriteTo = 1000; // Distance between write head and where we are writing.
}
CDSLink::CDSLink()
{
InterlockedIncrement(&g_cComponent);
m_fCSInitialized = FALSE;
::InitializeCriticalSection(&m_CriticalSection);
m_fCSInitialized = TRUE;
memset(&m_wfSynth, 0, sizeof(m_wfSynth));
m_pIMasterClock = NULL;
m_cRef = 0;
m_pSynth = NULL; // Reference back to parent Synth.
m_pDSound = NULL;
m_pPrimary = NULL;
m_pBuffer = NULL;
m_pExtBuffer = NULL;
m_dwWriteFromMax = 0;
Clear();
m_Clock.Stop();
m_fActive = FALSE;
}
CDSLink::~CDSLink()
{
if (m_fCSInitialized)
{
::EnterCriticalSection(&m_CriticalSection);
if (m_pIMasterClock)
{
m_pIMasterClock->Release(); m_pIMasterClock = NULL;
}
::LeaveCriticalSection(&m_CriticalSection);
Disconnect();
if (m_pExtBuffer)
{
m_pExtBuffer->Release(); m_pExtBuffer = NULL;
}
if (m_pDSound)
{
m_pDSound->Release(); m_pDSound = NULL;
}
::DeleteCriticalSection(&m_CriticalSection);
}
InterlockedDecrement(&g_cComponent);
}
CDSLinkList::CDSLinkList()
{
m_fOpened = FALSE;
m_fPleaseDie = FALSE;
m_hThread = NULL; // Handle for synth thread.
m_dwThread = 0; // ID for thread.
m_hEvent = NULL; // Used to signal thread.
m_dwCount = 0;
m_dwResolution = 20;
}
BOOL CDSLinkList::OpenUp()
{
if (m_fOpened)
{
Trace(1, "Warning: SynthSink - Already opened\n");
return TRUE;
}
m_fOpened = TRUE;
if (!GetRegValueDword(TEXT("Software\\Microsoft\\DirectMusic"),
TEXT("DSLResolution"),
&m_dwResolution))
{
m_dwResolution = 20;
}
try
{
::InitializeCriticalSection(&m_CriticalSection);
}
catch( ... )
{
m_fOpened = FALSE;
return FALSE;
}
return TRUE;
}
void CDSLinkList::CloseDown()
{
if (m_dwCount)
{
CDSLink *pLink;
if (pLink = GetHead())
{
Trace(0, "Error: SynthSink - Process Detach with port still active. May crash on exit.\n");
}
}
if (!m_fOpened)
{
Trace(2, "Warning: SynthSink - Process Detach, ports all deactivated\n");
}
else
{
m_fOpened = FALSE;
::DeleteCriticalSection(&m_CriticalSection);
}
}
void CDSLinkList::ActivateLink(CDSLink *pLink)
{
::EnterCriticalSection(&m_CriticalSection);
if (!pLink->m_fActive)
{
if (m_dwCount == 0)
{
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
m_hThread = CreateThread(NULL, 0, SynthThread, this, 0, &m_dwThread);
if (m_hThread)
{
if (!SetThreadPriority(m_hThread, THREAD_PRIORITY_TIME_CRITICAL))
{
Trace(0, "Error: SynthSink - Activate couldn't set thread priority\n");
}
}
else
{
Trace(0, "Error: SynthSink - Activate couldn't create thread\n");
}
}
if (!IsMember(pLink))
{
m_dwCount++;
AddTail(pLink);
}
pLink->m_fActive = TRUE;
}
::LeaveCriticalSection(&m_CriticalSection);
}
void CDSLinkList::DeactivateLink(CDSLink *pLink)
{
::EnterCriticalSection(&m_CriticalSection);
if (pLink->m_fActive)
{
if (m_dwCount)
{
Remove(pLink);
m_dwCount--;
}
pLink->m_fActive = FALSE;
if (m_dwCount == 0)
{
if (m_hThread && m_hEvent)
{
m_fPleaseDie = TRUE;
SetEvent(m_hEvent);
if (WaitForSingleObject(m_hThread, 10000) == WAIT_TIMEOUT)
{
Trace(0, "Error: SynthSink - Deactivate, thread did not exit\n");
}
}
if (m_hEvent)
{
CloseHandle(m_hEvent);
m_hEvent = NULL;
}
if(m_hThread)
{
CloseHandle(m_hThread);
m_hThread = NULL;
}
}
}
::LeaveCriticalSection(&m_CriticalSection);
}
CDSLink * CDSLink::GetNext()
{
return (CDSLink *) CListItem::GetNext();
}
void CDSLinkList::AddTail(CDSLink *pNode)
{
CList::AddTail((CListItem *) pNode);
}
void CDSLinkList::Remove(CDSLink *pNode)
{
CList::Remove((CListItem *) pNode);
}
CDSLink * CDSLinkList::GetHead()
{
return (CDSLink *)CList::GetHead();
}
CDSLink * CDSLinkList::RemoveHead()
{
return (CDSLink *)CList::RemoveHead();
}
CDSLink * CDSLinkList::GetItem(LONG index)
{
return (CDSLink *)CList::GetItem(index);
}
STDMETHODIMP CDSLink::QueryInterface(const IID &iid, void **ppv)
{
V_INAME(IDirectMusicSynthSink::QueryInterface);
V_REFGUID(iid);
V_PTRPTR_WRITE(ppv);
if (iid == IID_IUnknown || iid == IID_IDirectMusicSynthSink) {
*ppv = static_cast<IDirectMusicSynthSink*>(this);
}
else if (iid == IID_IKsControl)
{
*ppv = static_cast<IKsControl*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(this)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CDSLink::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CDSLink::Release()
{
if (!InterlockedDecrement(&m_cRef)) {
delete this;
return 0;
}
return m_cRef;
}
STDMETHODIMP CDSLink::Init(
IDirectMusicSynth *pSynth) // <i IDirectMusicSynth> to connect to.
{
m_pSynth = pSynth;
m_Clock.Init(this);
return S_OK;
}
STDMETHODIMP CDSLink::SetMasterClock(
IReferenceClock *pClock) // Master clock to synchronize to.
{
V_INAME(IDirectMusicSynthSink::SetMasterClock);
V_INTERFACE(pClock);
if (m_pIMasterClock)
{
m_pIMasterClock->Release(); m_pIMasterClock = NULL;
}
m_pIMasterClock = pClock;
if (pClock)
{
pClock->AddRef();
}
return S_OK;
}
STDMETHODIMP CDSLink::GetLatencyClock(
IReferenceClock **ppClock) // Returned <i IReferenceClock> interface for latency clock.
{
V_INAME(IDirectMusicSynthSink::GetLatencyClock);
V_PTR_WRITE(ppClock, IReferenceClock *);
return m_Clock.QueryInterface(IID_IReferenceClock, (void **)ppClock);
}
STDMETHODIMP CDSLink::Activate(
BOOL fEnable) // Whether to activate or deactivate audio.
{
if (fEnable)
{
return Connect();
}
return Disconnect();
}
STDMETHODIMP CDSLink::SampleToRefTime(
LONGLONG llSampleTime, // Incoming time, in sample position.
REFERENCE_TIME *prfTime) // Outgoing time, in REFERENCE_TIME units, relative to master clock.
{
V_INAME(IDirectMusicSynthSink::SampleToRefTime);
V_PTR_WRITE(prfTime, REFERENCE_TIME);
m_SampleClock.SampleToRefTime(llSampleTime, prfTime);
return S_OK;
}
STDMETHODIMP CDSLink::RefTimeToSample(
REFERENCE_TIME rfTime, // Incoming time, in REFERENCE_TIME units.
LONGLONG *pllSampleTime) // Outgoing equivalent sample position.
{
V_INAME(IDirectMusicSynthSink::RefTimeToSample);
V_PTR_WRITE(pllSampleTime, LONGLONG);
*pllSampleTime = m_SampleClock.RefTimeToSample(rfTime);
return S_OK;
}
STDMETHODIMP CDSLink::SetDirectSound(
LPDIRECTSOUND pDirectSound, // IDirectSound instance (required).
LPDIRECTSOUNDBUFFER pDirectSoundBuffer) // DirectSound buffer to render to (optional).
{
V_INAME(IDirectMusicSynthSink::SetDirectSound);
V_INTERFACE_OPT(pDirectSound);
V_INTERFACE_OPT(pDirectSoundBuffer);
if (m_fActive)
{
Trace(0, "Error: SynthSink - SetDirectSound failed, can't call while sink is active\n");
return DMUS_E_SYNTHACTIVE;
}
HRESULT hr = E_FAIL;
::EnterCriticalSection(&m_CriticalSection);
if (m_pExtBuffer)
{
m_pExtBuffer->Release(); m_pExtBuffer = NULL;
}
if (m_pDSound)
{
m_pDSound->Release();
}
m_pDSound = pDirectSound;
if (m_pDSound)
{
m_pDSound->AddRef();
if (m_pSynth)
{
DWORD dwWaveFormatExSize = sizeof(m_wfSynth);
if (SUCCEEDED(m_pSynth->GetFormat(&m_wfSynth, &dwWaveFormatExSize))) // update current synth format
{
if (IsValidFormat(&m_wfSynth))
{
m_pExtBuffer = pDirectSoundBuffer;
if (m_pExtBuffer)
{
m_pExtBuffer->AddRef();
// check format
WAVEFORMATEX wfExt;
memset(&wfExt, 0, sizeof(wfExt));
if (SUCCEEDED(m_pExtBuffer->GetFormat(&wfExt, sizeof(wfExt), NULL)))
{
// must exactly match synth format
if (wfExt.wFormatTag == m_wfSynth.wFormatTag &&
wfExt.nChannels == m_wfSynth.nChannels &&
wfExt.nSamplesPerSec == m_wfSynth.nSamplesPerSec &&
wfExt.nBlockAlign == m_wfSynth.nBlockAlign &&
wfExt.nAvgBytesPerSec == m_wfSynth.nAvgBytesPerSec &&
wfExt.wBitsPerSample == m_wfSynth.wBitsPerSample)
{
DSBCAPS dsbcaps;
dsbcaps.dwSize = sizeof(dsbcaps);
if (SUCCEEDED(m_pExtBuffer->GetCaps(&dsbcaps)))
{
// check for invalid flags
if (dsbcaps.dwFlags & (DSBCAPS_PRIMARYBUFFER | DSBCAPS_STATIC))
{
Trace(0, "Error: SynthSink - SetDirectSound failed, buffer not secondary streaming\n");
hr = DMUS_E_INVALIDBUFFER;
}
// is buffer too small?
else if (dsbcaps.dwBufferBytes < m_wfSynth.nAvgBytesPerSec)
{
Trace(0, "Error: SynthSink - SetDirectSound failed, buffer too small\n");
hr = DMUS_E_INSUFFICIENTBUFFER;
}
else
{
hr = S_OK;
}
}
else
{
Trace(0, "Error: SynthSink - SetDirectSound failed, couldn't get buffer caps\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - SetDirectSound failed, format doesn't match synth\n");
hr = DMUS_E_WAVEFORMATNOTSUPPORTED;
}
}
else
{
Trace(0, "Error: SynthSink - SetDirectSound failed, couldn't get buffer format\n");
hr = E_UNEXPECTED;
}
}
else
{
hr = S_OK;
}
}
else
{
Trace(0, "Error: SynthSink - SetDirectSound failed, synth format not valid for this sink\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - SetDirectSound failed, couldn't get synth format\n");
hr = E_UNEXPECTED;
}
}
else
{
Trace(0, "Error: SynthSink - SetDirectSound failed, sink not initialized\n");
hr = DMUS_E_SYNTHNOTCONFIGURED;
}
if (FAILED(hr))
{
if (m_pExtBuffer)
{
m_pExtBuffer->Release(); m_pExtBuffer = NULL;
}
m_pDSound->Release(); m_pDSound = NULL;
}
}
else
{
hr = S_OK;
}
::LeaveCriticalSection(&m_CriticalSection);
return hr;
}
STDMETHODIMP CDSLink::GetDesiredBufferSize(
LPDWORD pdwBufferSizeInSamples)
{
V_INAME(IDirectMusicSynthSink::GetDesiredBufferSize);
V_PTR_WRITE(pdwBufferSizeInSamples, DWORD);
if (!m_pSynth)
{
Trace(0, "Error: SynthSink - GetDesiredBufferSize, sink not initialized\n");
return DMUS_E_SYNTHNOTCONFIGURED;
}
HRESULT hr = E_FAIL;
WAVEFORMATEX wfx;
DWORD dwWaveFormatExSize = sizeof(wfx);
memset(&wfx, 0, sizeof(wfx));
::EnterCriticalSection(&m_CriticalSection);
if (SUCCEEDED(m_pSynth->GetFormat(&wfx, &dwWaveFormatExSize)))
{
*pdwBufferSizeInSamples = DSBUFFER_LENGTH_SEC * wfx.nAvgBytesPerSec;
hr = S_OK;
}
else
{
Trace(0, "Error: SynthSink - GetDesiredBufferSize, couldn't get synth format\n");
hr = E_UNEXPECTED;
}
::LeaveCriticalSection(&m_CriticalSection);
return hr;
}
CClock::CClock()
{
m_pDSLink = NULL;
m_fStopped = TRUE;
}
void CClock::Init(CDSLink *pDSLink)
{
m_pDSLink = pDSLink;
}
HRESULT CClock::QueryInterface( REFIID riid, LPVOID FAR* ppvObj )
{
V_INAME(IReferenceClock::QueryInterface);
V_REFGUID(riid);
V_PTRPTR_WRITE(ppvObj);
if( ::IsEqualIID( riid, IID_IReferenceClock ) ||
::IsEqualIID( riid, IID_IUnknown ) )
{
AddRef();
*ppvObj = this;
return S_OK;
}
*ppvObj = NULL;
return E_NOINTERFACE;
}
ULONG CClock::AddRef()
{
if (m_pDSLink)
{
return m_pDSLink->AddRef();
}
else return 0;
}
ULONG CClock::Release()
{
if (m_pDSLink)
{
return m_pDSLink->Release();
}
else return 0;
}
HRESULT STDMETHODCALLTYPE CClock::AdviseTime( REFERENCE_TIME /*baseTime*/,
REFERENCE_TIME /*streamTime*/,
HANDLE /*hEvent*/,
DWORD __RPC_FAR* /*pdwAdviseCookie*/)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CClock::AdvisePeriodic( REFERENCE_TIME /*startTime*/,
REFERENCE_TIME /*periodTime*/,
HANDLE /*hSemaphore*/,
DWORD __RPC_FAR* /*pdwAdviseCookie*/)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CClock::Unadvise( DWORD /*dwAdviseCookie*/ )
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CClock::GetTime(
REFERENCE_TIME __RPC_FAR* pTime ) // <t ReferenceTime> structure to hold returned time.
{
HRESULT hr = E_FAIL;
if( pTime == NULL )
{
return E_INVALIDARG;
}
if (m_pDSLink != NULL)
{
if (m_pDSLink->m_fActive && !m_fStopped)
{
REFERENCE_TIME rtCompare;
if (m_pDSLink->m_pIMasterClock)
{
m_pDSLink->m_pIMasterClock->GetTime(&rtCompare);
::EnterCriticalSection(&m_pDSLink->m_CriticalSection); // make sure SynthProc is not about to update
hr = m_pDSLink->SampleToRefTime(m_pDSLink->ByteToSample(m_pDSLink->m_llAbsWrite), pTime);
::LeaveCriticalSection(&m_pDSLink->m_CriticalSection);
if (FAILED(hr))
{
Trace(1, "Error: SynthSink Latency Clock: SampleToRefTime failed\n");
return hr;
}
if (*pTime < rtCompare)
{
Trace(3, "Warning: SynthSink Latency Clock off. Latency time is %ldms, Master time is %ldms\n",
(long) (*pTime / 10000), (long) (rtCompare / 10000));
*pTime = rtCompare;
}
else if (*pTime > (rtCompare + (10000 * 1000)))
{
Trace(3, "Warning: SynthSink Latency Clock off. Latency time is %ldms, Master time is %ldms\n",
(long) (*pTime / 10000), (long) (rtCompare / 10000));
*pTime = rtCompare + (10000 * 1000);
}
hr = S_OK;
}
else
{
Trace(2, "Warning: SynthSink Latency Clock - GetTime called with no master clock\n");
}
}
else
{
Trace(2, "Warning: SynthSink Latency Clock - GetTime called with synth sink not active\n");
}
}
return hr;
}
void CClock::Stop()
{
m_fStopped = TRUE;
}
void CClock::Start()
{
m_fStopped = FALSE;
}
static DWORD g_dwPropFalse = FALSE;
static DWORD g_dwPropTrue = TRUE;
SINKPROPERTY CDSLink::m_aProperty[] =
{
{
&GUID_DMUS_PROP_SynthSink_DSOUND,
0,
KSPROPERTY_SUPPORT_GET,
SINKPROP_F_STATIC,
&g_dwPropTrue,
sizeof(g_dwPropTrue),
NULL
},
{
&GUID_DMUS_PROP_SynthSink_WAVE,
0,
KSPROPERTY_SUPPORT_GET,
SINKPROP_F_STATIC,
&g_dwPropFalse,
sizeof(g_dwPropFalse),
NULL
},
{
&GUID_DMUS_PROP_WriteLatency,
0,
KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET,
SINKPROP_F_FNHANDLER,
NULL,
0,
HandleLatency
},
{
&GUID_DMUS_PROP_WritePeriod,
0,
KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET,
SINKPROP_F_STATIC,
&g_DSLinkList.m_dwResolution,
sizeof(g_DSLinkList.m_dwResolution),
NULL
},
{
&GUID_DMUS_PROP_SinkUsesDSound,
0,
KSPROPERTY_SUPPORT_GET,
SINKPROP_F_STATIC,
&g_dwPropTrue,
sizeof(g_dwPropTrue),
NULL
}
};
HRESULT CDSLink::HandleLatency(ULONG ulId, BOOL fSet, LPVOID pbBuffer, PULONG pcbBuffer)
{
DWORD dwLatency;
if (*pcbBuffer != sizeof(dwLatency))
{
return E_INVALIDARG;
}
if (!m_pSynth || !IsValidFormat(&m_wfSynth))
{
return DMUS_E_SYNTHNOTCONFIGURED;
}
if (fSet)
{
dwLatency = *(DWORD*)pbBuffer;
if (dwLatency < 5) dwLatency = 5;
if (dwLatency > 1000) dwLatency = 1000;
m_dwWriteTo = SampleAlign((500 + (m_wfSynth.nAvgBytesPerSec * dwLatency)) / 1000);
}
else
{
dwLatency = m_dwWriteTo * 1000;
if (m_wfSynth.nAvgBytesPerSec)
{
dwLatency += m_wfSynth.nAvgBytesPerSec / 2; // Correct rounding error.
dwLatency /= m_wfSynth.nAvgBytesPerSec;
}
else
{
dwLatency = 300; // Should never happen, trapped by IsValidFormat().
}
*(DWORD*)pbBuffer = dwLatency;
}
return S_OK;
}
const int CDSLink::m_nProperty = sizeof(m_aProperty) / sizeof(m_aProperty[0]);
/*
CDSLink::FindPropertyItem
Given a GUID and an item ID, find the associated property item in the synth's
table of SYNPROPERTY's.
Returns a pointer to the entry or NULL if the item was not found.
*/
SINKPROPERTY *CDSLink::FindPropertyItem(REFGUID rguid, ULONG ulId)
{
SINKPROPERTY *pPropertyItem = &m_aProperty[0];
SINKPROPERTY *pEndOfItems = pPropertyItem + m_nProperty;
for (; pPropertyItem != pEndOfItems; pPropertyItem++)
{
if (*pPropertyItem->pguidPropertySet == rguid &&
pPropertyItem->ulId == ulId)
{
return pPropertyItem;
}
}
return NULL;
}
#define KS_VALID_FLAGS (KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_GET| KSPROPERTY_TYPE_BASICSUPPORT)
STDMETHODIMP CDSLink::KsProperty(
PKSPROPERTY pPropertyIn, ULONG ulPropertyLength,
LPVOID pvPropertyData, ULONG ulDataLength,
PULONG pulBytesReturned)
{
HRESULT hr = E_FAIL;
V_INAME(DirectMusicSynthPort::IKsContol::KsProperty);
V_BUFPTR_WRITE(pPropertyIn, ulPropertyLength);
V_BUFPTR_WRITE_OPT(pvPropertyData, ulDataLength);
V_PTR_WRITE(pulBytesReturned, ULONG);
DWORD dwFlags = pPropertyIn->Flags & KS_VALID_FLAGS;
SINKPROPERTY *pProperty = FindPropertyItem(pPropertyIn->Set, pPropertyIn->Id);
if (pProperty == NULL)
{
Trace(2, "Warning: KsProperty call requested unknown property.\n");
return DMUS_E_UNKNOWN_PROPERTY;
}
if (pvPropertyData == NULL )
{
return E_INVALIDARG;
}
switch (dwFlags)
{
case KSPROPERTY_TYPE_GET:
if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_GET))
{
Trace(1, "Error: SynthSink does not support Get for the requested property.\n");
hr = DMUS_E_GET_UNSUPPORTED;
break;
}
if (pProperty->ulFlags & SINKPROP_F_FNHANDLER)
{
SINKPROPHANDLER pfn = pProperty->pfnHandler;
*pulBytesReturned = ulDataLength;
return (this->*pfn)(pPropertyIn->Id, FALSE, pvPropertyData, pulBytesReturned);
}
if (ulDataLength > pProperty->cbPropertyData)
{
ulDataLength = pProperty->cbPropertyData;
}
CopyMemory(pvPropertyData, pProperty->pPropertyData, ulDataLength);
*pulBytesReturned = ulDataLength;
hr = S_OK;
break;
case KSPROPERTY_TYPE_SET:
if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_SET))
{
Trace(1, "Error: SynthSink does not support Set for the requested property.\n");
hr = DMUS_E_SET_UNSUPPORTED;
break;
}
if (pProperty->ulFlags & SINKPROP_F_FNHANDLER)
{
SINKPROPHANDLER pfn = pProperty->pfnHandler;
hr = (this->*pfn)(pPropertyIn->Id, TRUE, pvPropertyData, &ulDataLength);
}
else
{
if (ulDataLength > pProperty->cbPropertyData)
{
ulDataLength = pProperty->cbPropertyData;
}
CopyMemory(pProperty->pPropertyData, pvPropertyData, ulDataLength);
hr = S_OK;
}
break;
case KSPROPERTY_TYPE_BASICSUPPORT:
// XXX Find out what convention is for this!!
//
if (ulDataLength < sizeof(DWORD) || pvPropertyData == NULL )
{
hr = E_INVALIDARG;
break;
}
*(LPDWORD)pvPropertyData = pProperty->ulSupported;
*pulBytesReturned = sizeof(DWORD);
hr = S_OK;
break;
default:
Trace(1, "Error: KSProperty failed, Flags must contain one of %s\n"
"\tKSPROPERTY_TYPE_SET, KSPROPERTY_TYPE_GET, or KSPROPERTY_TYPE_BASICSUPPORT\n");
hr = E_INVALIDARG;
break;
}
return hr;
}
STDMETHODIMP CDSLink::KsMethod(
PKSMETHOD pMethod, ULONG ulMethodLength,
LPVOID pvMethodData, ULONG ulDataLength,
PULONG pulBytesReturned)
{
V_INAME(DirectMusicSynth::IKsContol::KsMethod);
V_BUFPTR_WRITE(pMethod, ulMethodLength);
V_BUFPTR_WRITE_OPT(pvMethodData, ulDataLength);
V_PTR_WRITE(pulBytesReturned, ULONG);
return DMUS_E_UNKNOWN_PROPERTY;
}
STDMETHODIMP CDSLink::KsEvent(
PKSEVENT pEvent, ULONG ulEventLength,
LPVOID pvEventData, ULONG ulDataLength,
PULONG pulBytesReturned)
{
V_INAME(DirectMusicSynthPort::IKsContol::KsEvent);
V_BUFPTR_WRITE(pEvent, ulEventLength);
V_BUFPTR_WRITE_OPT(pvEventData, ulDataLength);
V_PTR_WRITE(pulBytesReturned, ULONG);
return DMUS_E_UNKNOWN_PROPERTY;
}