#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
///#include <math.h>
#include <mbstring.h>
#include <process.h>

#include "../../../kpi_common/ringbuf.h"
#include "../../../kpi_common/apihijack.h"
#include "umsdk/umut.h"
#include "kbum_decoder.h"

static const DWORD UNIT_RENDER = 1024;
static const DWORD BLOCK_ALIGN = 2 * 2;
extern HINSTANCE g_hKpi;
static HINSTANCE g_hUmut = NULL;
static HANDLE    g_hEvent = NULL;
static CRITICAL_SECTION g_cs;

static inline void um_Lock(void)
{
    EnterCriticalSection(&g_cs);
}
static inline void um_Unlock(void)
{
    LeaveCriticalSection(&g_cs);
}

static char* __fastcall GetUmPcmPath(char *pszPath, int nSize, IKpiConfig *pConfig)
{
    wchar_t szPcmPath[MAX_PATH];
    pConfig->GetStr(SEC_GENERAL, KEY_PCMPATH, szPcmPath, sizeof(szPcmPath), L"");
    wchar_t *delimiter = wcsrchr(szPcmPath, L'\\');
    if(delimiter && !delimiter[1]){//末尾の \ を除去
        delimiter[0] = 0;
    }
    WideCharToMultiByte(CP_ACP, 0, szPcmPath, -1, pszPath, nSize-1, NULL, NULL);
    pszPath[nSize-1] = 0;

    return pszPath;
}

typedef UTRET (WINAPI *pfnUmut_Init)(UMUTDEVICE* pDevs);
typedef UTRET (WINAPI *pfnUmut_End)(void);//
typedef UTRET (WINAPI *pfnUmut_GetVersion)(UMUTVERSIONINFO* pVInfo);//
typedef UTRET (WINAPI *pfnUmut_ConfigDevice)(const UMUTOUTCFG* pConfig);//
typedef UTRET (WINAPI *pfnUmut_Start)(HWND hwnd);//
typedef UTRET (WINAPI *pfnUmut_Stop)(void);//
typedef UTRET (WINAPI *pfnUmut_Pause)(void);//
typedef UTRET (WINAPI *pfnUmut_Resume)(void);//
typedef UTRET (WINAPI *pfnUmut_EnableMMX)(void);//
typedef UTRET (WINAPI *pfnUmut_DisableMMX)(void);//
typedef UTRET (WINAPI *pfnUmut_Mod_Load)(const UMUTSRC* source, int nPcmDirs, const char* aszPcmdirs[]);
typedef UTRET (WINAPI *pfnUmut_Mod_Play)(void);//
typedef UTRET (WINAPI *pfnUmut_Mod_Stop)(void);//
typedef UTRET (WINAPI *pfnUmut_Mod_Pause)(void);//
typedef UTRET (WINAPI *pfnUmut_Mod_Resume)(void);//
typedef UTRET (WINAPI *pfnUmut_Mod_Volume)(int volume);
typedef UTRET (WINAPI *pfnUmut_Mod_Fade)(int startVolume, int endVolume, long time_sec);
typedef UTRET (WINAPI *pfnUmut_Mod_Stat)(UMUTMODSTAT* pModstat);
typedef UTRET (WINAPI *pfnUmut_Mod_FlushPcmCache)(int fInvalidate);
typedef UTRET (WINAPI *pfnUmut_Mod_Release)(void);
pfnUmut_Init         Umut_Init = NULL;
pfnUmut_End          Umut_End = NULL;
pfnUmut_GetVersion   Umut_GetVersion = NULL;
pfnUmut_ConfigDevice Umut_ConfigDevice = NULL;
pfnUmut_Start        Umut_Start = NULL;
pfnUmut_Stop         Umut_Stop = NULL;
pfnUmut_Pause        Umut_Pause = NULL;
pfnUmut_Resume       Umut_Resume = NULL;
pfnUmut_EnableMMX    Umut_EnableMMX = NULL;
pfnUmut_DisableMMX   Umut_DisableMMX = NULL;
pfnUmut_Mod_Load     Umut_Mod_Load = NULL;
pfnUmut_Mod_Play     Umut_Mod_Play = NULL;
pfnUmut_Mod_Stop     Umut_Mod_Stop = NULL;
pfnUmut_Mod_Pause    Umut_Mod_Pause = NULL;
pfnUmut_Mod_Resume   Umut_Mod_Resume = NULL;
pfnUmut_Mod_Volume   Umut_Mod_Volume = NULL;
pfnUmut_Mod_Fade     Umut_Mod_Fade = NULL;
pfnUmut_Mod_Stat     Umut_Mod_Stat = NULL;
pfnUmut_Mod_FlushPcmCache Umut_Mod_FlushPcmCache = NULL;
pfnUmut_Mod_Release  Umut_Mod_Release = NULL;

class KbWaveOut
{
private:
    KbRingBuffer m_rb;
    WAVEHDR    **m_ppWaveHdrs;
    int          m_nWaveHdrsCount;
    int          m_nWaveHdrsMax;
    BOOL         m_bOpen;
public:
    HANDLE       m_hEvent;
    void      __fastcall AddWavHdr(WAVEHDR *pWaveHdr);
    WAVEHDR* __fastcall RemoveWavHdr(void);
    int   __fastcall GetNumWaveHdrs(void){return m_nWaveHdrsCount;}
    void  __fastcall Close(void){m_bOpen = FALSE;}
    BOOL  __fastcall IsOpen(void){return m_bOpen;}
    void  __fastcall Write(const BYTE* buf, DWORD dwSize){m_rb.Write(buf, dwSize);}
    DWORD __fastcall Read(BYTE* buf, DWORD dwSize){return m_rb.Read(buf, dwSize);}
    DWORD __fastcall GetWritten(void){return m_rb.GetWritten();}
    void  __fastcall Reset(void){m_rb.Reset();}
    KbWaveOut(void);
    ~KbWaveOut(void);
};

static KbWaveOut *g_wo = NULL;

KbWaveOut::KbWaveOut(void)
    :m_rb()
{
    m_hEvent = NULL;
    m_ppWaveHdrs = NULL;
    m_nWaveHdrsCount = 0;
    m_nWaveHdrsMax = 0;
    m_bOpen = TRUE;
    m_rb.SetSize(128*1024);
}
KbWaveOut::~KbWaveOut(void)
{
    free(m_ppWaveHdrs);
}
void __fastcall KbWaveOut::AddWavHdr(WAVEHDR *pWaveHdr)
{
    um_Lock();
    m_nWaveHdrsCount++;
    if(m_nWaveHdrsCount > m_nWaveHdrsMax){
        m_nWaveHdrsMax = m_nWaveHdrsCount;
        if(m_nWaveHdrsMax < 4){
            m_nWaveHdrsMax = 4;
        }
        m_ppWaveHdrs = (WAVEHDR**)realloc(m_ppWaveHdrs, sizeof(WAVEHDR*)*m_nWaveHdrsMax);
    }
    m_ppWaveHdrs[m_nWaveHdrsCount-1] = pWaveHdr;
    um_Unlock();
}
WAVEHDR* __fastcall KbWaveOut::RemoveWavHdr(void)
{
    WAVEHDR* ret = NULL;
    um_Lock();
    if(m_nWaveHdrsCount > 0){
        ret = m_ppWaveHdrs[0];
        m_nWaveHdrsCount--;
        int i;
        for(i = 0; i < m_nWaveHdrsCount; i++){
            m_ppWaveHdrs[i] = m_ppWaveHdrs[i+1];
        }
    }
    um_Unlock();
    return ret;
}
//winmm.dll
static MMRESULT WINAPI hook_waveOutOpen(LPHWAVEOUT phwo,
                                        UINT uDeviceID,
                                        LPCWAVEFORMATEX pwfx,
                                        DWORD dwCallback,
                                        DWORD dwCallbackInstance,
                                        DWORD fdwOpen);
static MMRESULT WINAPI hook_waveOutClose(HWAVEOUT hwo);
static MMRESULT WINAPI hook_waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);
static MMRESULT WINAPI hook_waveOutReset(HWAVEOUT hwo);
static MMRESULT WINAPI hook_waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);
static MMRESULT WINAPI hook_waveOutUnprepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);
static MMRESULT WINAPI hook_waveOutPause(HWAVEOUT hwo);
static MMRESULT WINAPI hook_waveOutRestart(HWAVEOUT hwo);

static SDLLHook WINMMHook =
{
    "WINMM.DLL",
    {
        {"waveOutOpen",            hook_waveOutOpen},
        {"waveOutClose",           hook_waveOutClose},
        {"waveOutWrite",           hook_waveOutWrite},
        {"waveOutReset",           hook_waveOutReset},
        {"waveOutPrepareHeader",   hook_waveOutPrepareHeader},
        {"waveOutUnprepareHeader", hook_waveOutUnprepareHeader},
        {"waveOutPause",           hook_waveOutPause},
        {"waveOutRestart",         hook_waveOutRestart},
        {NULL, NULL}
    }
};

static bool __fastcall LoadUmutDll(HINSTANCE hKpi)
{
    if(g_hUmut)return true;
    wchar_t szDllPath[MAX_PATH];

    GetModuleFileNameW(hKpi, szDllPath, sizeof(szDllPath));
    wchar_t *delimiter = wcsrchr(szDllPath, L'\\');
    if(delimiter){
        delimiter[1] = 0;
    }
    wcsncat_s(szDllPath, L"umut.dll", _TRUNCATE);
    g_hUmut = LoadLibraryW(szDllPath);
    if(!g_hUmut)return false;
    Umut_Init = (pfnUmut_Init)GetProcAddress(g_hUmut,"umut_init");
    Umut_End = (pfnUmut_End)GetProcAddress(g_hUmut,"umut_end");
    Umut_GetVersion = (pfnUmut_GetVersion)GetProcAddress(g_hUmut,"umut_getVersion");
    Umut_ConfigDevice = (pfnUmut_ConfigDevice)GetProcAddress(g_hUmut,"umut_configDevice");
    Umut_Start = (pfnUmut_Start)GetProcAddress(g_hUmut,"umut_start");
    Umut_Stop = (pfnUmut_Stop)GetProcAddress(g_hUmut,"umut_stop");
    Umut_Pause = (pfnUmut_Pause)GetProcAddress(g_hUmut,"umut_pause");
    Umut_Resume = (pfnUmut_Resume)GetProcAddress(g_hUmut,"umut_resume");
    Umut_EnableMMX = (pfnUmut_EnableMMX)GetProcAddress(g_hUmut,"umut_enableMMX");
    Umut_DisableMMX = (pfnUmut_DisableMMX)GetProcAddress(g_hUmut, "umut_disableMMX");
    Umut_Mod_Load = (pfnUmut_Mod_Load)GetProcAddress(g_hUmut, "umut_mod_load");
    Umut_Mod_Play = (pfnUmut_Mod_Play)GetProcAddress(g_hUmut, "umut_mod_play");
    Umut_Mod_Stop = (pfnUmut_Mod_Stop)GetProcAddress(g_hUmut, "umut_mod_stop");
    Umut_Mod_Pause = (pfnUmut_Mod_Pause)GetProcAddress(g_hUmut, "umut_mod_pause");
    Umut_Mod_Resume = (pfnUmut_Mod_Resume)GetProcAddress(g_hUmut, "umut_mod_resume");
    Umut_Mod_Volume = (pfnUmut_Mod_Volume)GetProcAddress(g_hUmut, "umut_mod_volume");
    Umut_Mod_Fade = (pfnUmut_Mod_Fade)GetProcAddress(g_hUmut, "umut_mod_fade");
    Umut_Mod_Stat = (pfnUmut_Mod_Stat)GetProcAddress(g_hUmut, "umut_mod_stat");
    Umut_Mod_FlushPcmCache = (pfnUmut_Mod_FlushPcmCache)
                                GetProcAddress(g_hUmut, "umut_mod_flushPcmCache");
    Umut_Mod_Release = (pfnUmut_Mod_Release)GetProcAddress(g_hUmut, "umut_mod_release");
    if(!Umut_Init ||
       !Umut_End ||
       !Umut_GetVersion ||
       !Umut_ConfigDevice ||
       !Umut_Start ||
       !Umut_Stop ||
       !Umut_Pause ||
       !Umut_Resume ||
       !Umut_EnableMMX ||
       !Umut_DisableMMX ||
       !Umut_Mod_Load ||
       !Umut_Mod_Play ||
       !Umut_Mod_Stop ||
       !Umut_Mod_Pause ||
       !Umut_Mod_Resume ||
       !Umut_Mod_Volume ||
       !Umut_Mod_Fade ||
       !Umut_Mod_Stat ||
       !Umut_Mod_FlushPcmCache ||
       !Umut_Mod_Release
       ){
        Umut_Init = NULL;
        Umut_End = NULL;
        Umut_GetVersion = NULL;
        Umut_ConfigDevice = NULL;
        Umut_Start = NULL;
        Umut_Stop = NULL;
        Umut_Pause = NULL;
        Umut_Resume = NULL;
        Umut_EnableMMX = NULL;
        Umut_DisableMMX = NULL;
        Umut_Mod_Load = NULL;
        Umut_Mod_Play = NULL;
        Umut_Mod_Stop = NULL;
        Umut_Mod_Pause = NULL;
        Umut_Mod_Resume = NULL;
        Umut_Mod_Volume = NULL;
        Umut_Mod_Fade = NULL;
        Umut_Mod_Stat = NULL;
        Umut_Mod_FlushPcmCache = NULL;
        Umut_Mod_Release = NULL;
        FreeLibrary(g_hUmut);
        g_hUmut = NULL;
    }
    else{
        g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        HookAPICalls(&WINMMHook, (HMODULE)g_hUmut);
        UMUTDEVICE devinfo = {0};
        Umut_Init(&devinfo);
        Umut_EnableMMX();//VS2015 でビルドしたものは SSE 対応が前提(MMX も対応してることが前提)
    }
    return g_hUmut != NULL;
}
static void __fastcall FreeUmutDll(void)
{
    if(g_hUmut){
        Umut_End();
        FreeLibrary(g_hUmut);
        g_hUmut = NULL;
        CloseHandle(g_hEvent);
        g_hEvent = NULL;
        Umut_Init = NULL;
        Umut_End = NULL;
        Umut_GetVersion = NULL;
        Umut_ConfigDevice = NULL;
        Umut_Start = NULL;
        Umut_Stop = NULL;
        Umut_Pause = NULL;
        Umut_Resume = NULL;
        Umut_EnableMMX = NULL;
        Umut_DisableMMX = NULL;
        Umut_Mod_Load = NULL;
        Umut_Mod_Play = NULL;
        Umut_Mod_Stop = NULL;
        Umut_Mod_Pause = NULL;
        Umut_Mod_Resume = NULL;
        Umut_Mod_Volume = NULL;
        Umut_Mod_Fade = NULL;
        Umut_Mod_Stat = NULL;
        Umut_Mod_Release = NULL;
        Umut_Mod_FlushPcmCache = NULL;
    }
}
//winmm.dll
MMRESULT WINAPI hook_waveOutOpen(LPHWAVEOUT phwo,
                                 UINT uDeviceID,
                                 LPCWAVEFORMATEX pwfx,
                                 DWORD dwCallback,
                                 DWORD dwCallbackInstance,
                                 DWORD fdwOpen)
{
    if(phwo){
        *phwo = NULL;
        switch(fdwOpen){
            case CALLBACK_EVENT:{
                if(g_wo){
                    return MMSYSERR_ERROR;
                }
                KbWaveOut *wo = new KbWaveOut;
                wo->m_hEvent = (HANDLE)dwCallback;
                g_wo = wo;
                *phwo = (HWAVEOUT)wo;
                SetEvent(wo->m_hEvent);
                return MMSYSERR_NOERROR;
            }
            case CALLBACK_FUNCTION:{
                break;
            }
            case CALLBACK_THREAD:{
                break;
            }
            case CALLBACK_WINDOW:{
                break;
            }
        }
        return MMSYSERR_ERROR;
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutClose(HWAVEOUT hwo)
{
    KbWaveOut *wo = (KbWaveOut*)hwo;
    if(wo){
        wo->Close();
        SetEvent(wo->m_hEvent);
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)
{
    KbWaveOut *wo = (KbWaveOut*)hwo;
    if(wo){
        um_Lock();
        const BYTE* data = (const BYTE*)(pwh->lpData);
        DWORD dwSize = pwh->dwBufferLength;
        wo->Write(data, dwSize);
        wo->AddWavHdr(pwh);
        if(wo->GetWritten() >= UNIT_RENDER){
            SetEvent(g_hEvent);
        }
        um_Unlock();
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutReset(HWAVEOUT hwo)
{
    KbWaveOut *wo = (KbWaveOut*)hwo;
    if(wo){
        HANDLE hEvent = wo->m_hEvent;
        WAVEHDR* pwh = wo->RemoveWavHdr();
        while(hEvent && pwh){
            pwh->dwFlags |= WHDR_DONE;
            SetEvent(hEvent);
            pwh = wo->RemoveWavHdr();
        }
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)
{
    if(pwh){
        pwh->dwFlags |= WHDR_PREPARED;
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutUnprepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)
{
    if(pwh){
        pwh->dwFlags |= WHDR_DONE;
    }
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutPause(HWAVEOUT hwo)
{
    return MMSYSERR_NOERROR;
}
MMRESULT WINAPI hook_waveOutRestart(HWAVEOUT hwo)
{
    return MMSYSERR_NOERROR;
}

KbUmDecoder* KbUmDecoder::s_pActive = NULL;
BOOL KbUmDecoder::s_bInitialized = FALSE;

unsigned __stdcall KbUmDecoder::KbUmThreadProc(void *pParam)
{
    MSG msg;
    PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
    KbUmDecoder *pUm = (KbUmDecoder*)pParam;
    if(Umut_Mod_Play() != UMUT_OK){
        Umut_Mod_Stop();
        return 1;
    }
    SetEvent(pUm->m_hEvent);
    while(GetMessage(&msg, NULL, 0, 0) > 0){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    Umut_Mod_Stop();
    return 0;
}

KbUmDecoder::KbUmDecoder(IKpiConfig *pConfig)
{
    m_dwThreadId = 0;
    m_hThread = NULL;
    m_hEvent = NULL;
    kpi_InitMediaInfo(&m_MediaInfo);
    m_pFile = NULL;
    m_ppPcmFiles = NULL;
    m_pFolder = NULL;
    m_nPcmFiles = 0;
    m_pConfig = pConfig;
    pConfig->AddRef();
}
KbUmDecoder::~KbUmDecoder(void)
{
    Close();
    if(m_hEvent){
        CloseHandle(m_hEvent);
    }
    InterlockedCompareExchangePointer((void**)&s_pActive, NULL, this); 
}
void __fastcall KbUmDecoder::Terminate(void)
{
    if(m_hThread){
        PostThreadMessage(m_dwThreadId, WM_QUIT, 0, 0);
        WaitForSingleObject(m_hThread, INFINITE);
        CloseHandle(m_hThread);
        m_hThread = NULL;
        m_dwThreadId = 0;
    }
}
void __fastcall KbUmDecoder::Close(void)
{
    Terminate();
    if(m_pFile){
        m_pFile->Release();m_pFile = NULL;
    }
    if(m_pFolder){
        m_pFolder->Release();m_pFolder = NULL;
    }
    if(m_ppPcmFiles){
        for(int i = m_nPcmFiles - 1; i >= 0; i--){
            m_ppPcmFiles[i]->Release();
        }
        free(m_ppPcmFiles);m_ppPcmFiles = NULL;
    }
    if(m_pConfig){
        m_pConfig->Release();
        m_pConfig = NULL;
    }
    if(g_hUmut && s_pActive == this){
        Umut_Mod_Release();
        Umut_Mod_FlushPcmCache(true);
        Umut_Stop();
        if(g_wo && !g_wo->IsOpen()){
            delete g_wo;
            g_wo = NULL;
        }
    }
}
DWORD __fastcall KbUmDecoder::Open(const KPI_MEDIAINFO *cpRequest,
                                   IKpiFile   *pFile,
                                   IKpiFolder *pFolder)
{
    if(InterlockedCompareExchangePointer((void**)&s_pActive, this, NULL) != NULL){
        return 0;
    }
    if(!Initialize(g_hKpi)){
        return 0;
    }
    UMUTOUTCFG config = {0};// デバイスの使用方法(こちらがセットする)
    // デバイスの設定
    config.devicetype    = UMUT_DEVTYPE_WAVEOUT;  // waveoutを使用
    config.deviceno      = 0;                     // wave mapper
    config.format_device = UMUT_PCMFORMAT_44S;
    config.format_mod    = UMUT_PCMFORMAT_44S;
    config.format_eff    = UMUT_PCMFORMAT_44S;
    config.hokan_mod     = TRUE;
    config.hokan_eff     = TRUE;
    if(Umut_ConfigDevice( &config ) != UMUT_OK){
        return 0;
    }
    if(g_wo){
        g_wo->Reset();
    }
    if(Umut_Start(NULL) != UMUT_OK){//dsoundの場合はwindowハンドルを入れる
        Umut_Stop();
        Umut_Mod_FlushPcmCache(true);
        Umut_Mod_Release();
        return 0;
    }
    // UM音楽ファイルのロード
    char szUmPath0[MAX_PATH*2];//um と同じフォルダ
    char szUmPath1[MAX_PATH*2];//PCM 検索パス
    const char *cszFileNameA;
    {
        if(!pFile->GetRealFileA(&cszFileNameA)){
            Close();
            return 0;
        }
        _mbsncpy_s((unsigned char*)szUmPath0, _countof(szUmPath0), (unsigned char*)cszFileNameA, _TRUNCATE);
        char *delimiter = (char*)_mbsrchr((unsigned char*)szUmPath0, '\\');
        if(delimiter){
            delimiter[1] = 0;
        }
        if(strlen(szUmPath0) != 3){
            delimiter[0] = 0;
        }
    }
    GetUmPcmPath(szUmPath1, sizeof(szUmPath1), m_pConfig);
    const char* pcmdirs[] = {
        szUmPath0,
        szUmPath1,
        NULL
    };
    UMUTSRC src = {0};   // 演奏するUM音楽ファイルの情報(こちらがセットする)
    src.type = UMUT_SRCTYPE_FILE;
    src.arg  = (char*)cszFileNameA;
    GetPcmFiles(pFolder);
    if(Umut_Mod_Load(&src, 2, pcmdirs) != UMUT_OK){
        Umut_Stop();
        Umut_Mod_FlushPcmCache(true);
        Umut_Mod_Release();
        return 0;
    }
    UMUTMODSTAT stat = {0};
    Umut_Mod_Stat(&stat);
    m_MediaInfo.dwSampleRate = 44100;
    m_MediaInfo.nBitsPerSample = 16;
    m_MediaInfo.dwChannels = 2;
    m_MediaInfo.dwSeekableFlags = KPI_MEDIAINFO::SEEK_DISABLE;
    m_MediaInfo.dwUnitSample = UNIT_RENDER/BLOCK_ALIGN;
    m_MediaInfo.qwLength = stat.nTotalTime_dsec * (100 * 10000ui64);//0.1秒単位
    m_MediaInfo.qwLoop = -1;//無限ループ
    m_MediaInfo.qwFadeOut = -1;
    m_pFile = pFile;
    pFile->AddRef();
    m_MediaInfo.dwCount = 1;
    m_pFolder = pFolder;
    pFolder->AddRef();
    m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    return 1;
}
DWORD WINAPI KbUmDecoder::Render(BYTE *Buffer, DWORD dwSizeSample)
{
    if(!g_wo){
        return 0;
    }
    DWORD dwSize = dwSizeSample * BLOCK_ALIGN;
    DWORD dwRet;
    um_Lock();
    KbWaveOut *wo = (KbWaveOut*)g_wo;
    HANDLE hEvent = wo->m_hEvent;
    dwRet = wo->Read(Buffer, UNIT_RENDER);
    while(dwRet < UNIT_RENDER){
        if(!wo->IsOpen()){
            break;
        }
        ResetEvent(g_hEvent);
        WAVEHDR *pwh = wo->RemoveWavHdr();
        int nNumWaveHdrs = wo->GetNumWaveHdrs();
        um_Unlock();
        if(pwh){
            pwh->dwFlags |= WHDR_DONE;
            SetEvent(hEvent);
            while(--nNumWaveHdrs > 0){
                pwh = wo->RemoveWavHdr();
                pwh->dwFlags |= WHDR_DONE;
                SetEvent(hEvent);
            }
        }
        else{
            WaitForSingleObject(g_hEvent, 100);
        }
        UMUTMODSTAT stat = {0};
        Umut_Mod_Stat(&stat);
        um_Lock();
        dwRet += wo->Read(Buffer + dwRet, UNIT_RENDER-dwRet);
        if(stat.status != UMUT_MODSTAT_PLAY){
            break;
        }
    }
    um_Unlock();
    return dwRet / BLOCK_ALIGN;
}
UINT64 WINAPI KbUmDecoder::Seek(UINT64 qwPosSample, DWORD dwFlag)
{
    Terminate();
    ResetEvent(m_hEvent);
    if(g_wo){
        g_wo->Reset();
    }
    m_hThread = (HANDLE)_beginthreadex(NULL, 0, KbUmThreadProc, this, 0, (unsigned int*)&m_dwThreadId);
    HANDLE hEvents[] = {m_hThread, m_hEvent};
    if(WaitForMultipleObjects(2, hEvents, FALSE, INFINITE) == WAIT_OBJECT_0){
        Close();
        return 0;
    }
    return 0;
}
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbUmDecoder::Select(DWORD dwNumber,
                                  const KPI_MEDIAINFO **ppMediaInfo,
                                  IKpiTagInfo *pTagInfo,
                                  DWORD dwTagGetFlags)
{
    if(ppMediaInfo){
        *ppMediaInfo = NULL;
    }
    if (dwNumber > m_MediaInfo.dwCount) {
        return 0;
    }
    if (!ppMediaInfo) {//タグ取得のみ(タイトル/アーティストのみ)
        //GetTagInfo(pTagInfo, dwTagGetFlags);
        return 1;
    }
    //GetTagInfo(pTagInfo, dwTagGetFlags);
    *ppMediaInfo = &m_MediaInfo;
    m_MediaInfo.dwNumber = dwNumber;
    return 1;
}
/////////////////////////////////////////////////////////////////////////
void  __fastcall KbUmDecoder::GetPcmFiles(IKpiFolder *pFolder)
{//曲と同じフォルダの *.pac ファイルを抽出
 //必要な .pac ファイル名の取得方法が不明なため、同一フォルダ内の
 //拡張子 .pac は全て抽出する
    wchar_t szPath[MAX_PATH];
    DWORD dwIndex = 0;
    while(1){
        if(!pFolder->EnumFiles(dwIndex++, szPath, sizeof(szPath), 0)){
            break;
        }
        if(!szPath[0]){
            continue;
        }
        wchar_t *pszExt = wcsrchr(szPath, L'.');
        if(!pszExt){
            continue;
        }
        if(_wcsicmp(pszExt, L".pac") == 0){
            IKpiFile *pFile = NULL;
            if(!pFolder->OpenFile(szPath, &pFile)){
                continue;
            }
            const char *cszFileName;
            if(pFile->GetRealFileA(&cszFileName)){
                m_ppPcmFiles = (IKpiFile**)realloc(m_ppPcmFiles, sizeof(IKpiFile*)*(m_nPcmFiles + 1));
                m_ppPcmFiles[m_nPcmFiles++] = pFile;
            }
            else{
                pFile->Release();
            }
        }
    }
}
/////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbUmDecoder::Initialize(HINSTANCE hDll)
{
    if(!s_bInitialized){
        s_bInitialized = TRUE;
        InitializeCriticalSection(&g_cs);
        return LoadUmutDll(hDll);
    }
    return g_hUmut != NULL;
}
/////////////////////////////////////////////////////////////////////////
void __fastcall KbUmDecoder::Uninitialize(void)
{
    if(s_bInitialized){
        FreeUmutDll();
        DeleteCriticalSection(&g_cs);
        s_bInitialized = FALSE;
    }
}
/////////////////////////////////////////////////////////////////////////
