//
// Copyright (c) 1996-2001 Microsoft Corporation
// DSLink.cpp
//
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX
//
// We disable this because we use exceptions and do *not* specify -GX (USE_NATIVE_EH in
// sources).
//
// The one place we use exceptions is around construction of objects that call
// InitializeCriticalSection. We guarantee that it is safe to use in this case with
// the restriction given by not using -GX (automatic objects in the call chain between
// throw and handler are not destructed). Turning on -GX buys us nothing but +10% to code
// size because of the unwind code.
//
// Any other use of exceptions must follow these restrictions or -GX must be turned on.
//
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
#pragma warning(disable:4530)

#include <objbase.h>
#include <ks.h>
#include <ksproxy.h>

#include "debug.h"
#include "dmusicc.h"
#include "dmusics.h"
#include "..\shared\validate.h"
#include "synth.h"
#include "DSLink.h"
#include "float.h"
#include "misc.h"
#include "dmksctrl.h"

#define DSBUFFER_LENGTH_SEC 2

extern long g_cComponent;
CDSLinkList g_DSLinkList;       // Master list of DSLinks.


void CDSLink::SynthProc()
{
    HRESULT hr;
    DWORD dwPlayCursor;         // current play head (driven by streaming wave crystal)
    DWORD dwWriteFromCursor;    // current write head

    ::EnterCriticalSection(&m_CriticalSection);

    if (!m_fActive || !m_pBuffer || !m_pIMasterClock)
    {
        Trace(2, "Warning: SynthSink - Thread in invalid state\n");
        ::LeaveCriticalSection(&m_CriticalSection);
        return;
    }

    hr = m_pBuffer->GetCurrentPosition(&dwPlayCursor, &dwWriteFromCursor);
    if (hr == DS_OK)
    {
        DWORD dwDeltaFilter = m_dwBufferSize >> 1;
        DWORD dwCursorDelta;

        if (dwWriteFromCursor >= dwPlayCursor)
            dwCursorDelta = dwWriteFromCursor - dwPlayCursor;
        else
            dwCursorDelta = (dwWriteFromCursor + m_dwBufferSize) - dwPlayCursor;

        if (dwCursorDelta > m_dwWriteFromMax)
        {
            if (dwCursorDelta < dwDeltaFilter)
            {
                TraceI(2, "Warning: SynthSink - Play to Write cursor distance increased from %lu to %lu\n", m_dwWriteFromMax, dwCursorDelta);
                m_dwWriteFromMax = dwCursorDelta;
            }
            else
            {
                TraceI(2, "Warning: SynthSink - Play to Write cursor delta value rejected:%lu\n", dwCursorDelta);
                SetEvent(g_DSLinkList.m_hEvent);
                ::LeaveCriticalSection(&m_CriticalSection);
                return;
            }
        }
        else
        {
            m_dwWriteFromMax -= ((m_dwWriteFromMax - dwCursorDelta) / 100);
            m_dwWriteFromMax = SampleAlign(m_dwWriteFromMax);
            dwCursorDelta = m_dwWriteFromMax;
        }

        dwWriteFromCursor = (dwPlayCursor + dwCursorDelta) % m_dwBufferSize;

        if (m_llAbsWrite == 0)
        {
            // we just started
            m_dwLastPlay = dwPlayCursor;
            m_dwLastWrite = dwWriteFromCursor;
            m_llAbsWrite = dwCursorDelta;
            m_SampleClock.Start(m_pIMasterClock, m_wfSynth.nSamplesPerSec, 0);
            m_Clock.Start(); // don't want anybody getting latency time until this thread is running
        }

        // check for overrun with master clock
        REFERENCE_TIME rtMaster;
        LONGLONG llMasterSampleTime;
        LONGLONG llMasterBytes;
        LONGLONG llMasterAhead;    // how far master clock is ahead of last known play time
        LONGLONG llAbsWriteFrom;

        m_pIMasterClock->GetTime(&rtMaster);
        RefTimeToSample(rtMaster, &llMasterSampleTime);
        llMasterBytes = SampleToByte(llMasterSampleTime);
        llMasterAhead = (llMasterBytes > m_llAbsPlay) ? llMasterBytes - m_llAbsPlay : 0;

        // check for half-buffer underruns, so backward-moving play cursors can be detected
        if (llMasterAhead > dwDeltaFilter)
        {
            Trace(2, "Warning: SynthSink - Buffer underrun by %lu\n", (long) llMasterAhead - dwDeltaFilter);

            m_llAbsPlay = llMasterBytes;
            m_dwLastWrite = dwWriteFromCursor;
            m_llAbsWrite = llAbsWriteFrom = m_llAbsPlay + dwCursorDelta;
        }
        else
        {
            DWORD dwPlayed;

            if (dwPlayCursor >= m_dwLastPlay)
                dwPlayed = dwPlayCursor - m_dwLastPlay;
            else
                dwPlayed = (dwPlayCursor + m_dwBufferSize) - m_dwLastPlay;

            if (dwPlayed > dwDeltaFilter)
            {
                Trace(2, "Warning: SynthSink - Play Cursor %lu looks invalid, rejecting it.\n", dwPlayed);
                SetEvent(g_DSLinkList.m_hEvent);
                ::LeaveCriticalSection(&m_CriticalSection);
                return;
            }

            m_llAbsPlay += dwPlayed;
            llAbsWriteFrom = m_llAbsPlay + dwCursorDelta;

            // how far ahead of the write head are we?
            if (llAbsWriteFrom > m_llAbsWrite)
            {
                DWORD dwWriteMissed;

                // we are behind-- let's catch up
                dwWriteMissed = DWORD(llAbsWriteFrom - m_llAbsWrite);

                Trace(2, "Warning: SynthSink - Write underrun, missed %lu bytes\n", dwWriteMissed);

                m_dwLastWrite = dwWriteFromCursor;
                m_llAbsWrite += dwWriteMissed;
            }
        }

        m_dwLastPlay = dwPlayCursor;
        m_SampleClock.SyncToMaster(ByteToSample(m_llAbsPlay), m_pIMasterClock);

        // how much to write?
        LONGLONG llAbsWriteTo;
        DWORD dwBytesToFill;

        llAbsWriteTo = llAbsWriteFrom + m_dwWriteTo;

        if (llAbsWriteTo > m_llAbsWrite)
        {
            dwBytesToFill = DWORD(llAbsWriteTo - m_llAbsWrite);
        }
        else
        {
            dwBytesToFill = 0;
        }

        if (dwBytesToFill)
        {
            LPVOID lpStart, lpEnd;      // Buffer pointers, filled by Lock command.
            DWORD dwStart, dwEnd;       // For Lock.

            hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
            if (hr == DSERR_BUFFERLOST)
            {
                Trace(2, "Warning: SynthSink - Buffer lost\n");
                hr = m_pBuffer->Restore();
                if (hr == DS_OK)
                {
                    Trace(2, "Warning: SynthSink - Buffer restored\n");
                    hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
                    if (hr == DS_OK)
                    {
                        Trace(2, "Warning: SynthSink - Play restarted\n");
                        hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
                    }
                }
            }
            if (hr == DS_OK)
            {
                if (dwStart)
                {
                    memset(lpStart, 0, dwStart);
                    if (m_pSynth)
                    {
                        m_pSynth->Render((short*)lpStart, ByteToSample(dwStart), ByteToSample(m_llAbsWrite));
                    }

                    m_dwLastWrite += dwStart;
                    m_llAbsWrite += dwStart;

                    if (m_dwLastWrite == m_dwBufferSize)
                    {
                        m_dwLastWrite = 0;
                    }
                }
                if (dwEnd)
                {
                    memset(lpEnd, 0, dwEnd);
                    if (m_pSynth)
                    {
                        m_pSynth->Render((short*)lpEnd, ByteToSample(dwEnd), ByteToSample(m_llAbsWrite));
                    }
                    m_dwLastWrite = dwEnd;
                    m_llAbsWrite += dwEnd;
                }
                m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);

                // write silence into unplayed buffer
                if (m_dwLastWrite >= dwPlayCursor)
                    dwBytesToFill = m_dwBufferSize - m_dwLastWrite + dwPlayCursor;
                else
                    dwBytesToFill = dwPlayCursor - m_dwLastWrite;

                hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
                if (hr == DSERR_BUFFERLOST)
                {
                    Trace(2, "Warning: SynthSink - Buffer lost\n");
                    hr = m_pBuffer->Restore();
                    if (hr == DS_OK)
                    {
                        Trace(2, "Warning: SynthSink - Buffer restored\n");
                        hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
                        if (hr == DS_OK)
                        {
                            Trace(2, "Warning: SynthSink - Play restarted\n");
                            hr = m_pBuffer->Lock(m_dwLastWrite, dwBytesToFill, &lpStart, &dwStart, &lpEnd, &dwEnd, 0);
                        }
                    }
                }
                if (hr == DS_OK)
                {
                    if (dwStart)
                    {
                        memset(lpStart, 0, dwStart);
                    }
                    if (dwEnd)
                    {
                        memset(lpEnd, 0, dwEnd);
                    }
                    m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);
                }
                else
                {
                    Trace(2, "Warning: SynthSink - Failed to lock DS buffer: %x\n", hr);
                }
            }
            else
            {
                Trace(2, "Warning: SynthSink - Failed to lock DS buffer: %x\n", hr);
            }
        }
    }
    else
    {
        if (hr == DSERR_BUFFERLOST)
        {
            Trace(2, "Warning: SynthSink - Buffer lost on GetCurrentPosition\n");
            hr = m_pBuffer->Restore();
            if (hr == DS_OK)
            {
                Trace(2, "Warning: SynthSink - Buffer restored\n");
                hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
                if (hr == DS_OK)
                {
                    Trace(2, "Warning: SynthSink - Play restarted\n");
                }
            }
        }
        else
        {
            Trace(0, "Error: SynthSink - Failed to get DS buffer position, error code: %lx\n", hr);
        }
    }

    ::LeaveCriticalSection(&m_CriticalSection);
}


void CDSLinkList::SynthProc()
{
    for (;;)
    {
        if (m_fPleaseDie)
        {
            m_fPleaseDie = FALSE;
            break;
        }

        for (DWORD dwX = 0; dwX < m_dwCount; dwX++)
        {
            ::EnterCriticalSection(&m_CriticalSection);
            CDSLink *pLink = GetItem(dwX);
            ::LeaveCriticalSection(&m_CriticalSection);
            if (pLink)
            {
                if (pLink->m_fActive)
                {
                    pLink->SynthProc();
                }
            }
        }
        if (m_dwResolution < 2) m_dwResolution = 2;
        if (m_dwResolution > 100) m_dwResolution = 100;
        WaitForSingleObject(m_hEvent, m_dwResolution);
    }
}


static DWORD WINAPI SynthThread (LPVOID lpThreadParameter)
{
    CDSLinkList *pLinkList = (CDSLinkList *) lpThreadParameter;
    pLinkList->SynthProc();
    return 0;
}

HRESULT CDSLink::Connect()
{
    if (!m_pSynth)
    {
        Trace(0, "Error: SynthSink - Activation failed, SynthSink not initialized\n");
        return DMUS_E_SYNTHNOTCONFIGURED;
    }

    if (!m_pDSound)
    {
        Trace(0, "Error: SynthSink - Activation failed, IDirectSound not set\n");
        return DMUS_E_DSOUND_NOT_SET;
    }

    if (!IsValidFormat(&m_wfSynth))
    {
        Trace(0, "Error: SynthSink - Activation failed, format not initialized/valid\n");
        return DMUS_E_SYNTHNOTCONFIGURED;
    }

    if (!m_pIMasterClock)
    {
        Trace(0, "Error: SynthSink - Activation failed, master clock not set\n");
        return DMUS_E_NO_MASTER_CLOCK;
    }

    if (m_fActive)
    {
        Trace(0, "Error: SynthSink - Activation failed, already active\n");
        return DMUS_E_SYNTHACTIVE;
    }

    assert(!m_pBuffer);

    HRESULT hr = E_FAIL;

    ::EnterCriticalSection(&m_CriticalSection);
    if (!m_pExtBuffer)
    {
        DSBUFFERDESC dsbdesc;
        memset(&dsbdesc, 0, sizeof(dsbdesc));
        dsbdesc.dwSize = sizeof(dsbdesc);
        dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;

        // create primary buffer
        if (SUCCEEDED(m_pDSound->CreateSoundBuffer(&dsbdesc, &m_pPrimary, NULL)))
        {
            WAVEFORMATEX wfPrimary;
            memset(&wfPrimary, 0, sizeof(wfPrimary));

            if (SUCCEEDED(m_pPrimary->GetFormat(&wfPrimary, sizeof(wfPrimary), NULL)))
            {
                assert(wfPrimary.wFormatTag == WAVE_FORMAT_PCM);

                BOOL fUpgrade = FALSE;
                if (wfPrimary.nChannels < m_wfSynth.nChannels)
                {
                    wfPrimary.nChannels = m_wfSynth.nChannels;
                    fUpgrade = TRUE;
                }
                if (wfPrimary.nSamplesPerSec < m_wfSynth.nSamplesPerSec)
                {
                    wfPrimary.nSamplesPerSec = m_wfSynth.nSamplesPerSec;
                    fUpgrade = TRUE;
                }
                if (wfPrimary.wBitsPerSample < m_wfSynth.wBitsPerSample)
                {
                    wfPrimary.wBitsPerSample = m_wfSynth.wBitsPerSample;
                    fUpgrade = TRUE;
                }

                if (fUpgrade)
                {
                    wfPrimary.nBlockAlign = wfPrimary.nChannels * (wfPrimary.wBitsPerSample / 8);
                    wfPrimary.nAvgBytesPerSec = wfPrimary.nSamplesPerSec * wfPrimary.nBlockAlign;

                    // the existing format is of lesser quality than we desire, so let's upgrade it
                    if (FAILED(hr = m_pPrimary->SetFormat( &wfPrimary )))
                    {
                        if (hr == DSERR_PRIOLEVELNEEDED)
                        {
                            // okay, so maybe the app doen't want us changing primary buffer
                            Trace(2, "Error: SynthSink - SetFormat on primary buffer failed, lacking priority\n");
                            hr = S_OK;
                        }
                        else
                        {
                            Trace(0, "Error: SynthSink - Activation failed, couldn't set primary buffer format\n");

                            m_pPrimary->Release();
                            m_pPrimary = NULL;
                            m_pBuffer = NULL;

                            hr = E_UNEXPECTED;
                        }
                    }
                }
                else
                {
                    hr = S_OK;
                }

                if (SUCCEEDED(hr))
                {
                    hr = E_FAIL;

                    memset(&dsbdesc, 0, sizeof(dsbdesc));
                    dsbdesc.dwSize = sizeof(dsbdesc);
                    // need default controls (pan, volume, frequency).
                    dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
                    // N-second buffer.
                    dsbdesc.dwBufferBytes = DSBUFFER_LENGTH_SEC * m_wfSynth.nAvgBytesPerSec;
                    dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&m_wfSynth;

                    if (SUCCEEDED(m_pDSound->CreateSoundBuffer(&dsbdesc, &m_pBuffer, NULL)))
                    {
                        hr = S_OK;
                    }
                    else
                    {
                        m_pBuffer = NULL;

                        if (m_pPrimary)
                        {
                            m_pPrimary->Release(); m_pPrimary = NULL;
                        }

                        Trace(0, "Error: SynthSink - Activation failed, couldn't create secondary buffer\n");
                        hr = E_UNEXPECTED;
                    }
                }
            }
            else
            {
                Trace(0, "Error: SynthSink - Activation failed, couldn't get primary buffer format\n");
                hr = E_UNEXPECTED;
            }
        }
        else
        {
            Trace(0, "Error: SynthSink - Activation failed, couldn't create primary buffer\n");
            hr = E_UNEXPECTED;
        }
    }
    else
    {
        m_pBuffer = m_pExtBuffer;
        m_pBuffer->AddRef();
    }

    if (m_pBuffer)
    {
        DSBCAPS dsbcaps;
        memset(&dsbcaps, 0, sizeof(dsbcaps));
        dsbcaps.dwSize = sizeof(dsbcaps);
        if (SUCCEEDED(m_pBuffer->GetCaps(&dsbcaps)))
        {
            DSCAPS  dsCaps ;
            memset( &dsCaps, 0, sizeof(DSCAPS) );
            dsCaps.dwSize = sizeof(DSCAPS);

            if (SUCCEEDED(m_pDSound->GetCaps(&dsCaps)))
            {
                DWORD dwMinLatency; // ms

                // Check for Dsound on top of Wave...
                if (dsCaps.dwFlags & DSCAPS_EMULDRIVER)
                {
                    dwMinLatency = 240;
                }
                else
                {
                    dwMinLatency = 80;
                }
                DWORD dwGetLatency = dwMinLatency;
                if (GetRegValueDword(TEXT("Software\\Microsoft\\DirectMusic"),
                                           TEXT("DSLMinLatency"),
                                           &dwGetLatency))
                {
                    Trace(4, "SynthSink: Registry set to change latency to %ld\n", dwGetLatency);
                    dwMinLatency = dwGetLatency;
                }
                m_dwWriteTo = SampleAlign((500 + (m_wfSynth.nAvgBytesPerSec * dwMinLatency)) / 1000);
                Trace(4, "SynthSink: Set Latency to %lu\n", dwMinLatency);

                m_dwBufferSize = dsbcaps.dwBufferBytes;

                m_dwLastWrite = 0;
                m_dwLastPlay = 0;
                m_llAbsPlay = 0;

                // fill initial buffer with silence
                LPVOID lpStart, lpEnd;
                DWORD dwStart, dwEnd;
                if (SUCCEEDED(m_pBuffer->Lock(0, m_dwBufferSize, &lpStart, &dwStart, &lpEnd, &dwEnd, 0)))
                {
                    if (dwStart)
                    {
                        memset(lpStart, 0, dwStart);
                    }
                    if (dwEnd)
                    {
                        memset(lpEnd, 0, dwEnd);
                    }
                    m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);

                    if (SUCCEEDED(m_pBuffer->Play(0, 0, DSBPLAY_LOOPING)))
                    {
                        g_DSLinkList.ActivateLink(this);
                        hr = S_OK;
                    }
                    else
                    {
                        Trace(0, "Error: SynthSink - Activation failed, couldn't start buffer\n");
                        hr = E_UNEXPECTED;
                    }
                }
                else
                {
                    Trace(0, "Error: SynthSink - Activation failed, couldn't lock buffer\n");
                    hr = E_UNEXPECTED;
                }
            }
            else
            {
                Trace(0, "Error: SynthSink - Activation failed, couldn't get DS caps\n");
                hr = E_UNEXPECTED;
            }
        }
        else
        {
            Trace(0, "Error: SynthSink - Activation failed, couldn't get buffer caps\n");
            hr = E_UNEXPECTED;
        }
    }

    if (FAILED(hr))
    {
        // Clean up
        //

        if (m_pBuffer)
        {
            m_pBuffer->Stop();
            m_pBuffer->Release();
            m_pBuffer = NULL;
        }

        if (m_pPrimary)
        {
            m_pPrimary->Release();
            m_pPrimary = NULL;
        }

        m_Clock.Stop();

        Clear();
    }
    ::LeaveCriticalSection(&m_CriticalSection);

    if (SUCCEEDED(hr))
    {
        // wait until the pump is primed
        for (WORD wRetry = 0; wRetry < 10 && !m_llAbsWrite; wRetry++)
        {
            Sleep(10);
        }

        if (m_llAbsWrite)
        {
            Trace(3, "Warning: SynthSink - Pump is primed\n");
        }
        else
        {
            Trace(0, "Error: SynthSink - Pump is NOT primed\n");
        }
    }

    return hr;
}

HRESULT CDSLink::Disconnect()
{
    // stop the buffer right away!
    ::EnterCriticalSection(&m_CriticalSection);
    if (m_pBuffer)
    {
        // write silence to prevent DSound blip bug if reactivated
        LPVOID lpStart, lpEnd;
        DWORD dwStart, dwEnd;
        if (SUCCEEDED(m_pBuffer->Lock(0, m_dwBufferSize, &lpStart, &dwStart, &lpEnd, &dwEnd, 0))) // REVIEW: don't need full buffer size
        {
            if (dwStart)
            {
                memset(lpStart, 0, dwStart);
            }
            if (dwEnd)
            {
                memset(lpEnd, 0, dwEnd);
            }
            m_pBuffer->Unlock(lpStart, dwStart, lpEnd, dwEnd);
            Sleep(50); // found experimentally
        }

        m_pBuffer->Stop();
    }
    m_Clock.Stop();
    ::LeaveCriticalSection(&m_CriticalSection);

    g_DSLinkList.DeactivateLink(this);

    ::EnterCriticalSection(&m_CriticalSection);

    if (m_pBuffer)
    {
        m_pBuffer->Release(); m_pBuffer = NULL;
    }

    if (m_pPrimary)
    {
        m_pPrimary->Release(); m_pPrimary = NULL;
    }

    Clear();

    ::LeaveCriticalSection(&m_CriticalSection);

    return S_OK;
}

void CDSLink::Clear()
{
    m_llAbsPlay = 0;        // Absolute point where play head is.
    m_dwLastPlay = 0;       // Last point where play head was.
    m_llAbsWrite = 0;    // Absolute point we've written up to.
    m_dwBufferSize = 0;     // Size of buffer.
    m_dwLastWrite = 0;   // Last position we wrote to in buffer.
    m_dwWriteTo = 1000;     // Distance between write head and where we are writing.
}

CDSLink::CDSLink()
{
    InterlockedIncrement(&g_cComponent);

    m_fCSInitialized = FALSE;
    ::InitializeCriticalSection(&m_CriticalSection);
    m_fCSInitialized = TRUE;

    memset(&m_wfSynth, 0, sizeof(m_wfSynth));
    m_pIMasterClock = NULL;
    m_cRef = 0;
    m_pSynth = NULL;      // Reference back to parent Synth.
    m_pDSound = NULL;
    m_pPrimary = NULL;
    m_pBuffer = NULL;
    m_pExtBuffer = NULL;
    m_dwWriteFromMax = 0;
    Clear();
    m_Clock.Stop();
    m_fActive = FALSE;
}

CDSLink::~CDSLink()
{
    if (m_fCSInitialized)
    {
        ::EnterCriticalSection(&m_CriticalSection);
        if (m_pIMasterClock)
        {
            m_pIMasterClock->Release(); m_pIMasterClock = NULL;
        }
        ::LeaveCriticalSection(&m_CriticalSection);

        Disconnect();

        if (m_pExtBuffer)
        {
            m_pExtBuffer->Release(); m_pExtBuffer = NULL;
        }

        if (m_pDSound)
        {
            m_pDSound->Release(); m_pDSound = NULL;
        }

        ::DeleteCriticalSection(&m_CriticalSection);
    }

    InterlockedDecrement(&g_cComponent);
}

CDSLinkList::CDSLinkList()
{
    m_fOpened = FALSE;
    m_fPleaseDie = FALSE;
    m_hThread = NULL;           // Handle for synth thread.
    m_dwThread = 0;             // ID for thread.
    m_hEvent = NULL;            // Used to signal thread.
    m_dwCount = 0;
    m_dwResolution = 20;
}

BOOL CDSLinkList::OpenUp()
{
    if (m_fOpened)
    {
        Trace(1, "Warning: SynthSink - Already opened\n");
        return TRUE;
    }

    m_fOpened = TRUE;

    if (!GetRegValueDword(TEXT("Software\\Microsoft\\DirectMusic"),
                          TEXT("DSLResolution"),
                          &m_dwResolution))
    {
        m_dwResolution = 20;
    }

    try
    {
        ::InitializeCriticalSection(&m_CriticalSection);
    }
    catch( ... )
    {
        m_fOpened = FALSE;
        return FALSE;
    }

    return TRUE;
}

void CDSLinkList::CloseDown()
{
    if (m_dwCount)
    {
        CDSLink *pLink;
        if (pLink = GetHead())
        {
            Trace(0, "Error: SynthSink - Process Detach with port still active. May crash on exit.\n");
        }
    }

    if (!m_fOpened)
    {
        Trace(2, "Warning: SynthSink - Process Detach, ports all deactivated\n");
    }
    else
    {
        m_fOpened = FALSE;
        ::DeleteCriticalSection(&m_CriticalSection);
    }
}

void CDSLinkList::ActivateLink(CDSLink *pLink)
{
    ::EnterCriticalSection(&m_CriticalSection);

    if (!pLink->m_fActive)
    {
        if (m_dwCount == 0)
        {
            m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
            m_hThread = CreateThread(NULL, 0, SynthThread, this, 0, &m_dwThread);

            if (m_hThread)
            {
                if (!SetThreadPriority(m_hThread, THREAD_PRIORITY_TIME_CRITICAL))
                {
                    Trace(0, "Error: SynthSink - Activate couldn't set thread priority\n");
                }
            }
            else
            {
                Trace(0, "Error: SynthSink - Activate couldn't create thread\n");
            }
        }

        if (!IsMember(pLink))
        {
            m_dwCount++;
            AddTail(pLink);
        }

        pLink->m_fActive = TRUE;
    }

    ::LeaveCriticalSection(&m_CriticalSection);
}

void CDSLinkList::DeactivateLink(CDSLink *pLink)
{
    ::EnterCriticalSection(&m_CriticalSection);

    if (pLink->m_fActive)
    {
        if (m_dwCount)
        {
            Remove(pLink);
            m_dwCount--;
        }

        pLink->m_fActive = FALSE;

        if (m_dwCount == 0)
        {
            if (m_hThread && m_hEvent)
            {
                m_fPleaseDie = TRUE;
                SetEvent(m_hEvent);
                if (WaitForSingleObject(m_hThread, 10000) == WAIT_TIMEOUT)
                {
                    Trace(0, "Error: SynthSink - Deactivate, thread did not exit\n");
                }
            }
            if (m_hEvent)
            {
                CloseHandle(m_hEvent);
                m_hEvent = NULL;
            }
            if(m_hThread)
            {
                CloseHandle(m_hThread);
                m_hThread = NULL;
            }
        }
    }

    ::LeaveCriticalSection(&m_CriticalSection);
}

CDSLink * CDSLink::GetNext()
{
    return (CDSLink *) CListItem::GetNext();
}

void CDSLinkList::AddTail(CDSLink *pNode)
{
    CList::AddTail((CListItem *) pNode);
}

void CDSLinkList::Remove(CDSLink *pNode)
{
    CList::Remove((CListItem *) pNode);
}

CDSLink * CDSLinkList::GetHead()
{
    return (CDSLink *)CList::GetHead();
}

CDSLink * CDSLinkList::RemoveHead()
{
    return (CDSLink *)CList::RemoveHead();
}

CDSLink * CDSLinkList::GetItem(LONG index)
{
    return (CDSLink *)CList::GetItem(index);
}

STDMETHODIMP CDSLink::QueryInterface(const IID &iid, void **ppv)
{
    V_INAME(IDirectMusicSynthSink::QueryInterface);
    V_REFGUID(iid);
    V_PTRPTR_WRITE(ppv);


    if (iid == IID_IUnknown || iid == IID_IDirectMusicSynthSink) {
        *ppv = static_cast<IDirectMusicSynthSink*>(this);
    }
    else if (iid == IID_IKsControl)
    {
        *ppv = static_cast<IKsControl*>(this);
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    reinterpret_cast<IUnknown*>(this)->AddRef();
    return S_OK;
}


STDMETHODIMP_(ULONG) CDSLink::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CDSLink::Release()
{
    if (!InterlockedDecrement(&m_cRef)) {
        delete this;
        return 0;
    }
    return m_cRef;
}

STDMETHODIMP CDSLink::Init(
    IDirectMusicSynth *pSynth) // <i IDirectMusicSynth> to connect to.
{
    m_pSynth = pSynth;
    m_Clock.Init(this);
    return S_OK;
}

STDMETHODIMP CDSLink::SetMasterClock(
    IReferenceClock *pClock)    // Master clock to synchronize to.

{
    V_INAME(IDirectMusicSynthSink::SetMasterClock);
    V_INTERFACE(pClock);

    if (m_pIMasterClock)
    {
        m_pIMasterClock->Release(); m_pIMasterClock = NULL;
    }
    m_pIMasterClock = pClock;
    if (pClock)
    {
        pClock->AddRef();
    }
    return S_OK;
}

STDMETHODIMP CDSLink::GetLatencyClock(
    IReferenceClock **ppClock) // Returned <i IReferenceClock> interface for latency clock.

{
    V_INAME(IDirectMusicSynthSink::GetLatencyClock);
    V_PTR_WRITE(ppClock, IReferenceClock *);
    return m_Clock.QueryInterface(IID_IReferenceClock, (void **)ppClock);
}

STDMETHODIMP CDSLink::Activate(
    BOOL fEnable)   // Whether to activate or deactivate audio.

{
    if (fEnable)
    {
        return Connect();
    }
    return Disconnect();
}

STDMETHODIMP CDSLink::SampleToRefTime(
    LONGLONG llSampleTime,         // Incoming time, in sample position.
    REFERENCE_TIME *prfTime)    // Outgoing time, in REFERENCE_TIME units, relative to master clock.

{
    V_INAME(IDirectMusicSynthSink::SampleToRefTime);
    V_PTR_WRITE(prfTime, REFERENCE_TIME);
    m_SampleClock.SampleToRefTime(llSampleTime, prfTime);
    return S_OK;
}

STDMETHODIMP CDSLink::RefTimeToSample(
    REFERENCE_TIME rfTime,  // Incoming time, in REFERENCE_TIME units.
    LONGLONG *pllSampleTime)   // Outgoing equivalent sample position.

{
    V_INAME(IDirectMusicSynthSink::RefTimeToSample);
    V_PTR_WRITE(pllSampleTime, LONGLONG);
    *pllSampleTime = m_SampleClock.RefTimeToSample(rfTime);
    return S_OK;
}

STDMETHODIMP CDSLink::SetDirectSound(
    LPDIRECTSOUND pDirectSound,             // IDirectSound instance (required).
    LPDIRECTSOUNDBUFFER pDirectSoundBuffer) // DirectSound buffer to render to (optional).
{
    V_INAME(IDirectMusicSynthSink::SetDirectSound);
    V_INTERFACE_OPT(pDirectSound);
    V_INTERFACE_OPT(pDirectSoundBuffer);

    if (m_fActive)
    {
        Trace(0, "Error: SynthSink - SetDirectSound failed, can't call while sink is active\n");
        return DMUS_E_SYNTHACTIVE;
    }

    HRESULT hr = E_FAIL;

    ::EnterCriticalSection(&m_CriticalSection);

    if (m_pExtBuffer)
    {
        m_pExtBuffer->Release(); m_pExtBuffer = NULL;
    }

    if (m_pDSound)
    {
        m_pDSound->Release();
    }

    m_pDSound = pDirectSound;

    if (m_pDSound)
    {
        m_pDSound->AddRef();

        if (m_pSynth)
        {
            DWORD dwWaveFormatExSize = sizeof(m_wfSynth);

            if (SUCCEEDED(m_pSynth->GetFormat(&m_wfSynth, &dwWaveFormatExSize))) // update current synth format
            {
                if (IsValidFormat(&m_wfSynth))
                {
                    m_pExtBuffer = pDirectSoundBuffer;

                    if (m_pExtBuffer)
                    {
                        m_pExtBuffer->AddRef();

                        // check format
                        WAVEFORMATEX wfExt;
                        memset(&wfExt, 0, sizeof(wfExt));

                        if (SUCCEEDED(m_pExtBuffer->GetFormat(&wfExt, sizeof(wfExt), NULL)))
                        {
                            // must exactly match synth format
                            if (wfExt.wFormatTag == m_wfSynth.wFormatTag &&
                                wfExt.nChannels == m_wfSynth.nChannels &&
                                wfExt.nSamplesPerSec == m_wfSynth.nSamplesPerSec &&
                                wfExt.nBlockAlign == m_wfSynth.nBlockAlign &&
                                wfExt.nAvgBytesPerSec == m_wfSynth.nAvgBytesPerSec &&
                                wfExt.wBitsPerSample == m_wfSynth.wBitsPerSample)
                            {
                                DSBCAPS dsbcaps;
                                dsbcaps.dwSize = sizeof(dsbcaps);

                                if (SUCCEEDED(m_pExtBuffer->GetCaps(&dsbcaps)))
                                {
                                    // check for invalid flags
                                    if (dsbcaps.dwFlags & (DSBCAPS_PRIMARYBUFFER | DSBCAPS_STATIC))
                                    {
                                        Trace(0, "Error: SynthSink - SetDirectSound failed, buffer not secondary streaming\n");
                                        hr = DMUS_E_INVALIDBUFFER;
                                    }
                                    // is buffer too small?
                                    else if (dsbcaps.dwBufferBytes < m_wfSynth.nAvgBytesPerSec)
                                    {
                                        Trace(0, "Error: SynthSink - SetDirectSound failed, buffer too small\n");
                                        hr = DMUS_E_INSUFFICIENTBUFFER;
                                    }
                                    else
                                    {
                                        hr = S_OK;
                                    }
                                }
                                else
                                {
                                    Trace(0, "Error: SynthSink - SetDirectSound failed, couldn't get buffer caps\n");
                                    hr = E_UNEXPECTED;
                                }
                            }
                            else
                            {
                                Trace(0, "Error: SynthSink - SetDirectSound failed, format doesn't match synth\n");
                                hr = DMUS_E_WAVEFORMATNOTSUPPORTED;
                            }
                        }
                        else
                        {
                            Trace(0, "Error: SynthSink - SetDirectSound failed, couldn't get buffer format\n");
                            hr = E_UNEXPECTED;
                        }
                    }
                    else
                    {
                        hr = S_OK;
                    }
                }
                else
                {
                    Trace(0, "Error: SynthSink - SetDirectSound failed, synth format not valid for this sink\n");
                    hr = E_UNEXPECTED;
                }
            }
            else
            {
                Trace(0, "Error: SynthSink - SetDirectSound failed, couldn't get synth format\n");
                hr = E_UNEXPECTED;
            }
        }
        else
        {
            Trace(0, "Error: SynthSink - SetDirectSound failed, sink not initialized\n");
            hr = DMUS_E_SYNTHNOTCONFIGURED;
        }

        if (FAILED(hr))
        {
            if (m_pExtBuffer)
            {
                m_pExtBuffer->Release(); m_pExtBuffer = NULL;
            }

            m_pDSound->Release(); m_pDSound = NULL;
        }
    }
    else
    {
        hr = S_OK;
    }

    ::LeaveCriticalSection(&m_CriticalSection);

    return hr;
}

STDMETHODIMP CDSLink::GetDesiredBufferSize(
    LPDWORD pdwBufferSizeInSamples)
{
    V_INAME(IDirectMusicSynthSink::GetDesiredBufferSize);
    V_PTR_WRITE(pdwBufferSizeInSamples, DWORD);

    if (!m_pSynth)
    {
        Trace(0, "Error: SynthSink - GetDesiredBufferSize, sink not initialized\n");
        return DMUS_E_SYNTHNOTCONFIGURED;
    }

    HRESULT hr = E_FAIL;
    WAVEFORMATEX wfx;
    DWORD dwWaveFormatExSize = sizeof(wfx);
    memset(&wfx, 0, sizeof(wfx));

    ::EnterCriticalSection(&m_CriticalSection);
    if (SUCCEEDED(m_pSynth->GetFormat(&wfx, &dwWaveFormatExSize)))
    {
        *pdwBufferSizeInSamples = DSBUFFER_LENGTH_SEC * wfx.nAvgBytesPerSec;
        hr = S_OK;
    }
    else
    {
        Trace(0, "Error: SynthSink - GetDesiredBufferSize, couldn't get synth format\n");
        hr = E_UNEXPECTED;
    }
    ::LeaveCriticalSection(&m_CriticalSection);

    return hr;
}


CClock::CClock()

{
    m_pDSLink = NULL;
    m_fStopped = TRUE;
}

void CClock::Init(CDSLink *pDSLink)

{
    m_pDSLink = pDSLink;
}

HRESULT CClock::QueryInterface( REFIID riid, LPVOID FAR* ppvObj )
{
    V_INAME(IReferenceClock::QueryInterface);
    V_REFGUID(riid);
    V_PTRPTR_WRITE(ppvObj);

    if( ::IsEqualIID( riid, IID_IReferenceClock ) ||
        ::IsEqualIID( riid, IID_IUnknown ) )
    {
        AddRef();
        *ppvObj = this;
        return S_OK;
    }
    *ppvObj = NULL;
    return E_NOINTERFACE;
}

ULONG CClock::AddRef()
{
    if (m_pDSLink)
    {
        return m_pDSLink->AddRef();
    }
    else return 0;
}

ULONG CClock::Release()
{
    if (m_pDSLink)
    {
        return m_pDSLink->Release();
    }
    else return 0;
}

HRESULT STDMETHODCALLTYPE CClock::AdviseTime( REFERENCE_TIME /*baseTime*/,
                                                REFERENCE_TIME /*streamTime*/,
                                                HANDLE /*hEvent*/,
                                                DWORD __RPC_FAR* /*pdwAdviseCookie*/)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CClock::AdvisePeriodic( REFERENCE_TIME /*startTime*/,
                                                    REFERENCE_TIME /*periodTime*/,
                                                    HANDLE /*hSemaphore*/,
                                                    DWORD __RPC_FAR* /*pdwAdviseCookie*/)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CClock::Unadvise( DWORD /*dwAdviseCookie*/ )
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CClock::GetTime(
    REFERENCE_TIME __RPC_FAR* pTime )   // <t ReferenceTime> structure to hold returned time.
{
    HRESULT hr = E_FAIL;
    if( pTime == NULL )
    {
        return E_INVALIDARG;
    }

    if (m_pDSLink != NULL)
    {
        if (m_pDSLink->m_fActive && !m_fStopped)
        {
            REFERENCE_TIME rtCompare;
            if (m_pDSLink->m_pIMasterClock)
            {
                m_pDSLink->m_pIMasterClock->GetTime(&rtCompare);

                ::EnterCriticalSection(&m_pDSLink->m_CriticalSection); // make sure SynthProc is not about to update
                hr = m_pDSLink->SampleToRefTime(m_pDSLink->ByteToSample(m_pDSLink->m_llAbsWrite), pTime);
                ::LeaveCriticalSection(&m_pDSLink->m_CriticalSection);
                if (FAILED(hr))
                {
                    Trace(1, "Error: SynthSink Latency Clock: SampleToRefTime failed\n");
                    return hr;
                }

                if (*pTime < rtCompare)
                {
                    Trace(3, "Warning: SynthSink Latency Clock off. Latency time is %ldms, Master time is %ldms\n",
                        (long) (*pTime / 10000), (long) (rtCompare / 10000));
                    *pTime = rtCompare;
                }
                else if (*pTime > (rtCompare + (10000 * 1000)))
                {
                    Trace(3, "Warning: SynthSink Latency Clock off. Latency time is %ldms, Master time is %ldms\n",
                        (long) (*pTime / 10000), (long) (rtCompare / 10000));
                    *pTime = rtCompare + (10000 * 1000);
                }

                hr = S_OK;
            }
            else
            {
                Trace(2, "Warning: SynthSink Latency Clock - GetTime called with no master clock\n");
            }
        }
        else
        {
            Trace(2, "Warning: SynthSink Latency Clock - GetTime called with synth sink not active\n");
        }
    }
    return hr;
}

void CClock::Stop()

{
    m_fStopped = TRUE;
}

void CClock::Start()

{
    m_fStopped = FALSE;
}

static DWORD g_dwPropFalse = FALSE;
static DWORD g_dwPropTrue = TRUE;

SINKPROPERTY CDSLink::m_aProperty[] =
{
    {
        &GUID_DMUS_PROP_SynthSink_DSOUND,
        0,
        KSPROPERTY_SUPPORT_GET,
        SINKPROP_F_STATIC,
        &g_dwPropTrue,
        sizeof(g_dwPropTrue),
        NULL
    },
    {
        &GUID_DMUS_PROP_SynthSink_WAVE,
        0,
        KSPROPERTY_SUPPORT_GET,
        SINKPROP_F_STATIC,
        &g_dwPropFalse,
        sizeof(g_dwPropFalse),
        NULL
    },
    {
        &GUID_DMUS_PROP_WriteLatency,
        0,
        KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET,
        SINKPROP_F_FNHANDLER,
        NULL,
        0,
        HandleLatency
    },
    {
        &GUID_DMUS_PROP_WritePeriod,
        0,
        KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET,
        SINKPROP_F_STATIC,
        &g_DSLinkList.m_dwResolution,
        sizeof(g_DSLinkList.m_dwResolution),
        NULL
    },
    {
        &GUID_DMUS_PROP_SinkUsesDSound,
        0,
        KSPROPERTY_SUPPORT_GET,
        SINKPROP_F_STATIC,
        &g_dwPropTrue,
        sizeof(g_dwPropTrue),
        NULL
    }
};

HRESULT CDSLink::HandleLatency(ULONG ulId, BOOL fSet, LPVOID pbBuffer, PULONG pcbBuffer)

{
    DWORD dwLatency;
    if (*pcbBuffer != sizeof(dwLatency))
    {
        return E_INVALIDARG;
    }
    if (!m_pSynth || !IsValidFormat(&m_wfSynth))
    {
        return DMUS_E_SYNTHNOTCONFIGURED;
    }
    if (fSet)
    {
        dwLatency = *(DWORD*)pbBuffer;
        if (dwLatency < 5) dwLatency = 5;
        if (dwLatency > 1000) dwLatency = 1000;
        m_dwWriteTo = SampleAlign((500 + (m_wfSynth.nAvgBytesPerSec * dwLatency)) / 1000);
    }
    else
    {
        dwLatency = m_dwWriteTo * 1000;
        if (m_wfSynth.nAvgBytesPerSec)
        {
            dwLatency += m_wfSynth.nAvgBytesPerSec / 2; // Correct rounding error.
            dwLatency /= m_wfSynth.nAvgBytesPerSec;
        }
        else
        {
            dwLatency = 300; // Should never happen, trapped by IsValidFormat().
        }
        *(DWORD*)pbBuffer = dwLatency;
    }

    return S_OK;
}

const int CDSLink::m_nProperty = sizeof(m_aProperty) / sizeof(m_aProperty[0]);

/*
CDSLink::FindPropertyItem

Given a GUID and an item ID, find the associated property item in the synth's
table of SYNPROPERTY's.

Returns a pointer to the entry or NULL if the item was not found.
*/
SINKPROPERTY *CDSLink::FindPropertyItem(REFGUID rguid, ULONG ulId)
{
    SINKPROPERTY *pPropertyItem = &m_aProperty[0];
    SINKPROPERTY *pEndOfItems = pPropertyItem + m_nProperty;

    for (; pPropertyItem != pEndOfItems; pPropertyItem++)
    {
        if (*pPropertyItem->pguidPropertySet == rguid &&
             pPropertyItem->ulId == ulId)
        {
            return pPropertyItem;
        }
    }

    return NULL;
}

#define KS_VALID_FLAGS (KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_GET| KSPROPERTY_TYPE_BASICSUPPORT)

STDMETHODIMP CDSLink::KsProperty(
    PKSPROPERTY pPropertyIn, ULONG ulPropertyLength,
    LPVOID pvPropertyData, ULONG ulDataLength,
    PULONG pulBytesReturned)
{
    HRESULT hr = E_FAIL;

    V_INAME(DirectMusicSynthPort::IKsContol::KsProperty);
    V_BUFPTR_WRITE(pPropertyIn, ulPropertyLength);
    V_BUFPTR_WRITE_OPT(pvPropertyData, ulDataLength);
    V_PTR_WRITE(pulBytesReturned, ULONG);

    DWORD dwFlags = pPropertyIn->Flags & KS_VALID_FLAGS;

    SINKPROPERTY *pProperty = FindPropertyItem(pPropertyIn->Set, pPropertyIn->Id);

    if (pProperty == NULL)
    {
        Trace(2, "Warning: KsProperty call requested unknown property.\n");
        return DMUS_E_UNKNOWN_PROPERTY;
    }

    if (pvPropertyData == NULL )
    {
        return E_INVALIDARG;
    }

    switch (dwFlags)
    {
        case KSPROPERTY_TYPE_GET:
            if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_GET))
            {
                Trace(1, "Error: SynthSink does not support Get for the requested property.\n");
                hr = DMUS_E_GET_UNSUPPORTED;
                break;
            }

            if (pProperty->ulFlags & SINKPROP_F_FNHANDLER)
            {
                SINKPROPHANDLER pfn = pProperty->pfnHandler;
                *pulBytesReturned = ulDataLength;
                return (this->*pfn)(pPropertyIn->Id, FALSE, pvPropertyData, pulBytesReturned);
            }

            if (ulDataLength > pProperty->cbPropertyData)
            {
                ulDataLength = pProperty->cbPropertyData;
            }

            CopyMemory(pvPropertyData, pProperty->pPropertyData, ulDataLength);
            *pulBytesReturned = ulDataLength;

            hr = S_OK;
            break;

        case KSPROPERTY_TYPE_SET:
            if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_SET))
            {
                Trace(1, "Error: SynthSink does not support Set for the requested property.\n");
                hr = DMUS_E_SET_UNSUPPORTED;
                break;
            }

            if (pProperty->ulFlags & SINKPROP_F_FNHANDLER)
            {
                SINKPROPHANDLER pfn = pProperty->pfnHandler;
                hr = (this->*pfn)(pPropertyIn->Id, TRUE, pvPropertyData, &ulDataLength);
            }
            else
            {
                if (ulDataLength > pProperty->cbPropertyData)
                {
                    ulDataLength = pProperty->cbPropertyData;
                }

                CopyMemory(pProperty->pPropertyData, pvPropertyData, ulDataLength);

                hr = S_OK;
            }

            break;

        case KSPROPERTY_TYPE_BASICSUPPORT:
            // XXX Find out what convention is for this!!
            //
            if (ulDataLength < sizeof(DWORD) || pvPropertyData == NULL )
            {
                hr = E_INVALIDARG;
                break;
            }

            *(LPDWORD)pvPropertyData = pProperty->ulSupported;
            *pulBytesReturned = sizeof(DWORD);

            hr = S_OK;
            break;

        default:
            Trace(1, "Error: KSProperty failed, Flags must contain one of %s\n"
                      "\tKSPROPERTY_TYPE_SET, KSPROPERTY_TYPE_GET, or KSPROPERTY_TYPE_BASICSUPPORT\n");
            hr = E_INVALIDARG;
            break;
    }

    return hr;
}

STDMETHODIMP CDSLink::KsMethod(
    PKSMETHOD pMethod, ULONG ulMethodLength,
    LPVOID pvMethodData, ULONG ulDataLength,
    PULONG pulBytesReturned)
{
    V_INAME(DirectMusicSynth::IKsContol::KsMethod);
    V_BUFPTR_WRITE(pMethod, ulMethodLength);
    V_BUFPTR_WRITE_OPT(pvMethodData, ulDataLength);
    V_PTR_WRITE(pulBytesReturned, ULONG);

    return DMUS_E_UNKNOWN_PROPERTY;
}

STDMETHODIMP CDSLink::KsEvent(
    PKSEVENT pEvent, ULONG ulEventLength,
    LPVOID pvEventData, ULONG ulDataLength,
    PULONG pulBytesReturned)
{
    V_INAME(DirectMusicSynthPort::IKsContol::KsEvent);
    V_BUFPTR_WRITE(pEvent, ulEventLength);
    V_BUFPTR_WRITE_OPT(pvEventData, ulDataLength);
    V_PTR_WRITE(pulBytesReturned, ULONG);

    return DMUS_E_UNKNOWN_PROPERTY;
}