#include <windows.h>
#include <stdio.h>
#pragma warning(disable:4786)

namespace std
{
    typedef size_t size_t;
    typedef FILE FILE;
};

#include "kbfmmidi_main.h"


#ifdef _DEBUG
#define KBFMMIDI_VERSION      0x7FFFFFFF         //デバッグ時は優先度最大
#define KBFMMIDI_VERSION_STR "0x7FFFFFFF(Debug)" 
#else
#define KBFMMIDI_VERSION     8      //プラグインのバージョン
#define KBFMMIDI_VERSION_STR "0.08" 
#endif
#define KBFMMIDI_COPYRIGHT    "Copyright (C) 2005-2016 yuno, Kobarin"
#define KBFMMIDI_DESCRIPTION "fmmidi MIDI Decoder v" KBFMMIDI_VERSION_STR " based on fmmidi v1.8"

static HINSTANCE g_hKpi;
#define UNIT_SAMPLE 512
#define NCH  2
#define BPS 64
#define UNIT_RENDER (UNIT_SAMPLE*NCH*(BPS/8))

KbFmMidiDecoder::KbFmMidiDecoder(void)
    :m_synthesizer(&m_note_factory)
{
    int i;
    m_synthesizers[0] = &m_synthesizer;
    for(i = 1; i < MAX_NUM_PORTS; i++){
        m_synthesizers[i] = NULL;
    }
    m_pMixingBuffer = NULL;
    m_dwSampleRate = 0;
    m_dwNumPorts = 0;
    m_dwCurrentSample = 0;
    m_dwLastSample = 0;
    m_bSeeking = FALSE;
}
KbFmMidiDecoder::~KbFmMidiDecoder(void)
{
    Close();
}
void KbFmMidiDecoder::midi_message(int port, uint_least32_t message)
{
    if(m_bSeeking){
        if(((message & 0xF0) == 0x90)){
        //シーク中はノートオンを無視する
            int velocity = (message >> 16) & 0x7F;
            if(velocity > 0){
                return;
            }
        }
    }
    if(port < 0 || port >= m_dwNumPorts){
        port = 0;
    }
    m_synthesizers[port]->midi_event(message);
}
void KbFmMidiDecoder::sysex_message(int port, const void* data, size_t size)
{
    if(port < 0 || port >= m_dwNumPorts){
        port = 0;
    }
    m_synthesizers[port]->sysex_message(data, size);
}
void KbFmMidiDecoder::meta_event(int type, const void* data, size_t size)
{
}
void KbFmMidiDecoder::reset()
{
    int i;
    for(i = 0; i < m_dwNumPorts; i++){
        m_synthesizers[i]->reset();
    }
}
BOOL __fastcall KbFmMidiDecoder::Open(const char *cszFileName, SOUNDINFO *pInfo)
{
    DWORD dwSampleRate = pInfo->dwSamplesPerSec;
    ZeroMemory(pInfo, sizeof(SOUNDINFO));
    FILE *fp = fopen(cszFileName, "rb");
    if(!fp){
        return FALSE;
    }
    if(!m_sequencer.load(fp)){
        return FALSE;
    }
    fclose(fp);
    char szProgramsTxt[MAX_PATH*2];
    GetModuleFileName(g_hKpi, szProgramsTxt, sizeof(szProgramsTxt));
    char *p = szProgramsTxt;
    char *path = szProgramsTxt;
    while(*p){
        if(IsDBCSLeadByte((BYTE)*p)){
            p += 2;
        }
        else{
            if(*p++ == '\\'){
                path = p;
            }
        }
    }
    strcpy(path, "programs.txt");
    fp = fopen(szProgramsTxt, "rt");
    if(fp){
        while(!feof(fp)){
            int c = getc(fp);
            if(c == '@'){
                int prog = 0;
                FMPARAMETER p;
                //ZeroMemory(&p, sizeof(p));
                if(fscanf(fp, "%d%d%d%d", &prog, &p.ALG, &p.FB, &p.LFO) == 4
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op1.AR, &p.op1.DR, &p.op1.SR, &p.op1.RR, &p.op1.SL, &p.op1.TL, &p.op1.KS, &p.op1.ML, &p.op1.DT, &p.op1.AMS) == 10
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op2.AR, &p.op2.DR, &p.op2.SR, &p.op2.RR, &p.op2.SL, &p.op2.TL, &p.op2.KS, &p.op2.ML, &p.op2.DT, &p.op2.AMS) == 10
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op3.AR, &p.op3.DR, &p.op3.SR, &p.op3.RR, &p.op3.SL, &p.op3.TL, &p.op3.KS, &p.op3.ML, &p.op3.DT, &p.op3.AMS) == 10
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op4.AR, &p.op4.DR, &p.op4.SR, &p.op4.RR, &p.op4.SL, &p.op4.TL, &p.op4.KS, &p.op4.ML, &p.op4.DT, &p.op4.AMS) == 10){
                    m_note_factory.set_program(prog, p);
                }
            }else if(c == '*'){
                int prog = 0;
                DRUMPARAMETER p;
                //ZeroMemory(&p, sizeof(p));
                if(fscanf(fp, "%d%d%d%d%d%d%d", &prog, &p.ALG, &p.FB, &p.LFO, &p.key, &p.panpot, &p.assign) == 7
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op1.AR, &p.op1.DR, &p.op1.SR, &p.op1.RR, &p.op1.SL, &p.op1.TL, &p.op1.KS, &p.op1.ML, &p.op1.DT, &p.op1.AMS) == 10
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op2.AR, &p.op2.DR, &p.op2.SR, &p.op2.RR, &p.op2.SL, &p.op2.TL, &p.op2.KS, &p.op2.ML, &p.op2.DT, &p.op2.AMS) == 10
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op3.AR, &p.op3.DR, &p.op3.SR, &p.op3.RR, &p.op3.SL, &p.op3.TL, &p.op3.KS, &p.op3.ML, &p.op3.DT, &p.op3.AMS) == 10
                && fscanf(fp, "%d%d%d%d%d%d%d%d%d%d", &p.op4.AR, &p.op4.DR, &p.op4.SR, &p.op4.RR, &p.op4.SL, &p.op4.TL, &p.op4.KS, &p.op4.ML, &p.op4.DT, &p.op4.AMS) == 10){
                    m_note_factory.set_drum_program(prog, p);
                }
            }
        }
        fclose(fp);
    }
    if(dwSampleRate < 8000){
        dwSampleRate = 44100;
    }
    else if(dwSampleRate > 96000){
       dwSampleRate = 96000;
    }
    m_dwNumPorts = m_sequencer.get_num_ports();
    m_pMixingBuffer = new int_least32_t[NCH*UNIT_SAMPLE];
    if(m_dwNumPorts > MAX_NUM_PORTS){
        m_dwNumPorts = MAX_NUM_PORTS;
    }
    int i;
    for(i = 1; i < m_dwNumPorts; i++){
        m_synthesizers[i] = new synthesizer(&m_note_factory);
    }
    pInfo->dwSamplesPerSec = m_dwSampleRate = dwSampleRate;
    pInfo->dwChannels = NCH;
#if BPS == 16
    pInfo->dwBitsPerSample = 16;//
#elif BPS == 32
    pInfo->dwBitsPerSample = -32;//
#else
    pInfo->dwBitsPerSample = -64;//
#endif
    pInfo->dwSeekable = 1;
    pInfo->dwUnitRender = UNIT_RENDER;
    pInfo->dwReserved1 = 0;
    pInfo->dwReserved2 = 0;
    DWORD dwLength = m_sequencer.get_total_time()*1000.0;
    m_dwLastSample = MulDiv(dwLength, m_dwSampleRate, 1000);;
    pInfo->dwLength = dwLength;
    return TRUE;
}
void __fastcall KbFmMidiDecoder::Close(void)
{
    int i;
    for(i = 1; i < m_dwNumPorts; i++){
        delete m_synthesizers[i];
        m_synthesizers[i] = NULL;
    }
    m_dwSampleRate = 0;
    m_dwNumPorts = 0;
    m_dwCurrentSample = 0;
    delete[] m_pMixingBuffer;
    m_pMixingBuffer = NULL;
    m_sequencer.clear();
}
DWORD __fastcall KbFmMidiDecoder::SetPosition(DWORD dwPos)
{
    if(dwPos == 0){
        reset();//戻すときは sequencer::play で呼ばれるが、先頭に戻すときは呼ばれない
    }
    m_bSeeking = TRUE;
    m_sequencer.play(dwPos / 1000.0, this);
    m_bSeeking = FALSE;
    m_dwCurrentSample = MulDiv(dwPos, m_dwSampleRate, 1000);
    return dwPos;
}
DWORD __fastcall KbFmMidiDecoder::Render(BYTE *pBuffer, DWORD dwSize)
{
    if(m_dwCurrentSample >= m_dwLastSample){
        return 0;
    }
    float fPos = (double)m_dwCurrentSample/(double)m_dwSampleRate;
    m_sequencer.play(fPos, this);

    ZeroMemory(m_pMixingBuffer, sizeof(int_least32_t)*NCH*UNIT_SAMPLE);
    int i;
    for(i = 0; i < m_dwNumPorts; i++){
        m_synthesizers[i]->synthesize_mixing(m_pMixingBuffer, UNIT_SAMPLE, m_dwSampleRate);
    }
#if BPS == 16   //16bit int
    short *p = (short*)pBuffer;
    for(i = 0; i < UNIT_SAMPLE*NCH; i++){
        int t = m_pMixingBuffer[i];
        if(t >= 32768)t = 32767;
        else if(t < -32768)t = -32768;
        p[i] = t;
    }
#elif BPS == 32 //32bit float
    float *p = (float*)pBuffer;
    for(i = 0; i < UNIT_SAMPLE*NCH; i++){
        int t = m_pMixingBuffer[i];
        p[i] = t / 32768.0;
    }
#else           //64bit float
    double *p = (double*)pBuffer;
    for(i = 0; i < UNIT_SAMPLE*NCH; i++){
        int t = m_pMixingBuffer[i];
        p[i] = t / 32768.0;
    }
#endif
    m_dwCurrentSample += UNIT_SAMPLE;
    if(m_dwCurrentSample > m_dwLastSample){
        dwSize -= (m_dwCurrentSample - m_dwLastSample)*(BPS/8)*NCH;
    }
    return dwSize;
}

static HKMP WINAPI kmp_Open(const char *cszFileName, SOUNDINFO *pInfo)
{
    KbFmMidiDecoder *pDec = new KbFmMidiDecoder;
    if(pDec->Open(cszFileName, pInfo))
        return pDec;
    delete pDec;
    return NULL;
}
static void WINAPI kmp_Close(HKMP hKMP)
{
    if(hKMP){
        KbFmMidiDecoder *pDec = (KbFmMidiDecoder*)hKMP;
        delete pDec;
    }
}
static DWORD WINAPI kmp_Render(HKMP hKMP, BYTE* pBuffer, DWORD dwSize)
{
    if(!hKMP)return 0;
    KbFmMidiDecoder *pDec = (KbFmMidiDecoder*)hKMP;
    return pDec->Render(pBuffer, dwSize);
}
static DWORD WINAPI kmp_SetPosition(HKMP hKMP, DWORD dwPos)
{
    if(!hKMP)return 0;
    KbFmMidiDecoder *pDec = (KbFmMidiDecoder*)hKMP;
    return pDec->SetPosition(dwPos);
}
static void WINAPI kmp_Init(void)
{
}
static void WINAPI kmp_Deinit(void)
{
}

extern "C" KMPMODULE* WINAPI kmp_GetTestModule(void)
{
    static const char *ppszSupportExts[] = {".mid", NULL};
    static KMPMODULE Mod = {
        KMPMODULE_VERSION,    //dwVersion 
        KBFMMIDI_VERSION,     //dwPluginVersion
        KBFMMIDI_COPYRIGHT,   //pszCopyright
        KBFMMIDI_DESCRIPTION, //pszDescription
        ppszSupportExts,    //ppszSupportExts
        1,                  //dwRentrant
        kmp_Init,           //Init
        kmp_Deinit,         //Deinit
        kmp_Open,           //Open
        NULL,               //OpenFromBuffer
        kmp_Close,          //Close
        kmp_Render,         //Render
        kmp_SetPosition     //SetPosition
    };
    return &Mod;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call){
        case DLL_PROCESS_ATTACH:
            {
                g_hKpi = (HINSTANCE)hModule;
                DisableThreadLibraryCalls((HMODULE)hModule);
            }
            break;
        case DLL_PROCESS_DETACH:
            {
            }
            break;
    }
    return TRUE;
}
