#include <windows.h>

#include <initguid.h>
#include <dsound.h>
#include <mmreg.h>
#include <ks.h>
#include <math.h>
#include <stdio.h>
#include "kbdsound_device.h"
#include "kbdsound_enum.h"
#include "kbdsound_config.h"

//
#ifdef _DEBUG
#define OUTPUT_DEBUGSTRING_ENABLE
BOOL g_bWaveOutDebug = TRUE;
#endif

typedef HRESULT (WINAPI *pfnDirectSoundCreate)(LPGUID, LPDIRECTSOUND*, LPUNKNOWN);
typedef HRESULT (WINAPI *pfnDirectSoundEnumerate)(LPDSENUMCALLBACKW, LPVOID);

pfnDirectSoundCreate    fnDirectSoundCreate;
pfnDirectSoundEnumerate fnDirectSoundEnumerate;
///////////////////////////////////////////////////////////////////////////////
static HWND __fastcall kbAllocateHWnd(WNDPROC WndProc)
{
    wchar_t szClassName[128];
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(NULL);
    LARGE_INTEGER counter = {0};
    QueryPerformanceCounter(&counter);
    _snwprintf_s(szClassName, 128, _TRUNCATE, L"%08x:%08x:%016llx",
                 GetCurrentThreadId(), GetTickCount(), counter.QuadPart);
    wc.lpszClassName = szClassName;
    RegisterClassEx(&wc);
    return CreateWindowEx(WS_EX_TOOLWINDOW,
                          szClassName,
                          L"",
                          WS_POPUP,
                          0,
                          0,
                          0,
                          0,
                          NULL,
                          NULL,
                          wc.hInstance,
                          NULL);
}
///////////////////////////////////////////////////////////////////////////////
static void __fastcall kbDeallocateHWnd(HWND hWnd)
{
    wchar_t szClassName[256];
    GetClassName(hWnd, szClassName, _countof(szClassName));
    DestroyWindow(hWnd);
    UnregisterClass(szClassName, GetModuleHandle(NULL));
}
///////////////////////////////////////////////////////////////////////////////
HINSTANCE KbDSoundDevice::s_hDSound = NULL;
HWND   KbDSoundDevice::s_hWnd = NULL;
HANDLE KbDSoundDevice::s_hThread = NULL;
DWORD  KbDSoundDevice::s_dwThreadId = 0;
///////////////////////////////////////////////////////////////////////////////
KbDSoundDevice::KbDSoundDevice(void)
{
    m_nSampleRate = 0;
    m_nChannels = 0;
    m_nBps = 0;
    m_nBufferSizeSample = 0;
    m_nBufferSizeBytes = 0;
    m_nBlockAlign = 0;
    m_bUseDsNotify = TRUE;

    m_pDSound = NULL;
    m_pPrimary = NULL;
    m_pDsBuffer = NULL;
    m_pNotify = NULL;
    m_qwOutputCount = 0;  //Output を何回呼び出したか
    m_dwFinishCursor = -1;
    m_dwFinishTime = -1;
    m_qwPrevPosition = 0;
    m_qwFinishPosition = -1;

    m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    int i;
    for(i = 0; i < cNumBuffers; i++){
        m_hNotifyEvents[i] = NULL;
    }
}
///////////////////////////////////////////////////////////////////////////////
IDirectSoundBuffer* __fastcall KbDSoundDevice::CreateDsBuffer(int nSampleRate,
                                                              int nChannels,
                                                              int nBps,
                                                              int nBufferSizeSample,
                                                              BOOL bIsMME)
{
    const WORD wBitsPerSample = (WORD)abs(nBps);
    const int nBlockAlign = wBitsPerSample / 8 * nChannels;
    WAVEFORMATEXTENSIBLE wfx = {0};
    DSBUFFERDESC desc = {0};
    if(nBps == -32){
        wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
    }
    else{
        wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
    }
    wfx.Format.nChannels       = (WORD)nChannels;
    wfx.Format.nBlockAlign     = (WORD)(nChannels *(wBitsPerSample/8));
    wfx.Format.wBitsPerSample  = wBitsPerSample;
    wfx.Format.cbSize          = 0;
    wfx.Format.nSamplesPerSec  = nSampleRate;
    wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec*wfx.Format.nBlockAlign;
    desc.dwSize=sizeof(DSBUFFERDESC);//-sizeof(GUID);
    desc.dwFlags=
            //DSBCAPS_PRIMARYBUFFER
            //DSBCAPS_LOCSOFTWARE |
            DSBCAPS_GETCURRENTPOSITION2 |
            DSBCAPS_CTRLVOLUME |
            DSBCAPS_CTRLPOSITIONNOTIFY |
            DSBCAPS_GLOBALFOCUS |
            DSBCAPS_CTRLFREQUENCY
            ;
    desc.dwBufferBytes = nBufferSizeSample * nBlockAlign * 2;//ダブルバッファ
    desc.lpwfxFormat = (WAVEFORMATEX*)&wfx;
    IDirectSoundBuffer *pBuffer = NULL;
#ifdef OUTPUT_DEBUGSTRING_ENABLE
    if(g_bWaveOutDebug){
        wchar_t sz[256];
        _snwprintf_s(sz, _TRUNCATE, L"DirectSound: Try to open \n"
                      L"BufferSizeSample*2 =%d %d Hz %d ch %d bit ",
                      nBufferSizeSample*2, nSampleRate, nChannels, abs(nBps));
        if(abs(nBps) > 16){
            wchar_t sz2[256];
            if(nBps != -32){
                wcsncpy_s(sz2, L" INT", _TRUNCATE);
                if(bIsMME){
                    wcsncat_s(sz2, L" (WAVE_FORMAT_PCM)", _TRUNCATE);
                }
                else{
                    wcsncat_s(sz2, L" (KSDATAFORMAT_SUBTYPE_PCM)", _TRUNCATE);
                }
            }
            else{
                wcsncpy_s(sz2, L" FLOAT", _TRUNCATE);
                if(bIsMME){
                    wcsncat_s(sz2, L" (WAVE_FORMAT_IEEE_FLOAT)", _TRUNCATE);
                }
                else{
                    wcsncat_s(sz2, L" (KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)", _TRUNCATE);
                }
            }
            wcsncat_s(sz, sz2, _TRUNCATE);
        }
        wcsncat_s(sz, L"\n", _TRUNCATE);
        OutputDebugString(sz);
    }
#endif
    if(!bIsMME && (nBps == 24 || nBps == 32 || nBps == -32)){
        wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
        wfx.Format.cbSize     = sizeof(WAVEFORMATEXTENSIBLE);
        wfx.Samples.wValidBitsPerSample = wBitsPerSample;
        if(nBps > 0){
            wfx.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
        }
        else{
            wfx.SubFormat=KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
        }
    }
    HRESULT hResult = m_pDSound->CreateSoundBuffer(&desc, &pBuffer, NULL);
    if(hResult == DS_OK){
        if(pBuffer->Play(0,0,DSBPLAY_LOOPING) != DS_OK){//再生を試みてみる
            pBuffer->Release();//デバイスを開けていない
            pBuffer = NULL;
        }
        else{
            pBuffer->Stop();
            if(m_bUseDsNotify){
                pBuffer->QueryInterface(IID_IDirectSoundNotify, (void**)&m_pNotify);
            }
            if(m_pNotify){
                int i;
                for(i = 0; i <cNumBuffers; i++){
                    m_hNotifyEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
                }
            }
        }
    }
#ifdef OUTPUT_DEBUGSTRING_ENABLE
    if(g_bWaveOutDebug){
        wchar_t sz[256];
        lstrcpy(sz, L"DirectSound: ");
        if(pBuffer){
            lstrcpy(sz, L"Succeeded.\n");
        }
        else{
            HRESULT hr = hResult;
            lstrcpy(sz, L"Failed. (retval = ");
            if(hr == DSERR_ALLOCATED){
                lstrcat(sz, L"DSERR_ALLOCATED");
            }
            else if(hr == DSERR_BADFORMAT ){
                lstrcat(sz, L"DSERR_BADFORMAT");
            }
            else if(hr == DSERR_INVALIDPARAM){
                lstrcat(sz, L"DSERR_INVALIDPARAM");
            }
            else if(hr == DSERR_NOAGGREGATION){
                lstrcat(sz, L"DSERR_NOAGGREGATION");
            }
            else if(hr == DSERR_OUTOFMEMORY){
                lstrcat(sz, L"DSERR_OUTOFMEMORY");
            }
            else if(hr == DSERR_UNINITIALIZED){
                lstrcat(sz, L"DSERR_UNINITIALIZED");
            }
            else if(hr == DSERR_UNSUPPORTED){
                lstrcat(sz, L"DSERR_UNSUPPORTED");
            }
            else{
                lstrcat(sz, L"unknown");
            }
            lstrcat(sz, L")\n");
        }
        OutputDebugString(sz);
    }
#endif
    return pBuffer;
}
///////////////////////////////////////////////////////////////////////////////
KbDSoundDevice::~KbDSoundDevice(void)
{
    if(m_pDsBuffer){
        if(m_pNotify){
            m_pNotify->Release();
            for(int i = 0; i < cNumBuffers; i++){
                if(m_hNotifyEvents[i]){
                    CloseHandle(m_hNotifyEvents[i]);
                }
            }
        }
        m_pDsBuffer->Release();
    }
    if(m_pDSound){
        m_pPrimary->Release();
        m_pDSound->Release();
    }
    CloseHandle(m_hStopEvent);
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbDSoundDevice::CreateDirectSound(const wchar_t *cszDeviceName,
                                                  const wchar_t *cszId)
{
    if(!LoadDSoundDll()){
        return FALSE;
    }
    GUID *pDriverGuid = NULL;
    GUID DriverGuid = {0};
    if(cszDeviceName[0]){
#ifdef _DEBUG
        if(g_bWaveOutDebug){
            wchar_t sz[512];
            wsprintf(sz, L"DirectSound: DeviceName = %s\n", cszDeviceName);
            OutputDebugString(sz);
        }
#endif
        KbDSoundEnumerator dsEnumerator;
        if(!dsEnumerator.GetDriverGuidFromDeviceName(cszDeviceName, cszId, &DriverGuid)){
            return FALSE;
        }
        pDriverGuid = &DriverGuid;
    }
    fnDirectSoundCreate(pDriverGuid, &m_pDSound, NULL);
    if(!m_pDSound){
        return FALSE;
    }
    HWND hWnd = CreateDSoundWindow();
    if(DS_OK!=(m_pDSound->SetCooperativeLevel(hWnd, DSSCL_PRIORITY))){
        m_pDSound->Release();
        m_pDSound = NULL;
#ifdef _DEBUG
        if(g_bWaveOutDebug){
            OutputDebugString(L"DirectSound: SetCooperativeLevel Failed.\n");
        }
#endif
        return FALSE;
    }
    // プライマリバッファの作成
    DSBUFFERDESC SoundBufferDesc = {0};
    SoundBufferDesc.dwSize=sizeof(DSBUFFERDESC);
    SoundBufferDesc.dwFlags=DSBCAPS_PRIMARYBUFFER;
    if(DS_OK!=(m_pDSound->CreateSoundBuffer(&SoundBufferDesc,&m_pPrimary,NULL))){
#ifdef _DEBUG
        if(g_bWaveOutDebug){
            OutputDebugString(L"DirectSound: CreateSoundBuffer Failed.\n");
        }
#endif
        m_pDSound->Release();
        m_pDSound = NULL;
        return FALSE;
    }
    WAVEFORMATEX wfx = {0};
    wfx.cbSize=0;
    wfx.wFormatTag=WAVE_FORMAT_PCM;
    wfx.nChannels=2;
    wfx.nSamplesPerSec=44100;//GetSampleRate();
    wfx.wBitsPerSample = 16;
    wfx.nBlockAlign=(WORD)(wfx.wBitsPerSample/8*wfx.nChannels);
    wfx.nAvgBytesPerSec=wfx.nSamplesPerSec*wfx.nBlockAlign;
    m_pPrimary->SetFormat(&wfx);
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbDSoundDevice::Stop(void)
{
    m_pDsBuffer->Stop();
    SetEvent(m_hStopEvent);
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbDSoundDevice::Pause(void)
{
    m_pDsBuffer->Stop();
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbDSoundDevice::Restart(void)
{
    m_pDsBuffer->Play(0,0,DSBPLAY_LOOPING);
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbDSoundDevice::SetVolume(int nVolume)
{
    BOOL bRet = false;
    if(0 <= nVolume){
        int nVolumeDB = 0;
        if(nVolume <= 100){
            if(nVolume > 0){
                double dB = 20.0 * log10((double)nVolume/100.0);
                nVolumeDB = (int)(100*dB);
            }
            else{
                nVolumeDB = -10000;
            }
            bRet = true;
        }
        m_pDsBuffer->SetVolume(nVolumeDB);
    }
    return bRet;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbDSoundDevice::SetRate(int nRate)
{
    if(nRate < 50)return FALSE;
    if(nRate > 200)return FALSE;
    int nFreq = m_nSampleRate * nRate / 100;
    m_pDsBuffer->SetFrequency(nFreq);
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL  WINAPI KbDSoundDevice::Output(BYTE *pOutput, int nSizeSample)
{//デバイスにデータを出力
    BYTE *p1,*p2;
    DWORD b1,b2;
    DWORD dwLockStartPos;
    if(WaitForSingleObject(m_hStopEvent, 0) == WAIT_OBJECT_0){
        m_qwOutputCount = 0;
        m_qwPrevPosition = 0;
        m_qwFinishPosition = -1;
        m_dwFinishCursor = -1;
        m_dwFinishTime = -1;
        ResetEvent(m_hStopEvent);
    }
    int nCurBufIndex = m_qwOutputCount & 0x01;
    dwLockStartPos = m_nBufferSizeBytes * nCurBufIndex;
    if(m_pDsBuffer->Lock(dwLockStartPos, m_nBufferSizeBytes,
                         (void**)&p1,&b1,(void**)&p2,&b2,0)==DS_OK){
        memcpy((void*)p1,(void*)pOutput,b1);
        m_pDsBuffer->Unlock((void*)p1,b1,(void*)p2,b2);
        if(m_qwOutputCount++ == 0){//停止後初めて呼ばれたときだけ再生開始
            m_pDsBuffer->SetCurrentPosition(0);
            if(m_pNotify){
                DSBPOSITIONNOTIFY pn[cNumBuffers];
                int i;
                for(i = 0; i <  cNumBuffers; i++){
                    if(i == 0){
                        pn[i].dwOffset = m_nBufferSizeBytes;
                    }
                    else{
                        pn[i].dwOffset = 0;
                    }
                    pn[i].hEventNotify = m_hNotifyEvents[i];
                    ResetEvent(m_hNotifyEvents[i]);
                }
                m_pNotify->SetNotificationPositions(cNumBuffers, pn);
                if(m_pDsBuffer->Play(0, 0, DSBPLAY_LOOPING) == DS_OK){
                    //m_hNotifyEvents[1] は再生を始めると直ちにシグナルになる
                    WaitForSingleObject(m_hNotifyEvents[1], INFINITE);
                }
            }
            else{
                m_pDsBuffer->Play(0, 0, DSBPLAY_LOOPING);
            }
        }
        if(nSizeSample < m_nBufferSizeSample){//演奏終了
            if(m_qwFinishPosition == (UINT64)-1){
                m_qwFinishPosition = (m_qwOutputCount - 1) * m_nBufferSizeSample + nSizeSample;
                m_dwFinishCursor = nSizeSample * m_nBlockAlign;
                if(nCurBufIndex != 0){
                    m_dwFinishCursor += m_nBufferSizeBytes;
                }
                m_dwFinishTime = timeGetTime();
            }
            else{
                //m_qwOutputCount--;//再生位置の補正
            }
        }
        return TRUE;
    }
    else{
#ifdef _DEBUG
        MessageBox(NULL, L"Lock failed", L"", MB_OK);
#endif
        return FALSE;
    }
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbDSoundDevice::Wait(void)
{//次の書き込みが可能になるまで待つ
    if(m_qwOutputCount < 2){
    //まだ全てのバッファを書き終えていない
    //=書き込み可能
        return;
    }
    if(m_dwFinishCursor == (DWORD)-2){//最後まで再生し終わっている
        return;
    }
    const DWORD dwOutputBufferSize = m_nBufferSizeBytes;
    const int nCurBufIndex = m_qwOutputCount&0x01;
    if(m_dwFinishCursor == (DWORD)-1 && m_pNotify){
        HANDLE hEvents[2] = {m_hStopEvent, m_hNotifyEvents[nCurBufIndex]};
        if(WaitForMultipleObjects(2, hEvents, FALSE, INFINITE) == WAIT_OBJECT_0){
            //Stop が呼ばれた
            return;
        }
        //再生カーソルのチェックに遷移する
        //if(m_dwFinishCursor != (DWORD)-1){
        //    return;
        //}
    }
    while(1){
        //再生カーソルを確認。問題がないところまで移動するまで待つ
        DWORD dwWait;//待ち時間
        DWORD pp, wp;
        if(m_pDsBuffer->GetCurrentPosition(&pp, &wp) == DS_OK){
            if(nCurBufIndex == 0){
                if(//m_dwFinishCursor != (DWORD)-1 &&
                   m_dwFinishCursor <= dwOutputBufferSize){
                    if(pp >= m_dwFinishCursor){
                        m_dwFinishCursor = -2;
                        return;
                    }
                    else{
                        dwWait = m_dwFinishCursor - pp;
                    }
                }
                else{
                    if(pp > dwOutputBufferSize){
                    //if(pp >= dwOutputBufferSize){//DirectSoundNotify を不使用時に正常に再生されなくなる
                        return;
                    }
                    else{
                        dwWait = dwOutputBufferSize - pp;
                    }
                }
            }
            else{
                if(m_dwFinishCursor != (DWORD)-1 &&
                   m_dwFinishCursor >= dwOutputBufferSize){
                    if(pp >= m_dwFinishCursor){
                        m_dwFinishCursor = -2;
                        return;
                    }
                    else{
                        dwWait = m_dwFinishCursor - pp;
                    }
                }
                else{
                    if(pp <= dwOutputBufferSize){
                        if(pp != 0){
                            return;
                        }
                        dwWait = 0;
                    }
                    else{
                        dwWait = pp - dwOutputBufferSize;
                    }
                }
            }
        }
        dwWait *= (/*1000.0*/500.0 / (m_nSampleRate * m_nBlockAlign));
        //dwWait /= 2;//待ちすぎると再生カーソルが戻ってしまう可能性がある
        dwWait++;//最低でも 1ms は待つ
        if(!m_pNotify && dwWait > 5){//DirectSoundNotify 不使用時はポーリング頻度を高める
            dwWait = 5;
        }
        //dwWait = 1;
        if(m_dwFinishCursor != (DWORD)-1){
            if(m_pNotify){
                HANDLE hEvents[2] = {m_hStopEvent, m_hNotifyEvents[nCurBufIndex]};
                if(WaitForMultipleObjects(2, hEvents, FALSE, dwWait) != WAIT_TIMEOUT){
                    return;
                }
            }
            else{
                if(WaitForSingleObject(m_hStopEvent, dwWait) == WAIT_OBJECT_0){
                    return;
                }
            }
            DWORD dwElapsed = timeGetTime() - m_dwFinishTime;
            DWORD dwSizeMs = MulDiv(m_nBufferSizeSample*2, 1000, m_nSampleRate);
            if(dwElapsed > dwSizeMs){
                return;
            }
        }
        else if(WaitForSingleObject(m_hStopEvent, dwWait) == WAIT_OBJECT_0){
            //Stop が呼ばれた
            break;
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbDSoundDevice::GetPosition(void)
{//現在の再生位置を返す
    if(m_dwFinishCursor == (DWORD)-2){//演奏終了済み
        return m_qwFinishPosition;
    }
    DWORD pp, wp;
    if(m_pDsBuffer->GetCurrentPosition(&pp, &wp) != DS_OK){
        return 0;
    }
    UINT64 ret = pp / m_nBlockAlign;
    if(m_qwOutputCount != 1){
        ret += (m_qwOutputCount-2) * m_nBufferSizeSample;
        if((m_qwOutputCount & 0x01)){
        //m_nBufferSizeSample〜m_nBufferSizeSample*2 の間
            if(pp == 0){
            //再生カーソルが m_nBufferSizeSample*2 の位置にあると考える
                ret += m_nBufferSizeSample;
            }
            else{
                ret -= m_nBufferSizeSample;
            }
        }
    }
    if(ret < m_qwPrevPosition){
#ifdef _DEBUG
        wchar_t szMes[256];
        double dMsPerSample = 1000.0 / ((double)m_nSampleRate);
        DWORD dwDiff = (m_qwPrevPosition - ret)*dMsPerSample;
        wsprintf(szMes, L"ret < m_qwPrevCurrentPosition. diff=%d\n", dwDiff);
        OutputDebugString(szMes);
#endif
        if(m_qwFinishPosition != (UINT64)-1){
            //ret = m_qwFinishPosition;
            ret += m_nBufferSizeSample * 2;
        }
        else{
            ret = m_qwPrevPosition;
        }
    }
    if(ret > m_qwFinishPosition){
        ret = m_qwFinishPosition;
    }
    m_qwPrevPosition = ret;
    return ret;
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbDSoundDevice::Open(const wchar_t *cszDeviceName,
                                     const wchar_t *cszId,
                                     const KPI_DEVICE_FORMAT *cpFormat,
                                     int nBufferSizeSample,
                                     IKpiConfig *pConfig)
{
    {
    KbDSoundConfig cfg(pConfig, cszDeviceName, cszId);
    m_bUseDsNotify = cfg.GetUseDsNotify();
    }
    int nSampleRate = cpFormat->dwSampleRate;
    int nChannels = cpFormat->dwChannels;
    int nBps = cpFormat->nBitsPerSample;
    switch(nBps){
        case 8: case 16: case 24: case 32: case -32: break;
        default: return FALSE; break;
    }
    if(!m_pDSound){
        if(!CreateDirectSound(cszDeviceName, cszId)){
            return FALSE;
        }
    }
    IDirectSoundBuffer* pDsBuffer;
    pDsBuffer = CreateDsBuffer(nSampleRate, nChannels, nBps,
                               nBufferSizeSample, FALSE);
    if(!pDsBuffer){
        pDsBuffer = CreateDsBuffer(nSampleRate, nChannels, nBps,
                                   nBufferSizeSample, TRUE);
        if(!pDsBuffer){
            return FALSE;
        }
    }
    m_nSampleRate = nSampleRate;
    m_nChannels = nChannels;
    m_nBps = nBps;
    m_nBlockAlign = nChannels * abs(nBps) / 8;
    m_nBufferSizeSample = nBufferSizeSample;
    m_nBufferSizeBytes = nBufferSizeSample * m_nBlockAlign;
    m_pDsBuffer = pDsBuffer;
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbDSoundDevice::ThreadProc(void *pvParam)
{//IDirectSound::SetCooperativeLevel に渡すウィンドウのハンドルを作成するスレッド
 //Open が呼ばれるスレッド内で作成すると本体の DDE 処理がおかしくなる
 //(作成されたウィンドウのメッセージループが適切に処理されなくなるから？)
    HWND hWnd = kbAllocateHWnd(DefWindowProc);
    s_hWnd = hWnd;
    SetEvent((HANDLE)pvParam);
    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0) > 0){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    s_hWnd = NULL;
    kbDeallocateHWnd(hWnd);
    return 0;
}
///////////////////////////////////////////////////////////////////////////////
HWND __fastcall KbDSoundDevice::CreateDSoundWindow(void)
{
    if(s_hThread){
        return s_hWnd;
    }
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    //s_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, hEvent, 0,
    //                                        (unsigned*)&s_dwThreadId);//ThreadProc 内で C ランタイムは使わない
    s_hThread = CreateThread(NULL, 0, ThreadProc, hEvent, 0, &s_dwThreadId);
    WaitForSingleObject(hEvent, INFINITE);//ウィンドウの作成が終わるまで待つ
    CloseHandle(hEvent);
    return s_hWnd;
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbDSoundDevice::LoadDSoundDll(void)
{
    if(s_hDSound){
        return TRUE;
    }
    HINSTANCE hDll = LoadLibrary(L"dsound.dll");
    if(!hDll){
        return FALSE;
    }
    fnDirectSoundCreate = (pfnDirectSoundCreate)GetProcAddress(hDll, "DirectSoundCreate");
    fnDirectSoundEnumerate = (pfnDirectSoundEnumerate)GetProcAddress(hDll, "DirectSoundEnumerateW");
    if(!fnDirectSoundCreate || !fnDirectSoundEnumerate){
        fnDirectSoundCreate = NULL;
        fnDirectSoundEnumerate = NULL;
        FreeLibrary(hDll);
        return FALSE;
    }
    s_hDSound = hDll;
    //列挙には DLL のロードが必要だが、ウィンドウの作成は必要ない
    //(必要になるまで作成しない)
    //CreateDSoundWindow();
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbDSoundDevice::Uninitialize(void)
{
    if(s_hDSound){
        FreeLibrary(s_hDSound);
        s_hDSound = NULL;
    }
    if(s_hThread){
        do{//メッセージの取りこぼし回避(念の為)
            PostThreadMessage(s_dwThreadId, WM_QUIT, 0, 0);
        }while(WaitForSingleObject(s_hThread, 100) != WAIT_OBJECT_0);
        CloseHandle(s_hThread);
        s_hThread = NULL;
        s_dwThreadId = 0;
    }
}
///////////////////////////////////////////////////////////////////////////////
