// // 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 #include #include #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(this); } else if (iid == IID_IKsControl) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(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) // 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 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 ) // 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; }