1570 lines
45 KiB
C++
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;
|
|
}
|