windows-nt/Source/XPSP1/NT/multimedia/directx/dmusic/dmusic32/dmeport.cpp

1869 lines
44 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
// Copyright (c) 1998-1999 Microsoft Corporation
// dmeport.cpp
//
// CDirectMusicEmulatePort
// Implements the MMSYSTEM API version of IDirectMusicPort.
//
#define INITGUID
#include <objbase.h>
#include <ks.h>
#include <ksproxy.h>
#include <assert.h>
#include <mmsystem.h>
#include <dsoundp.h>
#include "dmusicc.h"
#include "..\dmusic\dmusicp.h"
#include "debug.h"
#include "dmusic32.h"
#include "dm32p.h"
#include "dmthunk.h"
#include "..\shared\validate.h"
#include <ks.h> // 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<IDirectMusicPort*>(this);
}
else if (iid == IID_IDirectMusicPortP)
{
*ppv = static_cast<IDirectMusicPortP*>(this);
}
else if (iid == IID_IDirectMusicPortPrivate)
{
*ppv = static_cast<IDirectMusicPortPrivate*>(this);
}
else if (iid == IID_IKsControl)
{
*ppv = static_cast<IKsControl*>(this);
}
else if (iid == IID_IDirectMusicThru)
{
*ppv = static_cast<IDirectMusicThru*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*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<IDirectMusicPort*>(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<CDirectMusicBuffer *>(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<IReferenceClock*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*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;
}