#include <windows.h>
#include <wchar.h>

#include "kpi.h"
#include "kbfmpmd_config.h"
#include "fmpmd/fmpmd.h"

//セクション名
static const wchar_t SEC_GENERAL[] = L"General";
static const wchar_t SEC_SEARCHPATH[] = L"SearchPath";

//General セクション
//MultipleInstance
static const wchar_t KEY_MULTIPLEINSTANCE[] = L"MultipleInstance";
static const int     MULTIPLEINSTANCE_DEFAULT = 1;
static const wchar_t SZ_MULTIPLEINSTANCE_DEFAULT[] = L"1";
//FileIO
static const wchar_t KEY_FILEIO[] = L"EnableFileIO";
static const int     FILEIO_DEFAULT = 1;
static const wchar_t SZ_FILEIO_DEFAULT[] = L"1";
//再生周波数
static const wchar_t KEY_SAMPLERATE[] = L"SampleRate";
static const int     SAMPLERATE_DEFAULT = 44100;
static const wchar_t SZ_SAMPLERATE_DEFAULT[] = L"44100";
//FM を 55kHz で合成する
static const wchar_t KEY_FMCALC55K[] = L"FMCalc55k";
static const int     FMCALC55K_DEFAULT = 1;
static const wchar_t SZ_FMCALC55K_DEFAULT[] = L"1";
//PPZ8 駆動周波数
static const wchar_t KEY_PPZRATE[] = L"PPZRate";
static const int     PPZRATE_DEFAULT = 0;//出力周波数と同じ
static const wchar_t SZ_PPZRATE_DEFAULT[] = L"0";
//PCM がなくても強制的に再生
static const wchar_t KEY_NOPCMFORCEPLAY[] = L"NoPCMForcePlay";
static const int     NOPCMFORCEPLAY_DEFAULT = 1;
static const wchar_t SZ_NOPCMFORCEPLAY_DEFAULT[] = L"1";

//補間(PPS)
static const wchar_t KEY_PPS_INTERPOLATION[] = L"PPSInterpolation";
static const int     PPS_INTERPOLATION_DEFAULT = 1;
static const wchar_t SZ_PPS_INTERPOLATION_DEFAULT[] = L"1";
//補間(P86)
static const wchar_t KEY_P86_INTERPOLATION[] = L"P86Interpolation";
static const int     P86_INTERPOLATION_DEFAULT = 1;
static const wchar_t SZ_P86_INTERPOLATION_DEFAULT[] = L"1";
//補間(PPZ8)
static const wchar_t KEY_PPZ_INTERPOLATION[] = L"PPZInterpolation";
static const int     PPZ_INTERPOLATION_DEFAULT = 1;
static const wchar_t SZ_PPZ_INTERPOLATION_DEFAULT[] = L"1";
//PPS
static const wchar_t KEY_USEPPS[] = L"UsePPS";
static const int     USEPPS_DEFAULT = 1;
static const wchar_t SZ_USEPPS_DEFAULT[] = L"1";
//SSG+RHY
static const wchar_t KEY_SSGRHY[] = L"RhythmUseWithSSGDrum";
static const int     SSGRHY_DEFAULT = 0;
static const wchar_t SZ_SSGRHY_DEFAULT[] = L"0";
//PMD86->B2
static const wchar_t KEY_PMD86PCMMODE[] = L"PMD86PCMMode";
static const int     PMD86PCMMODE_DEFAULT = 0;
static const wchar_t SZ__PMD86PCMMODE_DEFAULT[] = L"0";
//PPZ8EM
static const wchar_t KEY_ADPCMPPZ8EMULATE[] = L"ADPCMPPZ8Emulate";
static const int     ADPCMPPZ8EMULATEE_DEFAULT = 0;
static const wchar_t SZ__ADPCMPPZ8EMULATEE_DEFAULT[] = L"0";

//SearchPath セクション
static const wchar_t KEY_PCMPATH[] = L"Pcm";    //PCM(PVI) 検索パス
static const wchar_t KEY_RHYTHMPATH[] = L"Rhythm"; //リズム音源のパス
static const wchar_t RHYTHMPATH_DEFAULT[] = L"";

//PMDWin.dll/WinFMP.dll のパス
static const wchar_t KEY_PMDWINDLL_X64[] = L"PmdWinDll_x64";
static const wchar_t KEY_PMDWINDLL_X86[] = L"PmdWinDll_x86";
static const wchar_t PMDWINDLL_X64_DEFAULT[] = L"";
static const wchar_t PMDWINDLL_X86_DEFAULT[] = L"";

static const wchar_t KEY_WINFMPDLL_X64[] = L"WinFmpDll_x64";
static const wchar_t KEY_WINFMPDLL_X86[] = L"WinFmpDll_x86";
static const wchar_t WINFMPDLL_X64_DEFAULT[] = L"";
static const wchar_t WINFMPDLL_X86_DEFAULT[] = L"";

static const struct
{//プラグイン固有の設定
    const wchar_t *szKey;
    bool  bPMD; //true: PMD, false: FMPP
}cfg_Unique[] = {
    {KEY_PPS_INTERPOLATION, true}, //PMD固有の設定
    {KEY_P86_INTERPOLATION, true}, //PMD
    {KEY_USEPPS, true},            //PMD
    {KEY_SSGRHY, true},            //PMD
    {KEY_PMD86PCMMODE, true},      //PMD
    {KEY_ADPCMPPZ8EMULATE, false}, //FMP

    {KEY_PMDWINDLL_X64, true},      //PMD
    {KEY_PMDWINDLL_X86, true} ,     //PMD
    {KEY_WINFMPDLL_X64, false},     //FMP
    {KEY_WINFMPDLL_X86, false},     //FMP

    {NULL, 0}
};
//////////////////////////////////////////////////////////////////////////////
DWORD GetMultipleInstance(IKpiConfig *pConfig)
{
    if(pConfig->GetInt(SEC_GENERAL, KEY_MULTIPLEINSTANCE, MULTIPLEINSTANCE_DEFAULT)){
        return KPI_MULTINST_INFINITE;
    }
    else{
        return KPI_MULTINST_ONE;
    }
}
//////////////////////////////////////////////////////////////////////////////
static void __fastcall CheckMaskStr(wchar_t* pszDest, int nDestSize, const wchar_t* cszSrc, int nDigits)//nDigits=桁数
{//パートマスクの文字列を検証
 //0 と 1 以外の文字を含む場合、数字であれば 1 に置換、数字以外なら 0 に置換
 //(nDigits+1) 桁目以降の文字は無視
 //nDigits 桁未満の場合、以降は 0 に置換(最大nDigits桁)
    if (nDestSize <= nDigits) {
        pszDest[0] = 0;
        return;
    }
    wcsncpy_s(pszDest, nDestSize, cszSrc, _TRUNCATE);
    int i;
    for (i = 0; i < nDigits; i++) {
        if (!pszDest[i]) {
            for (int j = i; j < nDigits; j++) {
                pszDest[j] = L'0';
            }
            break;
        }
        if (pszDest[i] == L'0') {
            continue;
        }
        if (L'1' <= pszDest[i] && pszDest[i] <= L'9') {//0 以外の数字は 1 に置換
            pszDest[i] = L'1';
        }
        else {//数字以外は 0 に置換
            pszDest[i] = L'0';
        }
    }
    pszDest[nDigits] = 0;
}
//////////////////////////////////////////////////////////////////////////////
BOOL __fastcall kbfmpmd_EnumConfig(IKpiConfig* pConfig, IKpiConfigEnumerator* pEnumerator, bool bPMD)//bPMD=true のとき PMD, false のとき FMP として列挙
{
    static const KPI_CFG_SECTION sec[] = {//static にしない
        {SEC_GENERAL, NULL,//L"General", 
            L"kbfmp.kpi と kbpmd.kpi に関する一般的な設定です。\r\n"
            L"kbfmp.kpi と kbpmd.kpi で設定を共有しています。"},
        {SEC_SEARCHPATH, L"検索パス",
            L"検索パスに関する設定です。\r\n"
            L"kbfmp.kpi と kbpmd.kpi で設定を共有しています。"},
        {NULL, NULL, NULL}
    };
    static const KPI_CFG_KEY key[] = {
    //   type, section, key, keydesc, default, min, max, list, listdesc, help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_MULTIPLEINSTANCE, NULL,
            SZ_MULTIPLEINSTANCE_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"false にすると本プラグイン対応の曲データを複数同時に開く際にプレイヤー本体とは別プロセス\r\n"
            L"経由で曲データを開きます。\r\n"
            L"\r\n"
            L"WinFMP/PMDWin の開発資料によるとハードウェア LFO を使うデータを複数同時に処理する際に支障\r\n"
            L"が出るそうです。\r\n"
            L"\r\n"
            L"true にした方が複数の曲データを同時に開いたときのパフォーマンスは向上しますが、\r\n"
            L"ハードウェア LFO を使用する曲を再生するなら false にした方が無難かもしれません。\r\n"
            L"\r\n"
            L"本プラグインの既定値は true です。ハードウェア LFO を使う曲を開いたとしても、同時に\r\n"
            L"再生してる少しの間(クロスフェード中等)だけ音質が劣化する程度と考えています。\r\n"
            L"実際にどうなのかは不明です。不安な方は false に設定して下さい。\r\n"
            L"\r\n"
            L"動作中のプレイヤー本体とプラグインの bit が異なる(64bit版の本体から 32bit版のプラグイン\r\n"
            L"を使用している等)場合、本設定は無視されます。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_FILEIO, L"FileIO インターフェースを使う",
            SZ_FILEIO_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると WinFMP.dll v.019 以降、PMDWin.dll v0.40 以降で実装された FileIO\r\n"
            L"インターフェースを使用します。\r\n"
            L"FileIO インターフェースを使用するとアーカイブ内の曲データ再生時にテンポラリを\r\n"
            L"作成しなくなったり検索パス指定でファイル名の制限(長さの制限、Unicode の名前が\r\n"
            L"使えないなど)がなくなります。\r\n"
            L"\r\n"
            L"true にすると動作に支障が出る場合は false にしてみて下さい。\r\n"
            L"DLL が FileIO インターフェースに対応しない場合、設定は無視されます。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_SAMPLERATE, L"再生周波数(Hz)",
            SZ_SAMPLERATE_DEFAULT, NULL, NULL, //default/min/max
            L"22050\t44100\t48000\t88200\t96000\t176400\t192000\t-22050\t-44100\t-48000\t-88200\t-96000\t-176400\t-192000", //list
            NULL,//listdesc
            L"プレイヤーからの要求が 0(既定値)の場合の再生周波数です。\r\n"
            L"負の値だとプレイヤーからの要求を無視します。\r\n"
            L"例えば -44100 を指定するとプレイヤーから 48000 Hz で\r\n"
            L"要求されても常に 44100 Hz で再生します。",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_FMCALC55K, L"FM を 55kHz で合成",
            SZ_FMCALC55K_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると FM 音源の合成周波数を 55kHz に指定します。\r\n"
            L"false にすると再生時の出力周波数と同じ周波数で合成します。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_PPZRATE, L"PPZ8 駆動周波数(Hz)",
            SZ_PPZRATE_DEFAULT, NULL, NULL, //default/min/max
            L"0\t22050\t44100\t48000\t88200\t96000\t176400\t192000", //list
            L"再生周波数と同じ",//listdesc
            L"PPZ8 の駆動周波数を指定します。\r\n"
            L"0 にすると再生時の出力周波数と同じとなります。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_NOPCMFORCEPLAY, L"PCM がなくても強制的に再生",
            SZ_PPS_INTERPOLATION_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると PPC, PVI, PZI, PPS, P86 等が見つからなくても強制的に演奏します。\r\n"
            L"PCM が見つからない場合、PPC, PVI, PZI, P86 のパートは発音されません。\r\n"
            L"PPS のパートは PMD 内蔵の SSG ドラムで演奏します。\r\n"
            L"false にすると PCM が見つからないとファイルオープンに失敗します。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_PPS_INTERPOLATION, L"補間(PPS)",
            SZ_PPS_INTERPOLATION_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると PPS の補間処理を有効にします。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_P86_INTERPOLATION, L"補間(P86)",
            SZ_P86_INTERPOLATION_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると P86 の補間処理を有効にします。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_PPZ_INTERPOLATION, L"補間(PPZ8)",
            SZ_PPZ_INTERPOLATION_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると PPZ8 の補間処理を有効にします。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_USEPPS, L"UsePPS",
            SZ_USEPPS_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると PMD の曲データで K/R リズムパートを PPS で演奏します。\r\n"
            L"false にすると PMD 内蔵の SSG ドラムで演奏します。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_SSGRHY, L"SSG+RHY",
            SZ_SSGRHY_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると PMD の曲データで K/R のリズムパートを演奏するときに\r\n"
            L"OPNA のリズム音源も同時に演奏します。\r\n"
            L"PMB2/PMD86/PMDPPZ の /N オプションに相当します。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_PMD86PCMMODE, L"PMD86->B2",
            SZ__PMD86PCMMODE_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると PMD の曲データで PCM の仕様を ADPCM になるべく合わせるようにします。\r\n"
            L"PMD86 の /S オプションに相当します。\r\n"
            L"",
            },//help
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_ADPCMPPZ8EMULATE, L"PPZ8EM",
            SZ__ADPCMPPZ8EMULATEE_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL,//listdesc
            L"true にすると FMP の曲データで ADPCM パートを PPZ8 を用いて演奏します。\r\n"
            L"",
            },//help
        //検索パス
        {KPI_CFG_TYPE_FILE, SEC_SEARCHPATH, KEY_PMDWINDLL_X64, L"PMDWin.dll(x64)",
            PMDWINDLL_X64_DEFAULT, L"PMDWin.dll(x64)(*.dll)\t*.dll*", NULL, //default/min/max
            NULL, //list
            NULL, //listdesc 
            L"PMDWin.dll(64bit版) の場所を指定します。未指定にするとプラグインと同じフォルダ\r\n"
            L"にある PMDWin.dll を使用します。\r\n"
            L"\r\n"
            L"PMDWin.dll: http://c60.la.coocan.jp/\r\n"
            L"\r\n"
            L"公式サイトの音質優先版(PMDWin.dll.256等)をリネームせずに使うことが出来ます。\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_FILE, SEC_SEARCHPATH, KEY_PMDWINDLL_X86, L"PMDWin.dll(x86)",
            PMDWINDLL_X86_DEFAULT, L"PMDWin.dll(x86)(*.dll)\t*.dll*", NULL, //default/min/max
            NULL, //list
            NULL, //listdesc 
            L"PMDWin.dll(32bit版) の場所を指定します。未指定にするとプラグインと同じフォルダ\r\n"
            L"にある PMDWin.dll を使用します。\r\n"
            L"\r\n"
            L"PMDWin.dll: http://c60.la.coocan.jp/\r\n"
            L"\r\n"
            L"公式サイトの音質優先版(PMDWin.dll.256等)をリネームせずに使うことが出来ます。\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_FILE, SEC_SEARCHPATH, KEY_WINFMPDLL_X64, L"WinFMP.dll(x64)",
            WINFMPDLL_X64_DEFAULT, L"WinFMP.dll(x64)(*.dll)\t*.dll*", NULL, //default/min/max
            NULL, //list
            NULL, //listdesc 
            L"WinFMP.dll(64bit版) の場所を指定します。未指定にするとプラグインと同じフォルダ\r\n"
            L"にある WinFMP.dll を使用します。\r\n"
            L"\r\n"
            L"公式サイトの音質優先版(WinFMP.dll.256等)をリネームせずに使うことが出来ます。\r\n"
            L"\r\n"
            L"PDZFZ8XWin.dll を WinFMP.dll と同じフォルダに置くことで再現性が向上します。\r\n"
            L"\r\n"
            L"WinFMP.dll:     http://c60.la.coocan.jp/\r\n"
            L"PDZFZ8XWin.dll: http://www.aosoft.jp/\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_FILE, SEC_SEARCHPATH, KEY_WINFMPDLL_X86, L"WinFMP.dll(x86)",
            WINFMPDLL_X86_DEFAULT, L"WinFMP.dll(x86)(*.dll)\t*.dll*", NULL, //default/min/max
            NULL, //list
            NULL, //listdesc 
            L"WinFMP.dll(32bit版) の場所を指定します。未指定にするとプラグインと同じフォルダ\r\n"
            L"にある WinFMP.dll を使用します。\r\n"
            L"\r\n"
            L"公式サイトの音質優先版(WinFMP.dll.256等)をリネームせずに使うことが出来ます。\r\n"
            L"\r\n"
            L"PDZFZ8XWin.dll を WinFMP.dll と同じフォルダに置くことで再現性が向上します。\r\n"
            L"\r\n"
            L"WinFMP.dll:     http://c60.la.coocan.jp/\r\n"
            L"PDZFZ8XWin.dll: http://www.aosoft.jp/\r\n"
            L""
        },//help
        {KPI_CFG_TYPE_FOLDER, SEC_SEARCHPATH, KEY_RHYTHMPATH, L"リズム音源",
            RHYTHMPATH_DEFAULT, NULL, NULL, //default/min/max
            NULL, //list
            NULL, //listdesc 
            L"リズム音源の WAVE データの検索パスを指定します。\r\n"
            L"未指定の場合はプラグインと同じフォルダから検索します。\r\n"
            L"\r\n"
            L"以下の WAVE ファイルが置いてあるパスを指定します。\r\n"
            L"\r\n"
            L"・2608_BD.WAV\r\n"
            L"・2608_HH.WAV\r\n"
            L"・2608_RIM.WAV\r\n"
            L"・2608_SD.WAV\r\n"
            L"・2608_TOM.WAV\r\n"
            L"・2608_TOP.WAV\r\n"
            L"\r\n"
            L"指定のパスに ym2608_adpcm_rom.bin が存在する場合はそちらを優先します。\r\n"
            L""
            },//help
        {KPI_CFG_TYPE_FOLDER, SEC_SEARCHPATH, KEY_PCMPATH, L"PCM",
            L"", NULL, NULL, //default/min/max
            NULL, //list
            NULL, //listdesc 
            L"PCM(PPC,PVI,PZI,PPS,P86 等) の検索パスを指定します。\r\n"
            L"最大 64 個まで指定出来ます。サブフォルダは検索しません。\r\n"
            L"ここで指定しなくても、曲データと同じフォルダは常に検索します。\r\n"
            },//help
       {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
    };
    int i;
    for (i = 0; sec[i].cszSection; i++) {
        pEnumerator->EnumSection(&sec[i]);
    }
    for (i = 0; key[i].cszSection; i++) {
        if (wcscmp(key[i].cszKey, KEY_PCMPATH) != 0) {
            bool bEnum = true;
            //プラグインが対応しない設定は列挙しない
            for (int j = 0; cfg_Unique[j].szKey; j++) {
                if (wcscmp(key[i].cszKey, cfg_Unique[j].szKey) == 0){
                    bEnum = cfg_Unique[j].bPMD == bPMD;
                    break;
                }
            }
            if(bEnum){
                pEnumerator->EnumKey(&key[i]);
            }
        }
        else {//PCM
            KPI_CFG_KEY key_pcmpath = key[i];
            //キー名とキーの表示名に番号付加して 64 個列挙
            wchar_t szKey[128];
            wchar_t szKeyDesc[128];
            key_pcmpath.cszKey = szKey;
            key_pcmpath.cszKeyDesc = szKeyDesc;
            for (int j = 0; j < 64; j++) {
                _snwprintf_s(szKey, _TRUNCATE, L"%s%d", key[i].cszKey, j + 1);
                _snwprintf_s(szKeyDesc, _TRUNCATE, L"%s%d", key[i].cszKeyDesc, j + 1);
                pEnumerator->EnumKey(&key_pcmpath);
            }
        }
    }
    return TRUE;
}
//////////////////////////////////////////////////////////////////////////////
DWORD __fastcall kbfmpmd_ApplyConfig(IKpiConfig* pConfig,
    const wchar_t* cszSection, //セクション名
    const wchar_t* cszKey, //キー名
    INT64  nValue, //変更後の値(BOOL/INT の場合)
    double dValue, //変更後の値(FLOAT の場合)
    const wchar_t* cszValue, //変更後の値(文字列)
    const wchar_t* cszPmSection, //PartMask セクション名
    const PM_DIGITS* pPmDigits)
{
    static const wchar_t KEY_REVERSE[] = L"Reverse";
    static const wchar_t KEY_RESET[] = L"Reset";
    if(_wcsicmp(cszSection, cszPmSection) == 0){//パートマスクセクション
        wchar_t szMask[16];
        int i;
        for (i = 0; pPmDigits[i].cszKey; i++) {
            if (_wcsicmp(pPmDigits[i].cszKey, cszKey) == 0) {
                CheckMaskStr(szMask, _countof(szMask), cszValue, pPmDigits[i].nDigits);//正しい文字列に置換
                pConfig->SetStr(cszSection, cszKey, szMask);
                return KPI_CFGRET_NOTIFY;
            }
        }
        if (_wcsicmp(cszKey, KEY_REVERSE) == 0 || _wcsicmp(cszKey, KEY_RESET) == 0) {
            bool bReverse = _wcsicmp(cszKey, KEY_REVERSE) == 0;
            wchar_t szTemp[16];
            for (i = 0; pPmDigits[i].cszKey; i++) {
                kbfmpmd_GetPartMaskStr(pConfig, cszSection, pPmDigits[i].cszKey, szMask, _countof(szMask), pPmDigits[i].nDigits);
                int j;
                for (j = 0; szMask[j]; j++) {
                    if (bReverse) {//反転
                        if (szMask[j] == L'1') {
                            szMask[j] = L'0';
                        }
                        else {
                            szMask[j] = L'1';
                        }
                    }
                    else {//リセット
                        szMask[j] = L'0';
                    }
                }
                pConfig->SetStr(cszSection, pPmDigits[i].cszKey, szMask);
            }
            return KPI_CFGRET_NOTIFY;
        }
        return KPI_CFGRET_OK;
    }
    if (cszKey[0]) {
        //取り敢えず全ての設定を適用(後で重複分を除去し、空いてる分を詰める)
        pConfig->SetStr(cszSection, cszKey, cszValue);
        if (_wcsicmp(cszKey, KEY_PMDWINDLL_X64) == 0 ||
            _wcsicmp(cszKey, KEY_PMDWINDLL_X86) == 0 ||
            _wcsicmp(cszKey, KEY_WINFMPDLL_X64) == 0 ||
            _wcsicmp(cszKey, KEY_WINFMPDLL_X86) == 0 ||
            _wcsicmp(cszKey, KEY_MULTIPLEINSTANCE) == 0) {
            return KPI_CFGRET_RELOAD_KPI;
        }
        static const wchar_t* cszNotifyKeys[] = {
            KEY_PPS_INTERPOLATION, KEY_P86_INTERPOLATION, KEY_PPZ_INTERPOLATION,
            KEY_USEPPS, KEY_SSGRHY, KEY_PMD86PCMMODE, KEY_ADPCMPPZ8EMULATE, NULL };
        for (int i = 0; cszNotifyKeys[i]; i++) {
            if (_wcsicmp(cszKey, cszNotifyKeys[i]) == 0) {
                return KPI_CFGRET_NOTIFY;
            }
        }
        return KPI_CFGRET_RELOAD_DATA;
    }
    if (_wcsicmp(cszSection, SEC_SEARCHPATH) != 0) {
        return KPI_CFGRET_OK;
    }
    wchar_t szKeyPcmPath[128];
    wchar_t *pszDirs[FMPMD_MAX_PCMDIR+1];
    int i;
    kbfmpmd_GetPcmDirs(pConfig, pszDirs, FMPMD_MAX_PCMDIR + 1);//重複分の削除と空文字列の詰め込みも行われる
    for(i = 0; i < FMPMD_MAX_PCMDIR; i++){
    //再設定(この時点で pszDirs は重複分が除去されている)
        _snwprintf_s(szKeyPcmPath, _TRUNCATE, L"%s%d", KEY_PCMPATH, i + 1);
        if(pszDirs[i]){
             pConfig->SetStr(SEC_SEARCHPATH, szKeyPcmPath, pszDirs[i]);
        }
        else{
             pConfig->SetStr(SEC_SEARCHPATH, szKeyPcmPath, L"");
        }
    }
    return KPI_CFGRET_RELOAD_DATA;
}
//////////////////////////////////////////////////////////////////////////////
/*
struct KbFMPMD_Settings
{
    int  nSampleRate;   //再生周波数
    bool bFmCalc55k;    //FM を55kHz で合成する
    int  nPPZRate;      //PPZ8 駆動周波数(0のとき駆動周波数と同じ)
    bool bPPSInterpolation;//補間(PPS)
    bool bP86Interpolation;//補間(P86)
    bool bPPZInterpolation;//補間(PPZ8)
    bool bUsePPS;              //PMD
    bool bRhythmUseWithSSGDrum;//PMD
    bool bPMD86PCMMode;        //PMD
    bool bADPCMPPZ8Emulate;    //FMP
};
*/
void __fastcall kbfmpmd_GetSettings(IKpiConfig* pConfig, KbFMPMD_Settings *pSettings)
{
    //General
    pSettings->nSampleRate = pConfig->GetInt(SEC_GENERAL, KEY_SAMPLERATE, SAMPLERATE_DEFAULT);
    switch(pSettings->nSampleRate){
        case 22050: case 44100: case 48000: case 88200: case 96000: case 176400: case 192000:
        case -22050: case -44100: case -48000: case -88200 : case -96000: case -176400: case -192000:break;
        default: pSettings->nSampleRate = SAMPLERATE_DEFAULT;
    }
    pSettings->nPPZRate = pConfig->GetInt(SEC_GENERAL, KEY_PPZRATE, PPZRATE_DEFAULT);
    switch (pSettings->nPPZRate) {
    case 0: case 22050: case 44100: case 48000: case 88200: case 96000: case 176400: case 192000: break;
    default: pSettings->nPPZRate = PPZRATE_DEFAULT;
    }
    pSettings->bEnableFileIO = pConfig->GetInt(SEC_GENERAL, KEY_FILEIO, FILEIO_DEFAULT) != 0;
    pSettings->bFmCalc55k = pConfig->GetInt(SEC_GENERAL, KEY_FMCALC55K, FMCALC55K_DEFAULT) != 0;
    pSettings->bNoPCMForcePlay = pConfig->GetInt(SEC_GENERAL, KEY_NOPCMFORCEPLAY, NOPCMFORCEPLAY_DEFAULT) != 0;
    pSettings->bPPSInterpolation = pConfig->GetInt(SEC_GENERAL, KEY_PPS_INTERPOLATION, PPS_INTERPOLATION_DEFAULT) != 0;
    pSettings->bP86Interpolation = pConfig->GetInt(SEC_GENERAL, KEY_P86_INTERPOLATION, P86_INTERPOLATION_DEFAULT) != 0;
    pSettings->bPPZInterpolation = pConfig->GetInt(SEC_GENERAL, KEY_PPZ_INTERPOLATION, PPZ_INTERPOLATION_DEFAULT) != 0;
    pSettings->bUsePPS = pConfig->GetInt(SEC_GENERAL, KEY_USEPPS, USEPPS_DEFAULT) != 0;
    pSettings->bRhythmUseWithSSGDrum = pConfig->GetInt(SEC_GENERAL, KEY_SSGRHY, SSGRHY_DEFAULT) != 0;
    pSettings->bPMD86PCMMode = pConfig->GetInt(SEC_GENERAL, KEY_PMD86PCMMODE, PMD86PCMMODE_DEFAULT) != 0;
    pSettings->bADPCMPPZ8Emulate = pConfig->GetInt(SEC_GENERAL, KEY_ADPCMPPZ8EMULATE, ADPCMPPZ8EMULATEE_DEFAULT) != 0;
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall kbfmpmd_GetPmdWinDllPath(IKpiConfig* pConfig, wchar_t* pszPath, int nSize)
{//nSize は文字数、GetStr に渡すサイズはバッファサイズ
#ifdef _WIN64
    pConfig->GetStr(SEC_SEARCHPATH, KEY_PMDWINDLL_X64, pszPath, nSize * sizeof(wchar_t), PMDWINDLL_X64_DEFAULT);
#else
    pConfig->GetStr(SEC_SEARCHPATH, KEY_PMDWINDLL_X86, pszPath, nSize * sizeof(wchar_t), PMDWINDLL_X86_DEFAULT);
#endif
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall kbfmpmd_GetWinFmpDllPath(IKpiConfig* pConfig, wchar_t* pszPath, int nSize)
{//nSize は文字数、GetStr に渡すサイズはバッファサイズ
#ifdef _WIN64
    pConfig->GetStr(SEC_SEARCHPATH, KEY_WINFMPDLL_X64, pszPath, nSize * sizeof(wchar_t), WINFMPDLL_X64_DEFAULT);
#else
    pConfig->GetStr(SEC_SEARCHPATH, KEY_WINFMPDLL_X86, pszPath, nSize * sizeof(wchar_t), WINFMPDLL_X86_DEFAULT);
#endif
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall kbfmpmd_GetRythmPath(IKpiConfig* pConfig, wchar_t* pszPath, int nSize)
{//リズム音源のパスを取得
 //nSize は文字数、GetStr に渡すサイズはバッファサイズ
    pConfig->GetStr(SEC_SEARCHPATH, KEY_RHYTHMPATH, pszPath, nSize * sizeof(wchar_t), RHYTHMPATH_DEFAULT);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall kbfmpmd_GetPcmDirs(IKpiConfig* pConfig, wchar_t** ppszPath, int nSize)
{//PCM の検索パスを取得
 //パス名の末尾に \ がなければ付加
 //重複設定されてる場合は除去
 //空文字列のパスは含めない
    int i;
    for (i = 0; i < nSize; i++) {
        ppszPath[i] = NULL;
    }
    if (nSize <= FMPMD_MAX_PCMDIR) {
        return;
    }
    typedef wchar_t fname_t[MAX_PATH];
    fname_t* pszPcmDirs = (fname_t*)malloc(sizeof(fname_t) * FMPMD_MAX_PCMDIR);
    for (i = 0; i < FMPMD_MAX_PCMDIR; i++) {
        wchar_t szKeyPcmPath[128];
        _snwprintf_s(szKeyPcmPath, _TRUNCATE, L"%s%d", KEY_PCMPATH, i);
        pConfig->GetStr(SEC_SEARCHPATH, szKeyPcmPath, pszPcmDirs[i], MAX_PATH * sizeof(wchar_t), L"");
        if (pszPcmDirs[i][0]) {//重複するフォルダ名を除く
            wchar_t* delimiter = wcsrchr(pszPcmDirs[i], L'\\');
            if (!delimiter || delimiter[1]) {//末尾が \ でない
                wcsncat_s(pszPcmDirs[i], L"\\", _TRUNCATE);//末尾に \ 付加
            }
            int j;
            for (j = 0; j < i; j++) {//重複するものを除く
                if (_wcsicmp(pszPcmDirs[i], pszPcmDirs[j]) == 0) {
                    pszPcmDirs[i][0] = 0;
                    break;
                }
            }
        }
    }
    int nPcmDirs = 0;
    for (i = 0; i < FMPMD_MAX_PCMDIR; i++) {
        if (!pszPcmDirs[i][0]) {//空文字列のパスは含めない
            continue;
        }
        ppszPath[nPcmDirs] = _wcsdup(pszPcmDirs[i]);
        nPcmDirs++;
    }
    ppszPath[nPcmDirs] = NULL;
    free(pszPcmDirs);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall kbfmpmd_GetPartMaskStr(IKpiConfig* pConfig, const wchar_t* cszSection, const wchar_t* cszKey,
    wchar_t* pszMask, int nSize, int nDigits)
{
    wchar_t szMask[32];
    pConfig->GetStr(cszSection, cszKey, szMask, sizeof(szMask), L"");
    CheckMaskStr(pszMask, nSize, szMask, nDigits);
}
//////////////////////////////////////////////////////////////////////////////
