#include <windows.h>
#include <stdio.h>
#include <process.h>
#include "kbasio_device.h"
#include "kbasio_enum.h"
#include "kbasio_config.h"

#define ASIO64toUINT64(a)  ((UINT64)(a).lo | (((UINT64)(a).hi)<<32))

////////////////////////////////////////////////////////////////
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, _TRUNCATE, 
                 L"kbasio.kpi:%08x:%016llx", 
                 GetCurrentThreadId(), 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));
}
///////////////////////////////////////////////////////////////////////////////
#pragma pack(push, 1)
typedef struct SAMPLE24_MONO {
    BYTE l[3];
}SAMPLE24_MONO;
#pragma pack(pop)
///////////////////////////////////////////////////////////////////////////////
void __fastcall ConvertPcmFrom64f(BYTE *pDest, const double *pSrc,
                                  DWORD dwSamples, DWORD dwBps, DWORD dwChannels)
{//64bit float => dwBps に変換
    const int nSamples = dwSamples * dwChannels;
    int i;
    if(dwBps == (DWORD)-64){
        double  *dst64f = (double*)pDest;
        const double *src64 = pSrc;
        for(i = 0; i < nSamples; i++){
            dst64f[i] = src64[i];
        }
    }
    else if(dwBps == (DWORD)-32){
        float  *dst32f = (float*)pDest;
        const double *src64 = pSrc;
        for(i = 0; i < nSamples; i++){
            dst32f[i] = src64[i];
        }
    }
    else if(dwBps == 32){
        int    *dst32 = (int*)pDest;
        const double *src64 = pSrc;
        for(i = 0; i < nSamples; i++){
            double s64 = src64[i] * 0x80000000i64;
            if(s64 > 0x7FFFFFFF){
                s64 = 0x7FFFFFFF;
            }
            else if(s64 < -0x80000000i64){
                s64 = -0x80000000i64;
            }
            dst32[i] = s64;
        }
    }
    else if(dwBps == 24){
        SAMPLE24_MONO *dst24 = (SAMPLE24_MONO*)pDest;
        const double *src64 = pSrc;
        for(i = 0; i < nSamples; i++){
            int  d24;
            double s64 = src64[i] * 0x800000;
#ifndef _WIN64
            __asm{
                fld   s64;
                fistp d24;
            }
#else
            d24 = (int)s64;
#endif
            if(d24 > 0x7FFFFF){
                d24 = 0x7FFFFF;
            }
            else if(d24 < -0x800000){
                d24 = -0x800000;
            }
            dst24[i].l[0] = ((BYTE*)(&d24))[0];
            dst24[i].l[1] = ((BYTE*)(&d24))[1];
            dst24[i].l[2] = ((BYTE*)(&d24))[2];
        }
    }
    else if(dwBps == 16){
        short *dst16 =(short*)pDest;
        const double *src64 = pSrc;
        int i;
        for(i = 0; i < nSamples; i++){
            double s64 = src64[i] * 0x8000;
            int d16;
#ifndef _WIN64
            __asm{
                fld s64;
                fistp d16
            }
#else
            d16 = (int)s64;
#endif
            if(d16 > 0x7FFF){
                d16 = 0x7FFF;
            }
            else if(d16 < -0x8000){
                d16 = -0x8000;
            }
            dst16[i] = d16;
        }
    }
    else if(dwBps == 8){
        BYTE *dst8 =(BYTE*)pDest;
        const double *src64 = pSrc;
        int i;
        for(i = 0; i < nSamples; i++){
            double s64 = (src64[i]+1.0)*0x80;
            int d8;
#ifndef _WIN64
            __asm{
                fld   s64;
                fistp d8;
            }
#else
            d8 = (int)s64;
#endif
            if(d8 > 0xFF){
                d8 = 0xFF;
            }
            else if(d8 < 0){
                d8 = 0;
            }
            dst8[i] = d8;
        }
    }
    else{//未対応。念のためゼロクリアする
        ZeroMemory(pDest, nSamples * (abs((int)dwBps)/8));
    }
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall ConvertDoPToDSD(BYTE *pDest, const BYTE *pSrc,
                                DWORD dwSamples, DWORD dwBps, DWORD dwChannels)
{//DoP => DSD に変換
    if(dwBps == (DWORD)-64){//64bit float
    //デコーダプラグインから得た 24bit/32bit DoP を通常の PCM と
    //見なして(本体によって) 64bit float に変換されている。
        DWORD i, j;
        const double *src64 = (double*)pSrc;
        for(i = 0; i < dwSamples; i++){
            for(j = 0; j < dwChannels; j++){
                //32bit int に戻す
                double s64 = *src64 * 0x80000000i64;
                //↓元々 DoP のデータを(加工しないで) 64bit float 化しただけなので不要
                //if(s64 > 0x7FFFFFFF){
                //    s64 = 0x7FFFFFFF;
                //}
                //else if(s64 < -0x80000000i64){
                //    s64 = -0x80000000i64;
                //}
                int src32 = s64;
                //marker を取り除く
                BYTE *src = (BYTE*)&src32;
                pDest[j] = src[2];
                pDest[j+dwChannels] = src[1];
                src64++;
            }
            pDest += dwChannels*2;
        }
    }
    else if(dwBps == 32){
        DWORD i, j;
        for(i = 0; i < dwSamples; i++){
            for(j = 0; j < dwChannels; j++){
                //marker を取り除く
                pDest[j] = pSrc[2];
                pDest[j+dwChannels] = pSrc[1];
                pSrc += 4;
            }
            pDest += dwChannels*2;
        }
    }
    else if(dwBps == 24){
        DWORD i, j;
        for(i = 0; i < dwSamples; i++){
            for(j = 0; j < dwChannels; j++){
                //marker を取り除く
                pDest[j] = pSrc[1];
                pDest[j+dwChannels] = pSrc[0];
                pSrc += 3;
            }
            pDest += dwChannels*2;
        }
    }
    else{
        memset(pDest, 0x69, dwSamples);
    }
}
///////////////////////////////////////////////////////////////////////////////
KbAsioDevice* KbAsioDevice::s_pActive = NULL;
///////////////////////////////////////////////////////////////////////////////
KbAsioDevice::KbAsioDevice(void)
{
    if(!s_pActive){
        s_pActive = this;
    }
    m_hWnd = NULL;
    m_hThread = NULL;
    m_hEventCmd = NULL;
    m_hEventDone = NULL;
    ZeroMemory(&m_Command, sizeof(m_Command));
    m_nSampleRate = 0;
    m_nChannels = 0;
    m_nInputBps = 0;
    m_nOutputBps = 0;
    m_nBufferSizeSample = 0;
    m_nOutputBlockAlign = 0;
    m_nInputBlockAlign = 0;
    m_dwFormatType = KPI_MEDIAINFO::FORMAT_PCM;
    m_lLatency = 0;
    m_pAsio = NULL;
    ZeroMemory(&m_asioChannelInfo, sizeof(m_asioChannelInfo));
    ZeroMemory(&m_asioBufferInfo, sizeof(m_asioBufferInfo));
    m_asioCallbacks.bufferSwitch = asio_callback_bufferSwitch;
    m_asioCallbacks.sampleRateDidChange = asio_callback_sampleRateDidChange;
    m_asioCallbacks.asioMessage = asio_callback_asioMessage;
    m_asioCallbacks.bufferSwitchTimeInfo = asio_callback_bufferSwitchTimeInfo;
    m_nAsioBufferSizeSample = 0;
    m_qwTotalWritten = 0;
    m_qwAsioPos = 0;
    m_qwAsioTimeNs = 0;
    m_qwPrevPos  = 0;
    m_i64OffsetPos = 0;
    m_nSamplePerBytes = 1;
    InitializeCriticalSection(&m_cs);
    m_hEventWritten = NULL;
    m_bPaused = false;
    m_bPostOutput = false;
    m_bAsioStart = false;
    m_bDopToDsd = false;
    m_bControlPanel = false;
    m_bFinished = false;
    m_pOutputWork = NULL;
    m_szDeviceName[0] = 0;
    m_szDeviceId[0] = 0;
    m_dwSilenceMS = 0;
}
///////////////////////////////////////////////////////////////////////////////
KbAsioDevice::~KbAsioDevice(void)
{
    if(m_hThread){
        SendCommand(CMD_ASIO_QUIT, 0, 0);
        WaitForSingleObject(m_hThread, INFINITE);
        CloseHandle(m_hEventCmd);
        CloseHandle(m_hEventDone);
        CloseHandle(m_hThread);
    }
    DeleteCriticalSection(&m_cs);
    if(s_pActive == this){
        s_pActive = NULL;
    }
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbAsioDevice::Output(BYTE *pOutput, int nSizeSample)
{
    //nSizeSample < m_nBufferSizeSample の場合、残りのバッファは無音が入っている
    //無音部分も含めて書き込む
    if(!m_nSampleRate){//start() に失敗している
        ZeroMemory(pOutput, nSizeSample * m_nOutputBlockAlign);
        return FALSE;
    }
    Lock();
    ResetEvent(m_hEventWritten);
    if(!m_bAsioStart){//まだ ASIO を開始していない
        if(start() != ASE_OK) {//再生に失敗(デバイスの開き直しを試みる処理は廃止)
            m_nSampleRate = 0;
            SetEvent(m_hEventWritten);
            Unlock();
            return FALSE;
        }
        m_bAsioStart = true;
        Unlock();
        Sleep(m_dwSilenceMS);//再生直後の音途切れ回避(HP-A8 対策)
        WaitForSingleObject(m_hEventWritten, 500/*INFINITE*/);//最初の無音が再生されるまで待つ
        Lock();
        ResetEvent(m_hEventWritten);
    }
    else{
        if(m_qwTotalWritten == 0){//停止中
            m_i64OffsetPos = -(INT64)m_qwAsioPos;//再生位置をリセット
        }
        m_qwTotalWritten += nSizeSample;
    }
    if(m_bDopToDsd){//DoP=>DSD に変換
        BYTE *pOutput1, *pOutput2;
        DWORD dwOutputSize1, dwOutputSize2;
        //1ch あたり DoP 1 サンプルあたり必要なバイト数は 2 バイト
        m_RingBuffer.LockBuffer(&pOutput1, &pOutput2, 
                                &dwOutputSize1, &dwOutputSize2,
                                nSizeSample * m_nChannels * 2);
        if(dwOutputSize1){
            ConvertDoPToDSD(pOutput1, pOutput, dwOutputSize1/(m_nChannels*2), m_nInputBps, m_nChannels);
        }
        if (dwOutputSize2) {
            ConvertDoPToDSD(pOutput2, pOutput + dwOutputSize1/(m_nChannels*2)*m_nInputBlockAlign,
                            dwOutputSize2/(m_nChannels*2), m_nInputBps, m_nChannels);
        }
        m_RingBuffer.UnlockBuffer(nSizeSample * m_nChannels * 2);
        
    }
    else if(m_nInputBps == m_nOutputBps){//受け取ったデータをそのままリングバッファに書き込む
        m_RingBuffer.Write(pOutput, nSizeSample * m_nOutputBlockAlign);
    }
    else{//m_nInputBps != m_nOutputBps の場合、Open の動作仕様上 m_nInputBps == -64 しか有りえない
        //最終出力段のビット数に変換
        BYTE *pOutput1, *pOutput2;
        DWORD dwOutputSize1, dwOutputSize2;
        m_RingBuffer.LockBuffer(&pOutput1, &pOutput2, 
                                &dwOutputSize1, &dwOutputSize2,
                                nSizeSample * m_nOutputBlockAlign);
        if(dwOutputSize1){
            ConvertPcmFrom64f(pOutput1, (double*)pOutput, 
                                dwOutputSize1 / m_nOutputBlockAlign, m_nOutputBps, m_nChannels);
        }
        if (dwOutputSize2) {
            ConvertPcmFrom64f(pOutput2, (double*)(pOutput + (dwOutputSize1/m_nOutputBlockAlign)*m_nInputBlockAlign),
                                dwOutputSize2 / m_nOutputBlockAlign, m_nOutputBps, m_nChannels);
        }
        m_RingBuffer.UnlockBuffer(nSizeSample * m_nOutputBlockAlign);
    }
    if(!m_bDopToDsd){
        if(nSizeSample != m_nBufferSizeSample && !m_bFinished){
            m_bFinished = true;
        }
        if(!m_bFinished){
            if(m_RingBuffer.GetWritten() <  m_nAsioBufferSizeSample * m_nOutputBlockAlign){
                SetEvent(m_hEventWritten);
            }
        }
    }
    else{
        if(nSizeSample != m_nBufferSizeSample/16 && !m_bFinished){
            m_bFinished = true;
        }
        if(!m_bFinished){
            if(m_RingBuffer.GetWritten() <  m_nAsioBufferSizeSample/16){
                SetEvent(m_hEventWritten);
            }
        }
    }
    m_bPaused = false;
    Unlock();
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbAsioDevice::Stop(void)
{//再生を停止。実際には無音を再生し続ける
    Lock();
    m_bPaused = true;
    m_qwTotalWritten = 0;
    m_bFinished = false;
    m_RingBuffer.Reset();
    m_qwPrevPos = 0;
    SetEvent(m_hEventWritten);
    Unlock();
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbAsioDevice::Pause(void)
{//一時停止。実際には無音を再生し続ける
    Lock();
    m_bPaused = true;
    Unlock();
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbAsioDevice::Restart(void)
{
    Lock();
    m_bPaused = false;
    Unlock();
}
///////////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbAsioDevice::GetPosition(void)
{//デバイスの再生位置をサンプル単位で返す
 //HP-A8 の場合、BufferSize を最大(3840samples)にすると再生位置がかなりずれる
 //取得方法が間違ってるかもしれないが、ASIO4ALL で最大の2048にしたときと HP-A8 で2048に
 //したときのずれ方が違う(ASIO4ALL だとずれを感じない)
    const int nSizeSample = m_nAsioBufferSizeSample / m_nSamplePerBytes;
    Lock();
    UINT64 qwPosition = m_qwAsioPos + m_i64OffsetPos;
    if(qwPosition > nSizeSample){
        qwPosition -= nSizeSample;
    }
    else{
        qwPosition = 0;
    }
    //レイテンシ分を減算
    if (qwPosition > m_lLatency) {
        qwPosition -= m_lLatency;
    }
    else {
        qwPosition = 0;
    }
    //直前の bufferSwitch が呼ばれた時刻からの経過時間分を加算
    UINT64 qwElapsedMs = m_qwAsioTimeNs == 0 ? 0 : (UINT64)timeGetTime() - m_qwAsioTimeNs/1000000;
    if(m_bDopToDsd){
    //ここで返すサンプル数は変換前の PCM 相当での値
    //DoP 1サンプル = DSD 16(=2x8) サンプル = DSD 2 バイト
        qwPosition /= 2;
        //DoP 相当では再生周波数は 1/16
        qwPosition += (qwElapsedMs * (m_nSampleRate/16) / 1000);
    }
    else{
        qwPosition += (qwElapsedMs * m_nSampleRate / 1000);
    }
    if(qwPosition < m_qwPrevPos){//位置が戻らないようにする
    #ifdef  _DEBUG
        UINT64 qwDiffSample = m_qwPrevPos-qwPosition;
        double dDiffMs = qwDiffSample / (double)m_nSampleRate * 1000.0;
        wchar_t sz[128];
        _snwprintf_s(sz, _TRUNCATE, L"qwPosition < m_qwPrevPos: diff=%lfms\n", dDiffMs);
        OutputDebugString(sz);
        //HP-A8 で 0.5〜4ms 程度
        //ASIO4ALL(出力先 Realtek Audio)では滅多に実行されない(されても0.1ms程度)
    #endif
        qwPosition = m_qwPrevPos;
    }
    if(qwPosition > m_qwTotalWritten){//進み過ぎないようにする
    #ifdef _DEBUG
        UINT64 qwDiffSample = qwPosition - m_qwTotalWritten;
        double dDiffMs = qwDiffSample / (double)m_nSampleRate * 1000.0;
        wchar_t sz[128];
        _snwprintf_s(sz, _TRUNCATE, L"qwPosition > m_qwTotalWritten: diff=%lfms\n", dDiffMs);
        OutputDebugString(sz);
        //演奏終了時以外はほぼ実行されない
    #endif
        qwPosition = m_qwTotalWritten;
    }
    m_qwPrevPos = qwPosition;
    Unlock();
    return qwPosition;
}
///////////////////////////////////////////////////////////////////////////////
void WINAPI KbAsioDevice::Wait(void)
{//次の書き込みが可能になるまで待つ
    WaitForSingleObject(m_hEventWritten, INFINITE);
    if(m_bFinished){
        while(GetPosition() != m_qwTotalWritten){
            Sleep(1);
        }
    } 
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbAsioDevice::SetVolume(int nVolume)
{
    return false;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbAsioDevice::SetRate(int nRate)
{
    return false;
}
///////////////////////////////////////////////////////////////////////////////
void KbAsioDevice::asio_callback_sampleRateDidChange(ASIOSampleRate sRate)
{
}
///////////////////////////////////////////////////////////////////////////////
long KbAsioDevice::asio_callback_asioMessage(long selector, long value, void* message, double* opt)
{
    return s_pActive->asioMessage(selector, value, message, opt);
}
///////////////////////////////////////////////////////////////////////////////
long KbAsioDevice::asioMessage(long selector, long value, void* message, double* opt)
{
    long ret = 0;
    switch(selector)
    {
        case kAsioSelectorSupported:
            if(value == kAsioResetRequest
            || value == kAsioEngineVersion
            || value == kAsioResyncRequest
            || value == kAsioLatenciesChanged
            // the following three were added for ASIO 2.0, you don't necessarily have to support them
            || value == kAsioSupportsTimeInfo
            || value == kAsioSupportsTimeCode
            || value == kAsioSupportsInputMonitor)
                ret = 1L;
            break;
        case kAsioResetRequest:
            // defer the task and perform the reset of the driver during the next "safe" situation
            // You cannot reset the driver right now, as this code is called from the driver.
            // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction
            // Afterwards you initialize the driver again.
            //OutputDebugString("kAsioResetRequest\n");
            ret = 1L;
            break;
        case kAsioResyncRequest:
            // This informs the application, that the driver encountered some non fatal data loss.
            // It is used for synchronization purposes of different media.
            // Added mainly to work around the Win16Mutex problems in Windows 95/98 with the
            // Windows Multimedia system, which could loose data because the Mutex was hold too long
            // by another thread.
            // However a driver can issue it in other situations, too.
            ret = 1L;
            break;
        case kAsioLatenciesChanged:
            // This will inform the host application that the drivers were latencies changed.
            // Beware, it this does not mean that the buffer sizes have changed!
            // You might need to update internal delay data.
            ret = 1L;
            break;
        case kAsioEngineVersion:
            // return the supported ASIO version of the host application
            // If a host applications does not implement this selector, ASIO 1.0 is assumed
            // by the driver
            ret = 2L;
            break;
        case kAsioSupportsTimeInfo:
            // informs the driver wether the asioCallbacks.bufferSwitchTimeInfo() callback
            // is supported.
            // For compatibility with ASIO 1.0 drivers the host application should always support
            // the "old" bufferSwitch method, too.
            ret = 1;
            break;
        case kAsioSupportsTimeCode:
            // informs the driver wether application is interested in time code info.
            // If an application does not need to know about time code, the driver has less work
            // to do.
            ret = 0;
            break;
        default:{
            break;}
    }
    return ret;
}
///////////////////////////////////////////////////////////////////////////////
ASIOTime* KbAsioDevice::asio_callback_bufferSwitchTimeInfo(ASIOTime* params,
                                                           long doubleBufferIndex,
                                                           ASIOBool directProcess)
{
    return s_pActive->bufferSwitchTimeInfo(params, doubleBufferIndex, directProcess);
}
///////////////////////////////////////////////////////////////////////////////
static void __fastcall FillSilence(BYTE *buf, int nSample, int nBps, DWORD dwFormatType)
{//
    if(dwFormatType == KPI_MEDIAINFO::FORMAT_DOP){
    //DoP の場合は marker を付加
        if(nBps == 32){//32bit
            BYTE marker = 0x05;
            for(int i = 0; i < nSample; i++){
                buf[0] = 0;
                buf[1] = 0x69;
                buf[2] = 0x69;
                buf[3] = marker;
                buf += 4;
                marker ^= 0xFF;
            }
            return;
        }
        else if(nBps == 24){//24bit(動作未確認)
            BYTE marker = 0x05;
            for(int i = 0; i < nSample; i++){
                buf[0] = 0x69;
                buf[1] = 0x69;
                buf[2] = marker;
                buf += 3;
                marker ^= 0xFF;
            }
            return;
        }
    }
    memset(buf, 0, nSample * nBps / 8);
}
ASIOTime* __fastcall KbAsioDevice::bufferSwitchTimeInfo(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess)
{
    Lock();
    const int nSizeSample = m_nAsioBufferSizeSample / m_nSamplePerBytes;
    BYTE *buf_l = (BYTE*)m_asioBufferInfo[0].buffers[doubleBufferIndex];//ASIO に実際に書き込むバッファ
    BYTE *buf_r = (BYTE*)m_asioBufferInfo[1].buffers[doubleBufferIndex];//ASIO に実際に書き込むバッファ
    if(m_bPaused){//一時停止 or 再生停止
        //実際には停止はせず、無音を再生し続ける
        //DoP/DSD 時の無音にも対応
        switch(m_asioChannelInfo.type ){
        case ASIOSTInt16LSB://16bit
        case ASIOSTInt16MSB://ASIOSTInt16MSB は Open で弾かれる
            memset( buf_l, 0, nSizeSample * 2);
            memset( buf_r, 0, nSizeSample * 2);break;
        case ASIOSTInt24LSB://24bit
        case ASIOSTInt24MSB://ASIOSTInt24MSB は Open で弾かれる
            FillSilence(buf_l, nSizeSample, 24, m_dwFormatType);
            FillSilence(buf_r, nSizeSample, 24, m_dwFormatType);break;
        case ASIOSTInt32LSB:  //32bit
        case ASIOSTInt32MSB:  //ASIOSTInt32LSB と ASIOSTFloat32LSB 以外は
        case ASIOSTInt32LSB16://Open で弾かれるので実際に呼ばれることはない
        case ASIOSTInt32LSB18://
        case ASIOSTInt32LSB20://
        case ASIOSTInt32LSB24://
        case ASIOSTInt32MSB16://
        case ASIOSTInt32MSB18://
        case ASIOSTInt32MSB20://
        case ASIOSTInt32MSB24://
        case ASIOSTFloat32LSB:
        case ASIOSTFloat32MSB://
            //ASIOSTInt32LSB 以外では FormatType は常に FORMAT_PCM
            FillSilence(buf_l, nSizeSample, 32, m_dwFormatType);
            FillSilence(buf_r, nSizeSample, 32, m_dwFormatType);break;
        case ASIOSTFloat64LSB: case ASIOSTFloat64MSB://64bit(ASIOSTFloat64MSB は Open で弾かれる)
            memset( buf_l, 0, nSizeSample * 8);
            memset( buf_r, 0, nSizeSample * 8);break;
        case ASIOSTDSDInt8LSB1:// DSD 1 bit data, 8 samples per byte. First sample in Least significant bit.
        case ASIOSTDSDInt8MSB1:// DSD 1 bit data, 8 samples per byte. First sample in Most significant bit.
            memset( buf_l, 0x69, nSizeSample/* / 8*/);
            memset( buf_r, 0x69, nSizeSample/* / 8*/);break;
        case ASIOSTDSDInt8NER8:// DSD 8 bit data, 1 sample per byte. No Endianness required.    
            memset( buf_l, 0x69, nSizeSample * 1);
            memset( buf_r, 0x69, nSizeSample * 1);break;
        }
        //m_qwAsioPos = ASIO64toUINT64(params->timeInfo.samplePosition);
        //UINT qwAsioPos = ASIO64toUINT64(params->timeInfo.samplePosition);
        m_qwAsioPos += nSizeSample;//常に qwAsipPos より nSizeSample だけ大きな値
        m_i64OffsetPos -= nSizeSample;
        m_qwAsioTimeNs = (UINT64)timeGetTime() * 1000000;
        if(m_bPostOutput){
            outputReady();
        }
        Unlock();
        return NULL;
    }
    BYTE *pOutputFinal;
    //リングバッファから直接読み取る(先頭と末尾をまたがっていない場合のみ読み取り可能)
    int nWriteSizeSample;
    switch(m_asioChannelInfo.type ){
    case ASIOSTDSDInt8MSB1:// DSD 1 bit data, 8 samples per byte. First sample in Most significant bit.
    case ASIOSTDSDInt8LSB1:// DSD 1 bit data, 8 samples per byte. First sample in Least significant bit.
    {
        nWriteSizeSample = m_RingBuffer.ReadDirect((BYTE**)&pOutputFinal, (m_nAsioBufferSizeSample/8) * m_nChannels) / m_nChannels;// * 8;
        if(!pOutputFinal){//読み取れなかった
            //作業用バッファにリングバッファからコピー
            nWriteSizeSample = m_RingBuffer.Read(m_pOutputWork, (m_nAsioBufferSizeSample/8) * m_nChannels) / m_nChannels;// * 8;
            pOutputFinal = m_pOutputWork;//リングバッファからコピーする
        }
        break;
    }//ASIOSTDSDInt8MSB1: / ASIOSTDSDInt8LSB1:
    default:{
        nWriteSizeSample = m_RingBuffer.ReadDirect((BYTE**)&pOutputFinal, m_nAsioBufferSizeSample * m_nOutputBlockAlign) / m_nOutputBlockAlign;
        if(!pOutputFinal){//読み取れなかった
            //作業用バッファにリングバッファからコピー
            nWriteSizeSample = m_RingBuffer.Read(m_pOutputWork, m_nAsioBufferSizeSample * m_nOutputBlockAlign) / m_nOutputBlockAlign;
            pOutputFinal = m_pOutputWork;//リングバッファからコピーする
        }
        break;
    }//default
    }//switch
    
    int i;
    switch(m_asioChannelInfo.type ){
    case ASIOSTInt16LSB:
    {//16bit(動作未確認)
        short*  dst_l = (short*)buf_l;
        short*  dst_r = (short*)buf_r;
        short*  src   = (short*)pOutputFinal;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src++);
                *(dst_r++) = *(src++);
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src);
                *(dst_r++) = *(src++);
            }
        }
        for(; i < nSizeSample; i++){
            *(dst_l++) = 0;
            *(dst_r++) = 0;
        }
        break;
    }
    case ASIOSTInt24LSB:
    {//24bit(動作未確認)
        //static const SAMPLE24_MONO s24_zero = {0};
        SAMPLE24_MONO*  dst_l = (SAMPLE24_MONO*)buf_l;
        SAMPLE24_MONO*  dst_r = (SAMPLE24_MONO*)buf_r;
        SAMPLE24_MONO*  src   = (SAMPLE24_MONO*)pOutputFinal;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src++);
                *(dst_r++) = *(src++);
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src);
                *(dst_r++) = *(src++);
            }
        }
        if(i < nSizeSample){
        //処理が間に合っていない場合、足りない分を無音で埋める(DoP 対応)
            FillSilence((BYTE*)dst_l, nSizeSample - i, 24, m_dwFormatType);
            FillSilence((BYTE*)dst_r, nSizeSample - i, 24, m_dwFormatType);
        }
        //for(; i < nSizeSample; i++){
        //    *(dst_l++) = s24_zero;
        //    *(dst_r++) = s24_zero;
        //}
        break;
    }
    case ASIOSTInt32LSB:
    {//32bit(HP-A8)
        int*    dst_l = (int*)buf_l;
        int*    dst_r = (int*)buf_r;
        int*    src   = (int*)pOutputFinal;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src++);
                *(dst_r++) = *(src++);
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src);
                *(dst_r++) = *(src++);
            }
        }
        if(i < nSizeSample){
        //処理が間に合っていない場合、足りない分を無音で埋める(DoP 対応)
            FillSilence((BYTE*)dst_l, nSizeSample - i, 32, m_dwFormatType);
            FillSilence((BYTE*)dst_r, nSizeSample - i, 32, m_dwFormatType);
        }
        break;
    }
    case ASIOSTFloat32LSB:
    {//32bit float(動作未確認)
        float*  dst_l = (float*)buf_l;
        float*  dst_r = (float*)buf_r;
        float*  src   = (float*)pOutputFinal;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src++);
                *(dst_r++) = *(src++);
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src);
                *(dst_r++) = *(src++);
            }
        }
        for(; i < nSizeSample; i++){
            *(dst_l++) = 0;
            *(dst_r++) = 0;
        }
        break;
    }
    case ASIOSTFloat64LSB:
    {//64bit float(動作未確認)
        double* dst_l = (double*)buf_l;
        double* dst_r = (double*)buf_r;
        double* src   = (double*)pOutputFinal;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src++);
                *(dst_r++) = *(src++);
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src);
                *(dst_r++) = *(src++);
            }
        }
        for(; i < nSizeSample; i++){
            *(dst_l++) = 0;
            *(dst_r++) = 0;
        }
        break;
    }
    case ASIOSTInt32LSB16://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32LSB18://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32LSB20://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32LSB24://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32MSB:  //Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32MSB16://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32MSB18://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32MSB20://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTInt32MSB24://Open で弾かれるので実際に呼ばれることはない
    case ASIOSTFloat32MSB://Open で弾かれるので実際に呼ばれることはない
        memset( buf_l, 0, nWriteSizeSample * 4 );
        memset( buf_r, 0, nWriteSizeSample * 4 );
        break;
    case ASIOSTFloat64MSB://Open で弾かれるので実際に呼ばれることはない
        memset( buf_l, 0, nWriteSizeSample * 8 );
        memset( buf_r, 0, nWriteSizeSample * 8 );
        break;
    case ASIOSTInt16MSB:  //Open で弾かれるので実際に呼ばれることはない
        memset( buf_l, 0, nWriteSizeSample * 2 );
        memset( buf_r, 0, nWriteSizeSample * 2 );
        break;
    case ASIOSTInt24MSB:  //Open で弾かれるので実際に呼ばれることはない
        memset( buf_l, 0, nWriteSizeSample * 3 );
        memset( buf_r, 0, nWriteSizeSample * 3 );
        break;          
    case ASIOSTDSDInt8MSB1:// DSD 1 bit data, 8 samples per byte. First sample in Most significant bit.
    {//DSD(HP-A8)
        BYTE*    dst_l = (BYTE*)buf_l;
        BYTE*    dst_r = (BYTE*)buf_r;
        BYTE*    src   = (BYTE*)pOutputFinal;
        //break;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src++);
                *(dst_r++) = *(src++);
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = *(src);
                *(dst_r++) = *(src++);
            }
        }
        for(; i < nSizeSample; i++){
            *(dst_l++) = 0x69;
            *(dst_r++) = 0x69;
        }
        break;
    }
    case ASIOSTDSDInt8LSB1:// DSD 1 bit data, 8 samples per byte. First sample in Least significant bit.
    {//DSD(動作未確認)
        memset( buf_l, 0x69, nSizeSample);
        memset( buf_r, 0x69, nSizeSample);
#if 0
        //DSD2DoP::Render の lsbf==TRUE に相当？
        //ここでの無音も 0x69 で正しいなら、
        //reverse(0x69) != 0x69 なので多分間違っている
        BYTE*    dst_l = (BYTE*)buf_l;
        BYTE*    dst_r = (BYTE*)buf_r;
        BYTE*    src   = (BYTE*)pOutputFinal;
        //break;
        if(m_nChannels == 2){
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = reverse(*(src++));
                *(dst_r++) = reverse(*(src++));
            }
        }
        else{
            for(i = 0; i < nWriteSizeSample; i++ ){
                *(dst_l++) = reverse(*(src));
                *(dst_r++) = reverse(*(src++));
            }
        }
        for(; i < nSizeSample; i++){
            *(dst_l++) = 0x69;
            *(dst_r++) = 0x69;
        }
#endif
        break;
    }
    case ASIOSTDSDInt8NER8:// DSD 8 bit data, 1 sample per byte. No Endianness required.    
    {//DSD(動作未確認)
     //どうすればよいか分からない
        memset( buf_l, 0x69, nWriteSizeSample * 1);
        memset( buf_r, 0x69, nWriteSizeSample * 1);
        break;
    }
    }//switch
    //Realtek ASIO だと timeInfo.samplePosition/systemTime ともに常に0が入っている
    //ドライバ更新前はそんなことなかったのだが…
    //timeInfo.samplePosition/systemTime が当てにならないので参照するのをやめる
    //
    //UINT qwAsioPos = ASIO64toUINT64(params->timeInfo.samplePosition);//初回は0
    m_qwAsioPos += nSizeSample;//qwAsioPos より nSizeSample だけ大きな値
    if(nWriteSizeSample < nSizeSample){
        //処理が間に合わない等の理由で音が途切れた場合、途切れた部分は無音が再生される
        //再生された無音部分は本来の位置からのずれとなるので、それを補正する
        //(演奏時間の進み方が遅くなるが位置は本来の位置となる)
        if(!m_bFinished){//演奏がまだ終了していない(補正が必要)
            m_i64OffsetPos -= (nSizeSample-nWriteSizeSample);
        }
        //else{//演奏終了
              //処理が間に合わないのではなく Output が呼ばれないことによるものなので補正不要
        //}
    }
    if(nWriteSizeSample){
        //UINT64 qwAsioTimeNs = ASIO64toUINT64(params->timeInfo.systemTime);//ほぼ timeGetTime() * 1000000と同じ値
        m_qwAsioTimeNs = (UINT64)timeGetTime() * 1000000;
    }
    // ASIOOutputReady()をサポートしている場合、毎回ASIOOutputReady()を呼び出す.
    if(m_bPostOutput){
        outputReady();
    }
    int nRemain = m_RingBuffer.GetRemain();
    if(!m_bFinished){
        if(nRemain >= m_nBufferSizeSample * m_nOutputBlockAlign / m_nSamplePerBytes){
            SetEvent(m_hEventWritten);
        }
    }
    else if(nRemain == m_RingBuffer.GetSize()){
        //既に終了している場合はバッファが空になるまで待つ
        SetEvent(m_hEventWritten);
    }
    Unlock();

    return NULL;
}
////////////////////////////////////////////////////////////////////////////////////////////
void KbAsioDevice::asio_callback_bufferSwitch(long doubleBufferIndex, ASIOBool directProcess)
{
    s_pActive->bufferSwitch(doubleBufferIndex, directProcess);
}
////////////////////////////////////////////////////////////////////////////////////////////
void KbAsioDevice::bufferSwitch(long doubleBufferIndex, ASIOBool directProcess)
{
    ASIOTime timeInfo = {0};
    Lock();
    // get the time stamp of the buffer, not necessary if no
    // synchronization to other media is required
    if(m_pAsio->getSamplePosition(&timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime) == ASE_OK){
        timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid;
    }
    bufferSwitchTimeInfo (&timeInfo, doubleBufferIndex, directProcess);
    Unlock();
    return;
}
////////////////////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioDevice::ControlPanel(const wchar_t *cszDeviceName, const wchar_t *cszId)
{
    if(!cszDeviceName[0])return FALSE;
    if(s_pActive && s_pActive->m_pAsio &&
       wcscmp(cszDeviceName, s_pActive->m_szDeviceName) == 0 &&
       wcscmp(cszId, s_pActive->m_szDeviceId) == 0){
        s_pActive->SendCommand(CMD_ASIO_CONTROLPANEL, 0, 0);
        return TRUE;
    }
    KbAsioDevice *dev = new KbAsioDevice;
    if(!dev->CreateAsio(cszDeviceName, cszId)){
        dev->Release();
        return FALSE;
    }
    dev->SendCommand(CMD_ASIO_CONTROLPANEL, 0, 0);
    dev->Release();
    return TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioDevice::Open(const wchar_t *cszDeviceName,
                                   const wchar_t *cszId,
                                   const KPI_DEVICE_FORMAT *cpFormat,
                                   int nBufferSizeSample,
                                   IKpiConfig *pConfig)
{
    if(s_pActive != this){//複数インスタンス非対応
        return FALSE;
    }
    if(m_bControlPanel)return FALSE;//controlPanel() 動作中
    CreateAsioThread();

    OPEN_PARAMS param;
    param.cszDeviceName = cszDeviceName;
    param.cszId = cszId;
    param.cpFormat = cpFormat;
    param.nBufferSizeSample = nBufferSizeSample;
    param.pConfig = pConfig;
    return SendCommand(CMD_ASIO_OPEN, (WPARAM)&param, 0);
}
////////////////////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioDevice::Open(OPEN_PARAMS *pParam)
{
    const wchar_t* cszDeviceName = pParam->cszDeviceName;
    const wchar_t* cszId = pParam->cszId;
    const KPI_DEVICE_FORMAT* cpFormat = pParam->cpFormat;
    int nBufferSizeSample = pParam->nBufferSizeSample;
    IKpiConfig* pConfig = pParam->pConfig;

    Close();
    int nSampleRate = cpFormat->dwSampleRate;
    const int nChannels = cpFormat->dwChannels;
    const int nBps = cpFormat->nBitsPerSample;
    const DWORD dwFormatType = cpFormat->dwFormatType;
    int nSamplePerBytes = 1;
    int nDsdMode = 0;
    if (pConfig) {
        KbAsioConfig cfg(pConfig, cszDeviceName, cszId);
        m_dwSilenceMS = cfg.GetSilence();
        nDsdMode = cfg.GetDsdMode();
    }
    if (dwFormatType == KPI_MEDIAINFO::FORMAT_PCM) {
        if (nBps != 8 &&
            nBps != 16 &&
            nBps != 24 &&
            nBps != 32 &&
            nBps != -32 &&
            nBps != -64) {
            return FALSE;
        }
    }
    else if (dwFormatType == KPI_MEDIAINFO::FORMAT_DOP) {
        if (nBps != 24 &&
            nBps != 32 &&
            nBps != -64) {
            return FALSE;
        }
    }
    else {//unsupport format type
        return FALSE;
    }

    if (nChannels != 1 && nChannels != 2) {
        return FALSE;
    }

    KbAsioEnumerator asioEnumerator;
    if (!OpenDriver(cszDeviceName, cszId)) {
        return FALSE;
    }
    do{ //Realtek ASIO 対策
        //ドライバ更新後も Realtek ASIO は相変わらず不安定
        //ドライバ更新前は1回目の createBuffers に失敗することがあるので createBuffers を2回試していた(Realtek ASIO のバグ？)
        //ドライバ更新後は失敗しなくなったが他にも動作が怪しいので対策を辞め、
        //仮に2回目で成功するのだとしても1回目の失敗で諦めることにした
        if (nDsdMode == 1 && dwFormatType == KPI_MEDIAINFO::FORMAT_DOP) {
            //DSD Native
            ASIOIoFormat NeedThis = { kASIODSDFormat };
            if (m_pAsio->future(kAsioCanDoIoFormat, &NeedThis) != ASE_SUCCESS) {
                m_pAsio->Release();m_pAsio = NULL;
                return FALSE;
            }
            if (m_pAsio->future(kAsioSetIoFormat, &NeedThis) != ASE_SUCCESS) {
                m_pAsio->Release(); m_pAsio = NULL;
                return FALSE;
            }
            nSampleRate *= 16;
            m_bDopToDsd = true;
        }
        //作成可能なバッファサイズを取得する.
        long minimum = 0;
        long maximum = 0;
        long preferred = 0;
        long granularity = 0;
        if (m_pAsio->getBufferSize(&minimum, &maximum, &preferred, &granularity) != ASE_OK) {
            m_pAsio->Release(); m_pAsio = NULL;
            return FALSE;
        }
        int i;
        for (i = 0; i < 2; i++) {
            m_asioBufferInfo[i].isInput = ASIOFalse;
            m_asioBufferInfo[i].channelNum = i;
            m_asioBufferInfo[i].buffers[0] = NULL;
            m_asioBufferInfo[i].buffers[1] = NULL;
        }
        m_nAsioBufferSizeSample = preferred;
        ASIOError err = m_pAsio->setSampleRate(nSampleRate);//createBuffers の前に呼ぶ
        //HP-A8 の場合、createBuffers の前に setSampleRate を呼び出さないと再生周波数が
        //切り替わったときに start() に失敗 & DSD Native のときに createBuffers に失敗する
        //Realtek ASIO の場合は常に(nSampleRate==48000でも) ASE_NotPresent を返すようになった
        //戻り値を無視して createBuffers すると 48000Hz で再生されるらしい
        //ドライバ更新する度に挙動が変わって(良い方向に変わることはない)訳が分からない
        //foobar2k v2.1.3(x64) では正しく再生出来るので対策がある筈だがどうしても分からない
        if (err == ASE_OK && //これをコメントアウトすれば Realtek ASIO でも再生可能だが48000Hzで再生される
            m_pAsio->createBuffers(m_asioBufferInfo,
                2, //モノラルでもステレオ扱い(左右同じ音を出す)
                preferred,
                &m_asioCallbacks) == ASE_OK) {
            break;//成功
        }
        m_pAsio->Release(); m_pAsio = NULL;//失敗
        return FALSE;
    }while(0);
    ZeroMemory(&m_asioChannelInfo, sizeof(m_asioChannelInfo));
    m_pAsio->getChannelInfo(&m_asioChannelInfo);
    BOOL bOk = TRUE;
    int nOutputBps = 0;
    switch (m_asioChannelInfo.type) {
    case ASIOSTInt16LSB:
    {//16bit(動作未確認)
        if (nBps != -64 && nBps != 16) {
            bOk = FALSE;
        }
        else {
            nOutputBps = 16;
        }
        break;
    }
    case ASIOSTInt24LSB:
    {//24bit(Realtek ASIO で確認)
        if (nBps != -64 && nBps != 24) {
            bOk = FALSE;
        }
        else {
            nOutputBps = 24;
        }
        break;
    }
    case ASIOSTInt32LSB:
    {//32bit(HP-A8 で確認)
        if (nBps != -64 && nBps != 32) {
            bOk = FALSE;
        }
        else {
            nOutputBps = 32;
        }
        break;
    }
    case ASIOSTFloat32LSB:
    {//32bit float(動作未確認)
        if (nBps != -64 && nBps != -32) {
            bOk = FALSE;
        }
        else {
            nOutputBps = -32;
        }
        break;
    }
    case ASIOSTFloat64LSB:
    {//64bit float(動作未確認)
        if (nBps != -64) {
            bOk = FALSE;
        }
        else {
            nOutputBps = -64;
        }
        break;
    }
    case ASIOSTDSDInt8LSB1:// DSD 1 bit data, 8 samples per byte. First sample in Least significant bit.
    {//動作確認出来るデバイスがない
        //nOutputBps = 8;
        //nSamplePerBytes = 8;
        //bOk = TRUE;
        //nBufferSizeSample *= 16;//DoP の場合、1サンプルあたり有効なデータは 16bit なので DSD に換算すると
        //                        //サンプル数は 16 倍
        bOk = FALSE;
        break;
    }
    case ASIOSTDSDInt8MSB1:// DSD 1 bit data, 8 samples per byte. First sample in Most significant bit.
    {//HP-A8
        nOutputBps = 8;
        nSamplePerBytes = 8;
        nBufferSizeSample *= 16;//16;//DoP の場合、1サンプルあたり有効なデータは 16bit なので DSD に換算すると
        //サンプル数は 16 倍
        bOk = TRUE;
        break;
    }
    case ASIOSTDSDInt8NER8:// DSD 8 bit data, 1 sample per byte. No Endianness required.    default:
    {//動作確認出来るデバイスがない
        //nOutputBps = 8;
        //nSamplePerBytes = 1;//?
        //bOk = TRUE;
        //nBufferSizeSample *= 2;?//DoP の場合、1サンプルあたり有効なデータは 16bit なので DSD に換算すると
        //                        //サンプル数は 2 倍?
        bOk = FALSE;
        break;
    }
    default:
    {
        bOk = FALSE;
        break;
    }
    }//switch
    if (bOk) {
        //DoP の場合は 24bit/32bit 以外は受け付けない
        if (dwFormatType == KPI_MEDIAINFO::FORMAT_DOP) {
            if (!m_bDopToDsd) {
                if (nOutputBps != 24 && nOutputBps != 32) {
                    bOk = FALSE;
                }
            }
            else {
                if (nOutputBps != 8) {
                    bOk = FALSE;
                }
            }
        }
    }
    if (!bOk) {
        m_pAsio->disposeBuffers();
        m_pAsio->Release(); m_pAsio = NULL;
        return FALSE;
    }
    //成功
    long lInputLatency = 0;
    m_pAsio->getLatencies(&lInputLatency, &m_lLatency);
    m_bPostOutput = m_pAsio->outputReady() == ASE_OK;
    m_nSampleRate = nSampleRate;
    m_nChannels = nChannels;
    m_nInputBps = nBps;
    m_nOutputBps = nOutputBps;
    m_nOutputBlockAlign = nChannels * abs(nOutputBps) / 8;
    m_nBufferSizeSample = nBufferSizeSample;
    m_nInputBlockAlign = nChannels * abs(nBps) / 8;
    m_nSamplePerBytes = nSamplePerBytes;
    int nOutputWorkSizeSample = m_nBufferSizeSample > m_nAsioBufferSizeSample ? m_nBufferSizeSample : m_nAsioBufferSizeSample;//大きい方
    m_pOutputWork = new BYTE[nOutputWorkSizeSample * m_nOutputBlockAlign / nSamplePerBytes];

    //リングバッファに格納するデータは最終出力段の量子化ビット数
    m_RingBuffer.SetSize((m_nBufferSizeSample + m_nAsioBufferSizeSample) * 2 * m_nOutputBlockAlign / nSamplePerBytes);
    if (cszDeviceName != m_szDeviceName) {
        wcsncpy_s(m_szDeviceName, cszDeviceName, _TRUNCATE);
    }
    if (cszId != m_szDeviceId) {
        wcsncpy_s(m_szDeviceId, cszId, _TRUNCATE);
    }
    m_dwFormatType = dwFormatType;
    m_hEventWritten = CreateEvent(NULL, TRUE, TRUE, NULL);
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioDevice::CreateAsio(const wchar_t* cszDeviceName, const wchar_t* cszId)
{
    CreateAsioThread();
    return SendCommand(CMD_ASIO_OPENDRIVER, (WPARAM)cszDeviceName, (LPARAM)cszId);
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbAsioDevice::OpenDriver(const wchar_t* cszDeviceName, const wchar_t* cszId)
{
    KbAsioEnumerator asioEnumerator;
    IASIO* pAsio = NULL;
    if (!asioEnumerator.OpenDriver(cszDeviceName, cszId, &pAsio)) {
        return FALSE;
    }
    if (!pAsio->init(m_hWnd)) {
        pAsio->Release();
        return FALSE;
    }
    m_pAsio = pAsio;
    return TRUE;
}
/*
///////////////////////////////////////////////////////////////////////////////
ASIOError __fastcall KbAsioDevice::start(void)
{
    return SendCommand(CMD_ASIO_START, 0, 0);
}
///////////////////////////////////////////////////////////////////////////////
ASIOError __fastcall KbAsioDevice::outputReady(void)
{
    return SendCommand(CMD_ASIO_OUTPUTREADY, 0, 0);
}
*/
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbAsioDevice::Close(void)
{
    if(m_pAsio){
        m_pAsio->stop();
        m_pAsio->disposeBuffers();
        m_pAsio->Release();
        m_pAsio = NULL;
    }
    delete[] m_pOutputWork; m_pOutputWork = NULL;
    m_RingBuffer.Reset();
    if(m_hEventWritten){//Open に成功したときだけ作成される
        CloseHandle(m_hEventWritten);
        m_hEventWritten = NULL;
    }
}
///////////////////////////////////////////////////////////////////////////////
UINT __fastcall KbAsioDevice::ThreadProcInternal(void)
{//IASIO のメソッドは基本的にこのスレッド内で呼び出す
 //start() と outputReady() は例外
 //OPEN が終わったら QUIT が呼ばれるまでほぼ寝てるだけのスレッド
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    //CoInitializeEx(NULL, COINIT_MULTITHREADED);//動作しない
    HANDLE hHandles[] = {m_hEventCmd};
    m_hWnd = kbAllocateHWnd(DefWindowProc);
    //ウィンドウの作成完了待ちは不要
    bool bQuit = false;
    while (!bQuit) {
        #if 0
        WaitForSingleObject(m_hEventCmd, INFINITE);
        #else //念の為、作成したウィンドウのメッセージも処理する
        DWORD dwWait = MsgWaitForMultipleObjects(1, hHandles, FALSE, INFINITE, QS_ALLINPUT);
        if(dwWait == WAIT_OBJECT_0 + 1){
        //Realtek ASIO の controlPanel() から戻った時にここが実行される
        //HP-A8 と ASIO4ALL では確認出来ないが MessageBox に m_hWnd を渡すと閉じたときにここが実行される
        //単に WaitForSingleObject にしただけでも問題なく動作する模様
            MSG msg;
            while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            continue;
        }
        #endif
        switch (m_Command.nCommand) {
        case CMD_ASIO_OPEN:
            m_Command.lResult = Open((OPEN_PARAMS*)m_Command.wParam);
            break;
        case CMD_ASIO_OPENDRIVER:
            m_Command.lResult = OpenDriver((const wchar_t*)m_Command.wParam, (const wchar_t*)m_Command.lParam);
            break;
        /*
        case CMD_ASIO_START:
            m_Command.lResult = m_pAsio->start();
            break;
        case CMD_ASIO_OUTPUTREADY:
            m_Command.lResult = m_pAsio->outputReady();
            break;
        */
        case CMD_ASIO_CONTROLPANEL:{
        //HP-A8/ASIO4ALL は controlPanel() 呼び出し後直ちに戻る。呼び出し元とは異なるプロセスからダイアログを表示するらしい
        //Realtek ASIO 32bit だと再生中に controlPanel を呼ぶとダイアログを閉じたところで落ちる。64bit 版では落ちない
        //foobar の ASIO プラグインは再生中に controlPanel を呼ばないようなので本プラグインでもそうする
            if(m_nOutputBps){//Open に成功した後(=再生中orもうすぐ再生する)
                MessageBox(m_hWnd, L"再生中はドライバ設定画面を表示出来ません。", L"kbasio.kpi", MB_OK);
                m_Command.lResult = ASE_NotPresent;
            }
            else{
                m_bControlPanel = true;//controlPanel() 動作中(デバイスを開けなくする)
                m_Command.lResult = m_pAsio->controlPanel();
                m_bControlPanel = false;//動作解除
            }
            break;
        }
        case CMD_ASIO_QUIT: {
            Close();
            bQuit = true;
            break;
        }
        default:
            break;
        }
        SetEvent(m_hEventDone);//コマンド実行完了
    }
    kbDeallocateHWnd(m_hWnd); m_hWnd = NULL;
    CoUninitialize();
    return 0;
}
///////////////////////////////////////////////////////////////////////////////
void __fastcall KbAsioDevice::CreateAsioThread(void)
{//IASIO へのアクセスは基本的にここで作成したスレッド内からのみ行う
 //start() と outputReady() は例外
    if(m_hThread)return;
    unsigned thread_id;
    m_hEventCmd = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hEventDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, this, 0, &thread_id);
}
///////////////////////////////////////////////////////////////////////////////
LRESULT __fastcall KbAsioDevice::SendCommand(int nCommand, WPARAM wParam, LPARAM lParam)
{
    m_Command.nCommand = nCommand;
    m_Command.wParam = wParam;
    m_Command.lParam = lParam;
    m_Command.lResult = 0;
    SetEvent(m_hEventCmd);
    WaitForSingleObject(m_hEventDone, INFINITE);
    return m_Command.lResult;
}
///////////////////////////////////////////////////////////////////////////////
