// Copyright (c) 1998-1999 Microsoft Corporation // dmeport.cpp // // CDirectMusicEmulatePort // Implements the MMSYSTEM API version of IDirectMusicPort. // #define INITGUID #include #include #include #include #include #include #include "dmusicc.h" #include "..\dmusic\dmusicp.h" #include "debug.h" #include "dmusic32.h" #include "dm32p.h" #include "dmthunk.h" #include "..\shared\validate.h" #include // KSDATAFORMAT_SUBTYPE_MIDI #pragma warning(disable:4530) #define CLOCK_UPDATE_INTERVAL 100 // milliseconds #define MS_TO_REFERENCE_TIME (10 * 1000) static HRESULT MMRESULTToHRESULT( MMRESULT mmr); static DWORD InputWorker(LPVOID lpv); // @func API call into DLL to get a new port // HRESULT CreateCDirectMusicEmulatePort( PORTENTRY *pPE, CDirectMusic *pDM, LPDMUS_PORTPARAMS pPortParams, CDirectMusicEmulatePort **pPort) { HRESULT hr; *pPort = new CDirectMusicEmulatePort(pPE, pDM); if (NULL == *pPort) { return E_OUTOFMEMORY; } hr = (*pPort)->Init(pPortParams); if (!SUCCEEDED(hr)) { delete *pPort; *pPort = NULL; return hr; } return hr; } // @mfunc Constructor for CDirectMusicEmulatePort // CDirectMusicEmulatePort::CDirectMusicEmulatePort( PORTENTRY *pPE, // @parm The portentry of this device CDirectMusic *pDM):// @parm The CDirectMusic implementation which created this port m_cRef(1), m_id(pPE->idxDevice), m_pDM(pDM), m_hKillThreads(NULL), m_hDataReady(NULL), m_hAppEvent(NULL), m_dwWorkBufferTileInfo(0), m_pThruBuffer(NULL), m_pThruMap(NULL), m_lActivated(0), m_hCaptureThread(NULL), m_pMasterClock(NULL), m_fCSInitialized(FALSE) { m_fIsOutput = (pPE->pc.dwClass == DMUS_PC_OUTPUTCLASS) ? TRUE : FALSE; m_hDevice = NULL; m_pLatencyClock = NULL; dmpc = pPE->pc; } // @mfunc Destructor for CDirectMusicEmulatePort // CDirectMusicEmulatePort::~CDirectMusicEmulatePort() { Close(); } // @mfunc Initialization of CDirectMusicEmulatePort // // @comm Call through the thunk layer to open the requested device. // // Flags we recognize // #define DMUS_ALL_FLAGS (DMUS_PORTPARAMS_VOICES | \ DMUS_PORTPARAMS_CHANNELGROUPS | \ DMUS_PORTPARAMS_AUDIOCHANNELS | \ DMUS_PORTPARAMS_SAMPLERATE | \ DMUS_PORTPARAMS_EFFECTS | \ DMUS_PORTPARAMS_SHARE) // Of those, which do we actually look at? // #define DMUS_SUP_FLAGS (DMUS_PORTPARAMS_CHANNELGROUPS | \ DMUS_PORTPARAMS_SHARE) HRESULT CDirectMusicEmulatePort::Init( LPDMUS_PORTPARAMS pPortParams) { MMRESULT mmr; HRESULT hr; BOOL fChangedParms; // Get, but don't hold onto, the notification interface // hr = m_pDM->QueryInterface(IID_IDirectMusicPortNotify, (void**)&m_pNotify); if (FAILED(hr)) { return hr; } m_pNotify->Release(); // Munge the portparams to match what we support. // fChangedParms = FALSE; if (pPortParams->dwValidParams & ~DMUS_ALL_FLAGS) { Trace(0, "Undefined flags in port parameters: %08X\n", pPortParams->dwValidParams & ~DMUS_ALL_FLAGS); // Flags set we don't recognize. // pPortParams->dwValidParams &= DMUS_ALL_FLAGS; fChangedParms = TRUE; } // We recognize these flags but don't support them. // if (pPortParams->dwValidParams & ~DMUS_SUP_FLAGS) { pPortParams->dwValidParams &= DMUS_SUP_FLAGS; fChangedParms = TRUE; } // Channel groups better be one. // if (pPortParams->dwValidParams & DMUS_PORTPARAMS_CHANNELGROUPS) { if (pPortParams->dwChannelGroups != 1) { pPortParams->dwChannelGroups = 1; fChangedParms = TRUE; } } else { pPortParams->dwValidParams |= DMUS_PORTPARAMS_CHANNELGROUPS; pPortParams->dwChannelGroups = 1; } BOOL fShare = FALSE; if (pPortParams->dwValidParams & DMUS_PORTPARAMS_SHARE) { if (m_fIsOutput) { fShare = pPortParams->fShare; } else { pPortParams->fShare = FALSE; fChangedParms = TRUE; } } else { pPortParams->dwValidParams |= DMUS_PORTPARAMS_SHARE; pPortParams->fShare = fShare; } mmr = OpenLegacyDevice(m_id, m_fIsOutput, fShare, &m_hDevice); if (mmr) { return MMRESULTToHRESULT(mmr); } // Set up the master clock and our latency clock // hr = InitializeClock(); if (FAILED(hr)) { return hr; } // If an input port, initialize capture specific stuff like thruing // if (!m_fIsOutput) { hr = InitializeCapture(); if (FAILED(hr)) { return hr; } } return fChangedParms ? S_FALSE : S_OK; } HRESULT CDirectMusicEmulatePort::InitializeClock() { HRESULT hr; GUID guidMasterClock; DWORD dwThreadID; REFERENCE_TIME rtMasterClock; REFERENCE_TIME rtSlaveClock; hr = m_pDM->GetMasterClock(&guidMasterClock, &m_pMasterClock); if (FAILED(hr)) { return hr; } m_pLatencyClock = new CEmulateLatencyClock(m_pMasterClock); if (NULL == m_pLatencyClock) { return E_OUTOFMEMORY; } #if 0 if (guidMasterClock == GUID_SysClock) { m_fSyncToMaster = FALSE; return S_OK; } #endif m_fSyncToMaster = TRUE; // Read both clocks // hr = m_pMasterClock->GetTime(&rtMasterClock); rtSlaveClock = MS_TO_REFERENCE_TIME * ((ULONGLONG)timeGetTime()); if (FAILED(hr)) { return hr; } m_lTimeOffset = rtMasterClock - rtSlaveClock; return S_OK; } HRESULT CDirectMusicEmulatePort::InitializeCapture() { HRESULT hr; MMRESULT mmr; DWORD dwThreadID; // Allocate thru map for 16 channels, since we only have one channel group // Initialize to no thruing (destination port is NULL). // m_pThruMap = new DMUS_THRU_CHANNEL[MIDI_CHANNELS]; ZeroMemory(m_pThruMap, MIDI_CHANNELS * sizeof(DMUS_THRU_CHANNEL)); // Create thruing buffer // // XXX Defer this until the first call to thru? // // Note: guaranteed by dmusic16 this is the biggest event ever to be returned // (thunk api asking?) // DMUS_BUFFERDESC dmbd; ZeroMemory(&dmbd, sizeof(dmbd)); dmbd.dwSize = sizeof(dmbd); dmbd.cbBuffer = 4096; // XXX Where should we get this??? hr = m_pDM->CreateMusicBuffer(&dmbd, &m_pThruBuffer, NULL); if (FAILED(hr)) { Trace(0, "Failed to create thruing buffer\n"); return hr; } // Create events // m_hDataReady = CreateEvent(NULL, // Event attributes FALSE, // Manual reset FALSE, // Not signalled NULL); // Name m_hKillThreads = CreateEvent(NULL, // Event attributes FALSE, // Manual reset FALSE, // Not signalled NULL); // Name if (m_hDataReady == (HANDLE)NULL || m_hKillThreads == (HANDLE)NULL) { return E_OUTOFMEMORY; } // Set our data ready event for dmusic16 // m_hVxDEvent = OpenVxDHandle(m_hDataReady); Trace(2, "Setting event handle; hDevice %08x hEvent=%08X hVxDEvent=%08X\n", (DWORD)m_hDevice, (DWORD)m_hDataReady, (DWORD)m_hVxDEvent); mmr = MidiInSetEventHandle(m_hDevice, m_hVxDEvent); if (mmr) { Trace(0, "MidiInSetEventHandle returned [%d]\n", mmr); return MMRESULTToHRESULT(mmr); } // Create a tiling for our work buffer so we only need to do it once // m_dwWorkBufferTileInfo = dmTileBuffer((DWORD)m_WorkBuffer, sizeof(m_WorkBuffer)); m_p1616WorkBuffer = TILE_P1616(m_dwWorkBufferTileInfo); if (m_p1616WorkBuffer == NULL) { Trace(0, "Could not tile work buffer\n"); return E_OUTOFMEMORY; } // Initialize cs to protect event queues. // // Unfortunately this can throw an exception if out of memory. // _try { InitializeCriticalSection(&m_csEventQueues); } _except (EXCEPTION_EXECUTE_HANDLER) { return E_OUTOFMEMORY; } m_fCSInitialized = TRUE; m_hCaptureThread = CreateThread(NULL, // Thread attributes 0, // Stack size ::InputWorker, this, 0, // Flags &dwThreadID); if (m_hCaptureThread == NULL) { Trace(0, "CreateThread failed with error %d\n", GetLastError()); return E_OUTOFMEMORY; } return S_OK; } static DWORD WINAPI InputWorker(LPVOID lpv) { CDirectMusicEmulatePort *pPort = (CDirectMusicEmulatePort*)lpv; return pPort->InputWorker(); } // @mfunc // // @comm Standard QueryInterface // STDMETHODIMP CDirectMusicEmulatePort::QueryInterface(const IID &iid, void **ppv) { if (iid == IID_IUnknown || iid == IID_IDirectMusicPort) { *ppv = static_cast(this); } else if (iid == IID_IDirectMusicPortP) { *ppv = static_cast(this); } else if (iid == IID_IDirectMusicPortPrivate) { *ppv = static_cast(this); } else if (iid == IID_IKsControl) { *ppv = static_cast(this); } else if (iid == IID_IDirectMusicThru) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; } // CDirectMusicEmulatePort::AddRef // STDMETHODIMP_(ULONG) CDirectMusicEmulatePort::AddRef() { return InterlockedIncrement(&m_cRef); } // CDirectMusicEmulatePort::Release // STDMETHODIMP_(ULONG) CDirectMusicEmulatePort::Release() { if (!InterlockedDecrement(&m_cRef)) { if (m_pNotify) { m_pNotify->NotifyFinalRelease(static_cast(this)); } delete this; return 0; } return m_cRef; } ////////////////////////////////////////////////////////////////////// // CDirectMusicEmulatePort::Compact STDMETHODIMP CDirectMusicEmulatePort::Compact() { return E_NOTIMPL; } ////////////////////////////////////////////////////////////////////// // CDirectMusicEmulatePort::GetCaps STDMETHODIMP CDirectMusicEmulatePort::GetCaps( LPDMUS_PORTCAPS pPortCaps) { V_INAME(IDirectMusicPort::GetCaps); V_STRUCTPTR_WRITE(pPortCaps, DMUS_PORTCAPS); if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } CopyMemory(pPortCaps, &dmpc, sizeof(DMUS_PORTCAPS)); return S_OK; } ////////////////////////////////////////////////////////////////////// // CDirectMusicEmulatePort::DeviceIoControl STDMETHODIMP CDirectMusicEmulatePort::DeviceIoControl( DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::SetNumChannelGroups( DWORD dwNumChannelGroups) { if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } if (dwNumChannelGroups != 1) { return E_INVALIDARG; } return S_OK; } STDMETHODIMP CDirectMusicEmulatePort::GetNumChannelGroups( LPDWORD pdwChannelGroups) { V_INAME(IDirectMusicPort::GetNumChannelGroups); V_PTR_WRITE(pdwChannelGroups, DWORD); if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } *pdwChannelGroups = 1; return S_OK; } // @mfunc Queue a buffer for playback // #define REFTIME_TO_MS (10L*1000L) STDMETHODIMP CDirectMusicEmulatePort::PlayBuffer( IDirectMusicBuffer *pIBuffer) { CDirectMusicBuffer *pBuffer = reinterpret_cast(pIBuffer); REFERENCE_TIME rt; LPBYTE pbData; DWORD cbData; DWORD dwTileInfo; LONGLONG msTime; MMRESULT mmr; V_INAME(IDirectMusicPort::PlayBuffer); V_INTERFACE(pIBuffer); if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } if (!m_fIsOutput) { return E_NOTIMPL; } if (!m_lActivated) { return DMUS_E_SYNTHINACTIVE; } // Make sure the object doesn't disappear out from under us while we're in Win16 // pBuffer->AddRef(); pBuffer->GetUsedBytes(&cbData); if (cbData == 0) { pBuffer->Release(); return S_OK; } pBuffer->GetRawBufferPtr(&pbData); assert(pbData); pBuffer->GetStartTime(&rt); // Adjust timebase if we are not using the timeGetTime clock // Trace(2, "Buffer base time %I64d timeGetTime %u\n", rt, timeGetTime()); SyncClocks(); MasterToSlave(&rt); Trace(2, "Buffer adjusted base time %I64d\n", rt); msTime = rt / REFTIME_TO_MS; // Send it through the thunk // dwTileInfo = dmTileBuffer((DWORD)pbData, cbData); mmr = MidiOutSubmitPlaybackBuffer(m_hDevice, TILE_P1616(dwTileInfo), cbData, (DWORD)msTime, (DWORD)(rt & 0xFFFFFFFF), // RefTime low (DWORD)((rt >> 32) & 0xFFFFFFFF)); // RefTime high dmUntileBuffer(dwTileInfo); pBuffer->Release(); return MMRESULTToHRESULT(mmr); } STDMETHODIMP CDirectMusicEmulatePort::Read( IDirectMusicBuffer *pIBuffer) { HRESULT hr; V_INAME(IDirectMusicPort::Read); V_INTERFACE(pIBuffer); if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } if (m_fIsOutput) { return E_NOTIMPL; } LPBYTE pbBuffer; hr = pIBuffer->GetRawBufferPtr(&pbBuffer); if (FAILED(hr)) { return hr; } DWORD cbBuffer; hr = pIBuffer->GetMaxBytes(&cbBuffer); if (FAILED(hr)) { return hr; } Trace(1, "Read: buffer size %u\n", cbBuffer); LPBYTE pbData = pbBuffer; // Since events are now buffered, we read them out of the local queue // // EnterCriticalSection(&m_csEventQueues); REFERENCE_TIME rtStart; if (m_ReadEvents.pFront) { rtStart = m_ReadEvents.pFront->e.rtDelta; } else { Trace(2, "Read: No events queued\n"); } while (m_ReadEvents.pFront) { QUEUED_EVENT *pQueuedEvent = m_ReadEvents.pFront; DWORD cbQueuedEvent = DMUS_EVENT_SIZE(pQueuedEvent->e.cbEvent); Trace(2, "Read: cbEvent %u cbQueuedEvent %u\n", pQueuedEvent->e.cbEvent, cbQueuedEvent); if (cbQueuedEvent > cbBuffer) { Trace(2, "Read: No more room for events in buffer.\n"); break; } Trace(2, "Read: Got an event!\n"); pQueuedEvent->e.rtDelta -= rtStart; CopyMemory(pbData, &pQueuedEvent->e, sizeof(DMEVENT) - sizeof(DWORD) + pQueuedEvent->e.cbEvent); pbData += cbQueuedEvent; cbBuffer -= cbQueuedEvent; m_ReadEvents.pFront = pQueuedEvent->pNext; if (pQueuedEvent->e.cbEvent <= sizeof(DWORD)) { // This event came out of the pool // m_FreeEvents.Free(pQueuedEvent); } else { // This event was allocated via new char[] // char *pOriginalMemory = (char*)pQueuedEvent; delete[] pOriginalMemory; } } if (m_ReadEvents.pFront == NULL) { m_ReadEvents.pRear = NULL; } LeaveCriticalSection(&m_csEventQueues); // Update the buffer header information to match the events just packed // Trace(2, "Read: Leaving with %u bytes in buffer\n", (unsigned)(pbData - pbBuffer)); pIBuffer->SetStartTime(rtStart); pIBuffer->SetUsedBytes(pbData - pbBuffer); return (pbData == pbBuffer) ? S_FALSE : S_OK; } STDMETHODIMP CDirectMusicEmulatePort::SetReadNotificationHandle( HANDLE hEvent) { if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } if (m_fIsOutput) { return E_NOTIMPL; } m_hAppEvent = hEvent; return S_OK; } STDMETHODIMP CDirectMusicEmulatePort::DownloadInstrument( IDirectMusicInstrument *pInstrument, IDirectMusicDownloadedInstrument **pDownloadedInstrument, DMUS_NOTERANGE *pRange, DWORD dw) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::UnloadInstrument( IDirectMusicDownloadedInstrument *pDownloadedInstrument) { V_INAME(IDirectMusicPort::UnloadInstrument); V_INTERFACE(pDownloadedInstrument); return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::GetLatencyClock( IReferenceClock **ppClock) { V_INAME(IDirectMusicPort::GetLatencyClock); V_PTRPTR_WRITE(ppClock); if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } m_pLatencyClock->AddRef(); *ppClock = m_pLatencyClock; return S_OK; } STDMETHODIMP CDirectMusicEmulatePort::GetRunningStats( LPDMUS_SYNTHSTATS pStats) { V_INAME(IDirectMusicPort::GetRunningStats); V_STRUCTPTR_WRITE(pStats, DMUS_SYNTHSTATS); return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::Activate( BOOL fActivate) { MMRESULT mmr; V_INAME(IDirectMusicPort::Activate); if (!m_pDM) { return DMUS_E_DMUSIC_RELEASED; } if (fActivate) { if (InterlockedExchange(&m_lActivated, 1)) { Trace(0, "Activate: Already active\n"); // Already activated // return S_FALSE; } mmr = ActivateLegacyDevice(m_hDevice, TRUE); if (mmr) { Trace(0, "Activate: Activate mmr %d\n", mmr); m_lActivated = 0; } } else { if (InterlockedExchange(&m_lActivated, 0) == 0) { Trace(0, "Activate: Already inactive\n"); // Already deactivated // return S_FALSE; } mmr = ActivateLegacyDevice(m_hDevice, FALSE); if (mmr) { Trace(0, "Activate: Deactivate mmr %d\n", mmr); m_lActivated = 1; } } return MMRESULTToHRESULT(mmr); } STDMETHODIMP CDirectMusicEmulatePort::SetChannelPriority( DWORD dwChannelGroup, DWORD dwChannel, DWORD dwPriority) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::GetChannelPriority( DWORD dwChannelGroup, DWORD dwChannel, LPDWORD pdwPriority) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::Close() { if (m_hCaptureThread) { SetEvent(m_hKillThreads); if (WaitForSingleObject(m_hCaptureThread, THREAD_KILL_TIMEOUT) == WAIT_TIMEOUT) { Trace(0, "Warning: Input thread timed out; exit anyway.\n"); } m_hCaptureThread = NULL; } if (m_pThruMap) { for (int iChannel = 0; iChannel < 16; iChannel++) { if (m_pThruMap[iChannel].pDestinationPort == NULL) { continue; } if (m_pThruMap[iChannel].fThruInWin16) { MMRESULT mmr = MidiInThru(m_hDevice, (DWORD)iChannel, 0, NULL); } m_pThruMap[iChannel].pDestinationPort->Release(); } delete[] m_pThruMap; m_pThruMap = NULL; } if (m_pThruBuffer) { m_pThruBuffer->Release(); m_pThruBuffer = NULL; } if (m_hDataReady) { CloseHandle(m_hDataReady); m_hDataReady = NULL; } if (m_hKillThreads) { CloseHandle(m_hKillThreads); m_hKillThreads = NULL; } if (m_hAppEvent) { m_hAppEvent = NULL; } if (m_dwWorkBufferTileInfo) { dmUntileBuffer(m_dwWorkBufferTileInfo); m_dwWorkBufferTileInfo = 0; m_p1616WorkBuffer = NULL; } if (m_hVxDEvent) { CloseVxDHandle(m_hVxDEvent); m_hVxDEvent = NULL; } if (m_hDevice) { CloseLegacyDevice(m_hDevice); m_hDevice = NULL; } if (m_pMasterClock) { m_pMasterClock->Release(); m_pMasterClock = NULL; } if (m_pLatencyClock) { m_pLatencyClock->Close(); m_pLatencyClock->Release(); m_pLatencyClock = NULL; } if (m_fCSInitialized) { DeleteCriticalSection(&m_csEventQueues); } m_pDM = NULL; m_pNotify = NULL; return S_OK; } STDMETHODIMP CDirectMusicEmulatePort::Report() { return S_OK; } // StartVoice and StopVoice don't work on legacy devices // STDMETHODIMP CDirectMusicEmulatePort::StartVoice( DWORD dwVoiceId, DWORD dwChannel, DWORD dwChannelGroup, REFERENCE_TIME rtStart, DWORD dwDLId, LONG prPitch, LONG veVolume, SAMPLE_TIME stVoiceStart, SAMPLE_TIME stLoopStart, SAMPLE_TIME stLoopEnd) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::StopVoice( DWORD dwVoiceID, REFERENCE_TIME rtStop) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::GetVoiceState( DWORD dwVoice[], DWORD cbVoice, DMUS_VOICE_STATE VoiceState[]) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::Refresh( DWORD dwDownloadID, DWORD dwFlags) { return E_NOTIMPL; } // CDirectMusicEmulatePort::ThruChannel // STDMETHODIMP CDirectMusicEmulatePort::ThruChannel( DWORD dwSourceChannelGroup, DWORD dwSourceChannel, DWORD dwDestinationChannelGroup, DWORD dwDestinationChannel, LPDIRECTMUSICPORT pDestinationPort) { V_INAME(IDirectMusicPort::Thru); V_INTERFACE_OPT(pDestinationPort); if (m_fIsOutput) { return E_NOTIMPL; } // Channel group must not be zero (broadcast) but in range 1..NumChannelGroups] // (which for legacy is always 1) // if (dwSourceChannelGroup != 1 || dwSourceChannel > 15) { return E_INVALIDARG; } // Given a port means enable thruing for this channel; NULL means // disable. // if (pDestinationPort) { // Enabling thruing on this channel. First look at the destination port. // DMUS_PORTCAPS dmpc; dmpc.dwSize = sizeof(dmpc); HRESULT hr = pDestinationPort->GetCaps(&dmpc); if (FAILED(hr)) { Trace(0, "ThruChannel: Destination port failed portcaps [%08X]\n", hr); return hr; } // Port must be an output port // if (dmpc.dwClass != DMUS_PC_OUTPUTCLASS) { return DMUS_E_PORT_NOT_RENDER; } // Channel group and channel must be in range. // if (dwDestinationChannel > 15 || dwDestinationChannelGroup > dmpc.dwMaxChannelGroups) { return E_INVALIDARG; } // Release existing port // if (m_pThruMap[dwSourceChannel].pDestinationPort) { // Reference to another port type, release it. // (NOTE: No need to turn off native dmusic16 thruing at this point, // that's handled in dmusic16). // m_pThruMap[dwSourceChannel].pDestinationPort->Release(); } m_pThruMap[dwSourceChannel].dwDestinationChannel = dwDestinationChannel; m_pThruMap[dwSourceChannel].dwDestinationChannelGroup = dwDestinationChannelGroup; m_pThruMap[dwSourceChannel].pDestinationPort = pDestinationPort; m_pThruMap[dwSourceChannel].fThruInWin16 = FALSE; // Is the destination also a legacy port? // if (dmpc.dwType == DMUS_PORT_WINMM_DRIVER) { // Woohoo! We can do native thruing in Win16! // m_pThruMap[dwSourceChannel].fThruInWin16 = TRUE; Trace(2, "32: Thruing <%d> -> <%d> in Win16\n", dwSourceChannel, dwDestinationChannel); MMRESULT mmr = MidiInThru(m_hDevice, dwSourceChannel, dwDestinationChannel, ((CDirectMusicEmulatePort*)pDestinationPort)->m_hDevice); if (mmr) { Trace(0, "ThruChannel: MidiInThru returned %d\n", mmr); return MMRESULTToHRESULT(mmr); } } else { Trace(2, "ThruChannel: From (%u,%u) -> (%u,%u,%p)\n", dwSourceChannelGroup, dwSourceChannel, dwDestinationChannelGroup, dwDestinationChannel, pDestinationPort); } pDestinationPort->AddRef(); } else { // Disabling thruing on this channel // if (m_pThruMap[dwSourceChannel].pDestinationPort) { if (m_pThruMap[dwSourceChannel].fThruInWin16) { MMRESULT mmr = MidiInThru(m_hDevice, dwSourceChannel, 0, (HANDLE)NULL); if (mmr) { Trace(0, "ThruChannel: MidiInThru returned %d\n", mmr); return MMRESULTToHRESULT(mmr); } } m_pThruMap[dwSourceChannel].pDestinationPort->Release(); m_pThruMap[dwSourceChannel].pDestinationPort = NULL; } } return S_OK; } STDMETHODIMP CDirectMusicEmulatePort::SetDirectSound( LPDIRECTSOUND pDirectSound, LPDIRECTSOUNDBUFFER pDirectSoundBuffer) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::GetFormat( LPWAVEFORMATEX pWaveFormatEx, LPDWORD pdwWaveFormatExSize, LPDWORD pdwBufferSize) { return E_NOTIMPL; } // CDirectMusicEmulatePort::DownloadWave // STDMETHODIMP CDirectMusicEmulatePort::DownloadWave( IDirectSoundWave *pWave, IDirectSoundDownloadedWaveP **ppWave, REFERENCE_TIME rtStartHint) { V_INAME(IDirectMusicPort::DownloadWave); V_INTERFACE(pWave); V_PTRPTR_WRITE(ppWave); return E_NOTIMPL; } // CDirectMusicEmulatePort::UnloadWave // STDMETHODIMP CDirectMusicEmulatePort::UnloadWave( IDirectSoundDownloadedWaveP *pDownloadedWave) { V_INAME(IDirectMusicPort::UnloadWave); V_INTERFACE(pDownloadedWave); return E_NOTIMPL; } // CDirectMusicEmulatePort::AllocVoice // STDMETHODIMP CDirectMusicEmulatePort::AllocVoice( IDirectSoundDownloadedWaveP *pWave, DWORD dwChannel, DWORD dwChannelGroup, REFERENCE_TIME rtStart, SAMPLE_TIME stLoopStart, SAMPLE_TIME stLoopEnd, IDirectMusicVoiceP **ppVoice) { V_INAME(IDirectMusicPort::AllocVoice); V_INTERFACE(pWave); V_PTRPTR_WRITE(ppVoice); return E_NOTIMPL; } // CDirectMusicEmulatePort::AssignChannelToBuses // STDMETHODIMP CDirectMusicEmulatePort::AssignChannelToBuses( DWORD dwChannelGroup, DWORD dwChannel, LPDWORD pdwBuses, DWORD cBusCount) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::SetSink( IDirectSoundConnect *pSinkConnect) { return E_NOTIMPL; } STDMETHODIMP CDirectMusicEmulatePort::GetSink( IDirectSoundConnect **ppSinkConnect) { return E_NOTIMPL; } GENERICPROPERTY CDirectMusicEmulatePort::m_aProperty[] = { { &GUID_DMUS_PROP_LegacyCaps, // Set 0, // Item KSPROPERTY_SUPPORT_GET, // KS support flags GENPROP_F_FNHANDLER, // GENPROP flags NULL, 0, // static data and size CDirectMusicEmulatePort::LegacyCaps // Handler } }; const int CDirectMusicEmulatePort::m_nProperty = sizeof(m_aProperty) / sizeof(m_aProperty[0]); HRESULT CDirectMusicEmulatePort::LegacyCaps( ULONG ulId, BOOL fSet, LPVOID pbBuffer, PULONG pcbBuffer) { if (fSet == KSPROPERTY_SUPPORT_SET) { return DMUS_E_SET_UNSUPPORTED; } MIDIINCAPS mic; MIDIOUTCAPS moc; LPBYTE pbData; ULONG cbData; if (m_fIsOutput) { MMRESULT mmr = midiOutGetDevCaps(m_id, &moc, sizeof(moc)); if (mmr) { Trace(0, "midiOutGetDevCaps failed!\n"); return MMRESULTToHRESULT(mmr); } pbData = (LPBYTE)&moc; cbData = sizeof(moc); } else { MMRESULT mmr = midiInGetDevCaps(m_id, &mic, sizeof(mic)); if (mmr) { Trace(0, "midiInGetDevCaps failed!\n"); return MMRESULTToHRESULT(mmr); } pbData = (LPBYTE)&mic; cbData = sizeof(mic); } ULONG cbToCopy = min(*pcbBuffer, cbData); CopyMemory(pbBuffer, pbData, cbToCopy); *pcbBuffer = cbToCopy; return S_OK; } // // CDirectMusicEmulatePort::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. // GENERICPROPERTY *CDirectMusicEmulatePort::FindPropertyItem(REFGUID rguid, ULONG ulId) { GENERICPROPERTY *pPropertyItem = &m_aProperty[0]; GENERICPROPERTY *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 CDirectMusicEmulatePort::KsProperty( PKSPROPERTY pPropertyIn, ULONG ulPropertyLength, LPVOID pvPropertyData, ULONG ulDataLength, PULONG pulBytesReturned) { 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; if ((dwFlags == 0) || (dwFlags == (KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_GET))) { } GENERICPROPERTY *pProperty = FindPropertyItem(pPropertyIn->Set, pPropertyIn->Id); if (pProperty == NULL) { return DMUS_E_UNKNOWN_PROPERTY; } switch (dwFlags) { case KSPROPERTY_TYPE_GET: if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_GET)) { return DMUS_E_GET_UNSUPPORTED; } if (pProperty->ulFlags & GENPROP_F_FNHANDLER) { GENPROPHANDLER pfn = pProperty->pfnHandler; *pulBytesReturned = ulDataLength; return (this->*pfn)(pPropertyIn->Id, KSPROPERTY_SUPPORT_GET, pvPropertyData, pulBytesReturned); } if (ulDataLength > pProperty->cbPropertyData) { ulDataLength = pProperty->cbPropertyData; } CopyMemory(pvPropertyData, pProperty->pPropertyData, ulDataLength); *pulBytesReturned = ulDataLength; return S_OK; case KSPROPERTY_TYPE_SET: if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_SET)) { return DMUS_E_SET_UNSUPPORTED; } if (pProperty->ulFlags & GENPROP_F_FNHANDLER) { GENPROPHANDLER pfn = pProperty->pfnHandler; return (this->*pfn)(pPropertyIn->Id, KSPROPERTY_SUPPORT_SET, pvPropertyData, &ulDataLength); } if (ulDataLength > pProperty->cbPropertyData) { ulDataLength = pProperty->cbPropertyData; } CopyMemory(pProperty->pPropertyData, pvPropertyData, ulDataLength); return S_OK; case KSPROPERTY_TYPE_BASICSUPPORT: if (pProperty == NULL) { return DMUS_E_UNKNOWN_PROPERTY; } // XXX Find out what convention is for this!! // if (ulDataLength < sizeof(DWORD)) { return E_INVALIDARG; } *(LPDWORD)pvPropertyData = pProperty->ulSupported; *pulBytesReturned = sizeof(DWORD); return S_OK; } Trace(-1, "%s: Flags must contain one of\n" "\tKSPROPERTY_TYPE_SET, KSPROPERTY_TYPE_GET, or KSPROPERTY_TYPE_BASICSUPPORT\n"); return E_INVALIDARG; } STDMETHODIMP CDirectMusicEmulatePort::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 CDirectMusicEmulatePort::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; } #define OFFSET_DATA_READY 0 #define OFFSET_KILL_THREAD 1 DWORD CDirectMusicEmulatePort::InputWorker() { HANDLE h[2]; h[OFFSET_DATA_READY] = m_hDataReady; h[OFFSET_KILL_THREAD] = m_hKillThreads; UINT uWait; for(;;) { uWait = WaitForMultipleObjects(2, h, FALSE, INFINITE); switch(uWait) { case WAIT_OBJECT_0 + OFFSET_DATA_READY: // m_hDataReady set // InputWorkerDataReady(); if (m_hAppEvent) { try { SetEvent(m_hAppEvent); } catch (...) { Trace(0, "Capture: Application notify event handle prematurely free'd!\n"); } } break; case WAIT_OBJECT_0 + OFFSET_KILL_THREAD: // m_hKillThread set // Trace(0, "CDirectMusicEmulateWorker::InputWorker thread exit\n"); return 0; case WAIT_FAILED: Trace(0, "WaitForMultipleObjects failed %d killing thread\n", GetLastError()); return 0; default: break; } } return 0; } // CDirectMusicEmulatePort::InputWorkerDataReady() // // The input worker thread has been notified that there is data available. // Read any pending events from the 16-bit DLL, perform needed thruing, and // save the data in a queue so we can repackage it on the read request // from the client. // void CDirectMusicEmulatePort::InputWorkerDataReady() { MMRESULT mmr; DWORD cbData; DWORD msTime; LPBYTE pbData; DMEVENT *pEvent; DWORD cbRounded; REFERENCE_TIME rtStart; HRESULT hr; REFERENCE_TIME rtMasterClock; Trace(0, "Enter InputWorkerDataReady()\n"); for(;;) { // Fill temporary buffer // cbData = sizeof(m_WorkBuffer); mmr = MidiInRead(m_hDevice, m_p1616WorkBuffer, &cbData, &msTime); rtStart = ((ULONGLONG)msTime) * REFTIME_TO_MS; SyncClocks(); SlaveToMaster(&rtStart); hr = m_pMasterClock->GetTime(&rtMasterClock); if (mmr) { Trace(2, "InputWorkerDataReady: MidiInRead returned %d\n", mmr); return; } if (cbData == 0) { Trace(2, "MidiInRead returned no data\n"); return; } // Copy temporary buffer as events into queue // pbData = m_WorkBuffer; while (cbData) { pEvent = (DMEVENT*)pbData; cbRounded = DMUS_EVENT_SIZE(pEvent->cbEvent); Trace(2, "cbData %u cbRounded %u\n", cbData, cbRounded); if (cbRounded > cbData) { Trace(0, "InputWorkerDataReady: Event ran off end of buffer\n"); break; } cbData -= cbRounded; pbData += cbRounded; EnterCriticalSection(&m_csEventQueues); QUEUED_EVENT *pQueuedEvent; int cbEvent; if (pEvent->cbEvent <= sizeof(DWORD)) { // Channel message or other really small event, take from // free pool. // pQueuedEvent = m_FreeEvents.Alloc(); cbEvent = sizeof(DMEVENT); Trace(2, "Queue [%02X %02X %02X %02X]\n", pEvent->abEvent[0], pEvent->abEvent[1], pEvent->abEvent[2], pEvent->abEvent[3]); } else { // SysEx or other long event, just allocate it // cbEvent = DMUS_EVENT_SIZE(pEvent->cbEvent); pQueuedEvent = (QUEUED_EVENT*)new char[QUEUED_EVENT_SIZE(pEvent->cbEvent)]; } if (pQueuedEvent) { CopyMemory(&pQueuedEvent->e, pEvent, cbEvent); // rtDelta is the absolute time of the event while it's in our queue // pQueuedEvent->e.rtDelta += rtStart; ThruEvent(&pQueuedEvent->e); if (m_ReadEvents.pFront) { m_ReadEvents.pRear->pNext = pQueuedEvent; } else { m_ReadEvents.pFront = pQueuedEvent; } m_ReadEvents.pRear = pQueuedEvent; pQueuedEvent->pNext = NULL; } else { Trace(1, "InputWorker: Failed to allocate event; dropping\n"); } LeaveCriticalSection(&m_csEventQueues); } } Trace(2, "Leave InputWorkerDataReady()\n"); } void CDirectMusicEmulatePort::ThruEvent( DMEVENT *pEvent) { // Since we know we only have one event and we already have it in the right format, // just slam it into the thru buffer. We only have to do this because we might modify // it. // LPBYTE pbData; DWORD cbData; DWORD cbEvent = DMUS_EVENT_SIZE(pEvent->cbEvent); // First see if the event is thruable // if (pEvent->cbEvent > 3 || ((pEvent->abEvent[0] & 0xF0) == 0xF0)) { // SysEx of some description return; } // Note: legacy driver assures no running status // DWORD dwSourceChannel = (DWORD)(pEvent->abEvent[0] & 0x0F); DMUS_THRU_CHANNEL *pThru = &m_pThruMap[dwSourceChannel]; if (pThru->pDestinationPort == NULL || pThru->fThruInWin16) { return; } if (FAILED(m_pThruBuffer->GetRawBufferPtr(&pbData))) { Trace(0, "Thru: GetRawBufferPtr\n"); return; } if (FAILED(m_pThruBuffer->GetMaxBytes(&cbData))) { Trace(0, "Thru: GetMaxBytes\n"); return; } if (cbEvent > cbData) { Trace(0, "Thru: cbData %u cbEvent %u\n", cbData, cbEvent); return; } if (FAILED(m_pThruBuffer->SetStartTime(pEvent->rtDelta)) || FAILED(m_pThruBuffer->SetUsedBytes(cbEvent))) { Trace(0, "Thru: buffer setup failed\n"); } pEvent->rtDelta = 50000; CopyMemory(pbData, pEvent, cbEvent); pEvent = (DMEVENT*)pbData; pEvent->dwChannelGroup = pThru->dwDestinationChannelGroup; pEvent->abEvent[0] = (BYTE)((pEvent->abEvent[0] & 0xF0) | pThru->dwDestinationChannel); pThru->pDestinationPort->PlayBuffer(m_pThruBuffer); } void CDirectMusicEmulatePort::MasterToSlave( REFERENCE_TIME *prt) { if (m_fSyncToMaster) { *prt -= m_lTimeOffset; } } void CDirectMusicEmulatePort::SlaveToMaster( REFERENCE_TIME *prt) { if (m_fSyncToMaster) { *prt += m_lTimeOffset; } } void CDirectMusicEmulatePort::SyncClocks() { HRESULT hr; REFERENCE_TIME rtMasterClock; REFERENCE_TIME rtSlaveClock; LONGLONG drift; if (m_fSyncToMaster) { hr = m_pMasterClock->GetTime(&rtMasterClock); rtSlaveClock = ((ULONGLONG)timeGetTime()) * MS_TO_REFERENCE_TIME; if (FAILED(hr)) { return; } drift = (rtSlaveClock + m_lTimeOffset) - rtMasterClock; // Work-around 46782 for DX8 release: // If drift is greater than 10ms, jump to the new offset value instead // of drifting there slowly. if( drift > 10000 * 10 || drift < 10000 * -10 ) { m_lTimeOffset -= drift; } else { m_lTimeOffset -= drift / 100; } } } ///////////////////////////////////////////////////////////////////// // // CEmulateLatencyClock // // Latency clock for emulated ports, which is just a fixed offset from // the DirectMusic master clock // CEmulateLatencyClock::CEmulateLatencyClock(IReferenceClock *pMasterClock) : m_cRef(1), m_pMasterClock(pMasterClock) { pMasterClock->AddRef(); } CEmulateLatencyClock::~CEmulateLatencyClock() { Close(); } STDMETHODIMP CEmulateLatencyClock::QueryInterface( const IID &iid, void **ppv) { if (iid == IID_IUnknown || iid == IID_IReferenceClock) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CEmulateLatencyClock::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CEmulateLatencyClock::Release() { if (!InterlockedDecrement(&m_cRef)) { delete this; return 0; } return m_cRef; } STDMETHODIMP CEmulateLatencyClock::GetTime( REFERENCE_TIME *pTime) { REFERENCE_TIME rt; V_INAME(IReferenceClock::GetTime); V_PTR_WRITE(pTime, REFERENCE_TIME); if (!m_pMasterClock) { return DMUS_E_DMUSIC_RELEASED; } HRESULT hr = m_pMasterClock->GetTime(&rt); rt += FIXED_LEGACY_LATENCY_OFFSET; // Default : 10 ms *pTime = rt; return hr; } STDMETHODIMP CEmulateLatencyClock::AdviseTime( REFERENCE_TIME baseTime, REFERENCE_TIME streamTime, HANDLE hEvent, DWORD * pdwAdviseCookie) { return DMUS_E_UNKNOWN_PROPERTY; } STDMETHODIMP CEmulateLatencyClock::AdvisePeriodic( REFERENCE_TIME startTime, REFERENCE_TIME periodTime, HANDLE hSemaphore, DWORD * pdwAdviseCookie) { return DMUS_E_UNKNOWN_PROPERTY; } STDMETHODIMP CEmulateLatencyClock::Unadvise( DWORD dwAdviseCookie) { return DMUS_E_UNKNOWN_PROPERTY; } void CEmulateLatencyClock::Close() { if (m_pMasterClock) { m_pMasterClock->Release(); m_pMasterClock = NULL; } } static HRESULT MMRESULTToHRESULT( MMRESULT mmr) { switch (mmr) { case MMSYSERR_NOERROR: return S_OK; case MMSYSERR_ALLOCATED: return DMUS_E_DEVICE_IN_USE; case MIDIERR_BADOPENMODE: return DMUS_E_ALREADYOPEN; case MMSYSERR_NOMEM: return E_OUTOFMEMORY; } return E_FAIL; }