You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1868 lines
46 KiB
1868 lines
46 KiB
// 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;
|
|
}
|