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

#include "kpi.h"
#include "kbsnesapu_config.h"
#include "snesapu.dll/SNESAPU.H"

//セクション名
static const wchar_t SEC_GENERAL[] = L"General";
static const wchar_t SEC_TEMPORARY[] = L"Temporary";
//同梱の SNESAPU.DLL を使う
static const wchar_t KEY_IGNORE_DLLPATH[] = L"IgnoreDllPath";
static const int IGNORE_DLLPATH_DEFAULT = 1;
static const wchar_t SZ_IGNORE_DLLPATH_DEFAULT[] = L"1";
//SNESAPU.DLL のパス
static const wchar_t KEY_DLLPATH[] = L"DllPath";
static const wchar_t DLLPATH_DEFAULT[] = L"";
#define SZ_DLLPATH_DEFAULT DLLPATH_DEFAULT
//補間処理
static const wchar_t KEY_INTERPOLATION[] = L"Interpolation";
static const int INT_DEFAULT = 4;  //=INT_SYNC
static const wchar_t SZ_INT_DEFAULT[] = L"4" ;
//シーク制御
static const wchar_t KEY_SEEK_CTRL[] = L"SeekControl";
static const int SEEK_CTRL_DEFAULT = 30;
static const wchar_t SZ_SEEK_CTRL_DEFAULT[] = L"30";
static const int SEEK_CTRL_MIN = 0;
static const wchar_t SZ_SEEK_CTRL_MIN[] = L"0";
static const int SEEK_CTRL_MAX = 9999;
static const wchar_t SZ_SEEK_CTRL_MAX[] = L"9999";
//AmpControl
static const wchar_t KEY_REPLAYGAIN[] = L"ReplayGain";
static const int REPLAYGAIN_DEFAULT = 0;
static const wchar_t SZ_REPLAYGAIN_DEFAULT[] = L"0";
//Default Amplification
static const wchar_t KEY_DEFAULT_AMP[] = L"DefaultAmp";
static const int DEFAULT_AMP_DEFAULT = 65536;
static const wchar_t SZ_DEFAULT_AMP_DEFAULT[] = L"65536";
static const int DEFAULT_AMP_MIN = 32768;
static const wchar_t SZ_DEFAULT_AMP_MIN[] = L"32768";
static const int DEFAULT_AMP_MAX = 524288;
static const wchar_t SZ_DEFAULT_AMP_MAX[] = L"524288";
//XID6 の音量値を使う
static const wchar_t KEY_USE_XID6AMP[] = L"UseXID6Amp";
static const int USE_XID6AMP_DEFAULT = 1;
static const wchar_t SZ_USE_XID6AMP_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";
//実機ローパスフィルタ
static const wchar_t KEY_LPF[] = L"LPF";
static const int LPF_DEFAULT = 1;
static const wchar_t SZ_LPF_DEFAULT[] = L"1";
//実機エコー/FIR 処理 (旧「エコー作業メモリ書き換え」)
static const wchar_t KEY_ECHOFIR[] = L"EchoFir"; //旧EchoMem
static const int ECHOFIR_DEFAULT = 0;  //false が音質重視
static const wchar_t SZ_ECHOFIR_DEFAULT[] = L"0";
//サラウンド無効
static const wchar_t KEY_NOSURROUND[] = L"NoSurround";
static const int NOSURROUND_DEFAULT = 1;//true が音質重視
static const wchar_t SZ_NOSURROUND_DEFAULT[] = L"1";
//
//Temporaryセクション(一定時間経過後初期値に戻す)
//
static const wchar_t KEY_RESET[] = L"Reset";
static const int RESET_DEFAULT = 1;
static const wchar_t SZ_RESET_DEFAULT[] = L"1";
//再生速度
static const wchar_t KEY_SPEED[] = L"Speed";
static const int SPEED_DEFAULT = 65536;
static const wchar_t SZ_SPEED_DEFAULT[] = L"65536";
static const int SPEED_MIN = 4096;
static const wchar_t SZ_SPEED_MIN[] = L"4096";
static const int SPEED_MAX = 1048576;
static const wchar_t SZ_SPEED_MAX[] = L"1048576";
//音程
static const wchar_t KEY_PITCH[] = L"Pitch";
static const int PITCH_DEFAULT = 32000;
static const wchar_t SZ_PITCH_DEFAULT[] = L"32000";
static const int PITCH_MIN = 8000;
static const wchar_t SZ_PITCH_MIN[] = L"8000";
static const int PITCH_MAX = 128000;
static const wchar_t SZ_PITCH_MAX[] = L"128000";
//左右拡散度
static const wchar_t KEY_DSPSTEREO[] = L"DspStereo";
static const int DSPSTEREO_DEFAULT = 32768;
static const wchar_t SZ_DSPSTEREO_DEFAULT[] = L"32768";
static const int DSPSTEREO_MIN = 0;
static const wchar_t SZ_DSPSTEREO_MIN[] = L"0";
static const int DSPSTEREO_MAX = 65536;
static const wchar_t SZ_DSPSTEREO_MAX[] = L"65536";
//フィードバック反転度
static const wchar_t KEY_DSPEFBCT[] = L"DspEFBCT";
static const int  DSPEFBCT_DEFAULT = 32768;
static const wchar_t SZ_DSPEFBCT_DEFAULT[] = L"32768";
static const int DSPEFBCT_MIN = -32768;
static const wchar_t SZ_DSPEFBCT_MIN[] = L"-32768";
static const int DSPEFBCT_MAX = 32768;
static const wchar_t SZ_DSPEFBCT_MAX[] = L"32768";
//BASS BOOST
static const wchar_t KEY_BASSBOOST[] = L"BassBoost";
static const int BASSBOOST_DEFAULT = 0;
static const wchar_t SZ_BASSBOOST_DEFAULT[] = L"0";
//過去の ADPCM 互換
static const wchar_t KEY_OLDADPCM[] = L"OldADPCM";
static const int OLDADPCM_DEFAULT = 0;
static const wchar_t SZ_OLDADPCM_DEFAULT[] = L"0";
//左右反転
static const wchar_t KEY_REVERSE[] = L"Reverse";
static const int REVERSE_DEFAULT = 0;
static const wchar_t SZ_REVERSE_DEFAULT[] = L"0";
//逆位相サラウンド強制
static const wchar_t KEY_SURROUND[] = L"Surround";
static const int SURROUND_DEFAULT = 0;
static const wchar_t SZ_SURROUND_DEFAULT[] = L"0";
//エコー無効
static const wchar_t KEY_NOECHO[] = L"NoEcho";
static const int NOECHO_DEFAULT = 0;
static const wchar_t SZ_NOECHO_DEFAULT[] = L"0";
//ピッチモジュレーション無効
static const wchar_t KEY_NOPITCHMOD[] = L"NoPicthMod";
static const int NOPITCHMOD_DEFAULT = 0;
static const wchar_t SZ_NOPITCHMOD_DEFAULT[] = L"0";
//ピッチベンド無効
static const wchar_t  KEY_NOPITCHREAD[] = L"NoPicthRead";
static const int NOPITCHREAD_DEFAULT = 0;
static const wchar_t SZ_NOPITCHREAD_DEFAULT[] = L"0";
//FIRフィルタ無効
static const wchar_t KEY_NOFIR[] = L"NoFIR";
static const int NOFIR_DEFAULT = 0;
static const wchar_t SZ_NOFIR_DEFAULT[] = L"0";
//エンベロープ無効
static const wchar_t KEY_NOENV[] = L"NoEnv";
static const int NOENV_DEFAULT = 0;
static const wchar_t SZ_NOENV_DEFAULT[] = L"0";
//ノイズ無効
static const wchar_t KEY_NONOISE[] = L"NoNoise";
static const int NONOISE_DEFAULT = 0;
static const wchar_t SZ_NONOISE_DEFAULT[] = L"0";
//エンベロープ速度を同期(SNESAPU.DLL v2.19.3以降)
static const wchar_t KEY_ENVSPD[] = L"EnvSpd";
static const int ENVSPD_DEFAULT = 0;
static const wchar_t SZ_ENVSPD_DEFAULT[] = L"0";
//チャンネルミュート
static const wchar_t KEY_CH_MUTE[] = L"ChMute";
static const wchar_t CH_MUTE_DEFAULT[] = L"00000000";
//static const wchar_t SZ_CH_MUTE_DEFAULT = CH_MUTE_DEFAULT;
#define SZ_CH_MUTE_DEFAULT CH_MUTE_DEFAULT
//チャンネルノイズ
static const wchar_t KEY_CH_NOISE[] = L"ChNoize";
static const wchar_t CH_NOISE_DEFAULT[] = L"00000000";
//static const wchar_t SZ_CH_NOISE_DEFAULT[] = CH_NOISE_DEFAULT;
#define SZ_CH_NOISE_DEFAULT CH_NOISE_DEFAULT

//Temporary セクションの最終更新時刻
static const wchar_t KEY_LASTUPDATED[] = L"LastUpdated";

static void __fastcall CheckTemporary(IKpiConfig *pConfig);
static void __fastcall CheckChMaskStr(wchar_t* pszDest, int nDestSize, const wchar_t* cszSrc);//nDestSize >= 9 でなければならない

void __fastcall CheckTemporary(IKpiConfig* pConfig)
{//Temporary セクションの最終更新時刻から 24 時間以上経過していたら
 //Temporary セクションの設定を初期値に戻す
 //ファイルオープン(Open)時と列挙(EnumConfig)時に呼び出す
    static const struct
    {
        const wchar_t* cszKey;
        int nDefault;
        const wchar_t* cszDefault;
    }def_values[] = {
        {KEY_BASSBOOST, BASSBOOST_DEFAULT, NULL},
        {KEY_OLDADPCM,  OLDADPCM_DEFAULT, NULL},
        {KEY_REVERSE,   REVERSE_DEFAULT, NULL},
        {KEY_SURROUND,  SURROUND_DEFAULT, NULL},
        {KEY_NOECHO,    NOECHO_DEFAULT, NULL},
        {KEY_NOPITCHMOD,  NOPITCHMOD_DEFAULT, NULL},
        {KEY_NOPITCHREAD, NOPITCHREAD_DEFAULT, NULL},
        {KEY_NOFIR,     NOFIR_DEFAULT, NULL},
        {KEY_NOENV,     NOENV_DEFAULT, NULL},
        {KEY_NONOISE,   NONOISE_DEFAULT, NULL},
        //{KEY_ECHOMEM,   ECHOMEM_DEFAULT, NULL}, //KEY_ECHOFIR に変更して General セクションに移動
        {KEY_SPEED,     SPEED_DEFAULT, NULL},
        {KEY_PITCH,     PITCH_DEFAULT, NULL},
        {KEY_DSPSTEREO, DSPSTEREO_DEFAULT, NULL},
        {KEY_DSPEFBCT,  DSPEFBCT_DEFAULT, NULL},
        {KEY_ENVSPD,    ENVSPD_DEFAULT, NULL},
        {KEY_CH_MUTE,   0, CH_MUTE_DEFAULT},
        {KEY_CH_NOISE,  0, CH_NOISE_DEFAULT},
        {NULL, NULL}
    };
#ifndef _DEBUG
    static const UINT64 qwResetTime = 24ui64 * 60 * 60 * 1000 * 10000;//24時間
#else
    static const UINT64 qwResetTime = 1ui64 * 60 * 1000 * 10000;//1分
#endif
    BOOL bReset = pConfig->GetInt(SEC_TEMPORARY, KEY_RESET, RESET_DEFAULT) != 0;
    if (!bReset) {
        return;
    }
    UINT64 qwLastUpdated = pConfig->GetInt(SEC_TEMPORARY, KEY_LASTUPDATED, 0);
    if (qwLastUpdated == 0) {
        return;
    }
    SYSTEMTIME st;
    FILETIME ft;
    GetLocalTime(&st);
    SystemTimeToFileTime(&st, &ft);
    UINT64 qwElapsed = *(UINT64*)&ft - qwLastUpdated;
    if (qwElapsed > qwResetTime) {
        for (int i = 0; def_values[i].cszKey; i++) {
            if (def_values[i].cszDefault) {
                pConfig->SetStr(SEC_TEMPORARY,
                    def_values[i].cszKey,
                    def_values[i].cszDefault);
            }
            else {
                pConfig->SetInt(SEC_TEMPORARY,
                    def_values[i].cszKey,
                    def_values[i].nDefault);
            }
        }
        pConfig->SetInt(SEC_TEMPORARY, KEY_LASTUPDATED, 0);
    }
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall CheckChMaskStr(wchar_t* pszDest, int nDestSize, const wchar_t* cszSrc)
{//8桁のチャンネルマスクの文字列を検証
 //0 と 1 以外の文字を含む場合、数字であれば 1 に置換、数字以外なら 0 に置換
 //9桁目以降の文字は無視
 //8桁未満の場合、以降は 0 に置換(最大8桁)
    if (nDestSize < 9) {
        pszDest[0] = 0;
        return;
    }
    wcsncpy_s(pszDest, nDestSize, cszSrc, _TRUNCATE);
    int i;
    for (i = 0; i < 8; i++) {
        if (!pszDest[i]) {
            for (int j = i; j < 8; 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[8] = 0;
}
//////////////////////////////////////////////////////////////////////////////
BOOL __fastcall kbsnesapu_EnumConfig(IKpiConfig *pConfig, IKpiConfigEnumerator *pEnumerator)
{
    static const KPI_CFG_SECTION sec[] = {
        {SEC_GENERAL, NULL, L"kbsnesapu.kpi に関する一般的な設定です。"},
        {SEC_TEMPORARY, L"一時的な設定",
            L"色々設定を変えて見ると面白いかもしれませんが、通常、あまり常用すべきではないため、"
            L"変更から 24 時間経過したら初期値に戻します。"
            L"初期値に戻さないように設定することも出来ます。"},
        {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_IGNORE_DLLPATH, L"同梱の SNESAPU.DLL を使う",
                SZ_IGNORE_DLLPATH_DEFAULT, NULL, NULL, //default/min/max
                NULL, //list
                NULL, //listdesc
                L"true にするとプラグインに同梱の SNESAPU.DLL を使用します。\r\n"
                L"false にすると「SNESAPU.DLL のパス」で指定する DLL を使用します。\r\n"
                L""},//help
            {KPI_CFG_TYPE_FILE, SEC_GENERAL, KEY_DLLPATH, L"SNESAPU.DLL のパス",
                SZ_DLLPATH_DEFAULT, L"SNESAPU.DLL(*.dll)\t*.dll", NULL, //default/min/max
                L"", //list
                L"プラグイン同梱の SNESAPU.DLL", //listdesc
                L"SNESAPU.DLL のパスを指定します。\r\n"
                L"「同梱の SNESAPU.DLL を使う」が false のときに有効です。\r\n"
                L"同梱のより新しい SNESAPU.DLL を使用したいがプラグインと同じフォルダにコピーするのが煩わしい場合に指定します。\r\n"
                L"未指定にするとプラグイン同梱の SNESAPU.DLL を使用します。\r\n"
                L"(「同梱の SNESAPU.DLL を使う」が true のときと同じ動作となります)\r\n"
                L"\r\n"
                L""},//help
            {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_INTERPOLATION, L"補間処理",
                SZ_INT_DEFAULT, NULL, NULL, //default/min/max
                L"0\t1\t2\t3\t4\t7", //list
                L"なし\t線形補間\t三次スプライン補間\t実機ガウス分布補間\tシンク関数補間\t高精度ガウス分布補間", //listdesc
                L"0: なし\r\n"
                L"1: 線形補間\r\n"
                L"2: 三次スプライン補間\r\n"
                L"3: 実機ガウス分布補間\r\n"
                L"4: シンク関数補間\r\n"
                L"7: 高精度ガウス分布補間\r\n"
                L"\r\n"
                L"音質は良い順に 4 > 7 > 3 > 2 > 1 > 0 です。\r\n"
                L"実際には好みによっても変わってくるかと思います。\r\n"
                L"3(実機ガウス分布補間)が最も実機に近いです。\r\n"
                L"0(なし)はお勧めしません。"},//help
            {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_SEEK_CTRL, L"シーク制御(秒)",
                SZ_SEEK_CTRL_DEFAULT, SZ_SEEK_CTRL_MIN, SZ_SEEK_CTRL_MAX, //default/min/max
                L"0\t10\t20\t30\t60\t120\t180\t300\t600\t9999", //list
                L"0(高速)\t10\t20\t30\t60\t120\t180\t300\t600\t9999(低速)", //listdesc
                L"シーク先が指定時間より遠い場合、途中までは高速にシークします。\r\n"
                L"例えば設定値が 30 秒のときに 90 秒先にシークする場合、60 秒先まで高速シーク、残りの 30 秒を低速シークします。"
                L"この例では 30 秒以下の曲では常に低速シークとなります。\r\n"
                L"値を小さくするほどシークが高速になりますが、データによってはシーク後の演奏が不正確になります。"}, //help
            {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_REPLAYGAIN, L"リプレイゲイン",
                SZ_REPLAYGAIN_DEFAULT, NULL, NULL, //default/min/max
                NULL, //list
                NULL, //listdesc
                L"XID6 の音量値(または音量既定値)をリプレイゲインとして本体に通知するかの設定です。\r\n"
                L"true にするとリプレイゲイン(TrackGain)として通知します。プレイヤーがリプレイゲインを有効にしたときのみ、再生に反映されます。\r\n"
                L"false にするとプレイヤーの設定に関わりなく XID6 の音量値(または音量既定値)が再生に反映されます。",
                },//help
            {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_DEFAULT_AMP, L"音量既定値",
                SZ_DEFAULT_AMP_DEFAULT, SZ_DEFAULT_AMP_MIN, SZ_DEFAULT_AMP_MAX, //default/min/max
                L"65536\t81920\t98304\t114688\t131072\t163840\t196608",  //list
                L"65536\t81920(125%)\t98304(150%)\t114688(175%)\t131072(200%)\t163840(250%)\t196608(300%)",  //listdesc
                L"XID6 に音量値がない場合の既定値です。\r\n"
                L"「XID6 の音量値を使う」が false の場合もここで設定した値が適用されます。\r\n"
                L"65536 より大きくすると音量を増幅しますが、大きくし過ぎると音割れして音質が著しく劣化することがあります。\r\n"
                L"再生への反映のされ方は「リプレイゲイン」に従います。\r\n"}, //help
            {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_USE_XID6AMP, L"XID6 の音量値を使う",
                SZ_USE_XID6AMP_DEFAULT, NULL, NULL, //default/min/max
                NULL, //list
                NULL, //listdesc
                L"true にすると XID6 に音量値を含む場合はその値を使用します。\r\n"
                L"false にすると値は使用されず、「音量既定値」を適用します。\r\n"
                L"XID6 に音量値が含まれていても、その値は使わずに「音量既定値」の方を使いたい場合に false にします。\r\n"
                L"再生への反映のされ方は「リプレイゲイン」に従います。"}, //help
            {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_SAMPLERATE, L"再生周波数(Hz)",
                SZ_SAMPLERATE_DEFAULT, L"-192000", L"192000", //default/min/max
                L"32000\t44100\t48000\t88200\t96000\t176400\t192000", //list
                L"32000(実機と同じ)", //listdesc
                L"プレイヤーからの要求が 0(既定値)の場合の再生周波数です。\r\n"
                L"32000 が実機に最も近いですが、デバイスによっては WASAPI や ASIO では"
                L"再生出来ないことがあります。DirectSound/waveOut であれば通常は再生出来ます。\r\n"
                L"\r\n"
                L"DirectSound/waveOut 使用時は 32000Hz で再生し、WASAPI/ASIO 時はデバイスが対応する"
                L"再生周波数で再生したい場合は以下のようにします。\r\n"
                L"\r\n"
                L"1.この設定値を 32000(実機と同じ)にする\r\n"
                L"2.プレイヤーのデバイス設定で DirectSound/waveOut の「プラグインへの再生周波数要求」を 0 にする\r\n"
                L"3.プレイヤーのデバイス設定で WASAPI/ASIO の「プラグインへの再生周波数」をデバイスが対応する値にする\r\n"
                L"\r\n"
                L"32000Hz だと WASAPI/ASIO で再生出来ない環境で DirectSound/waveOut で 32000Hz で"
                L"再生する場合、実際には OS のカーネルミキサが再生周波数変換を行っています。\r\n"
                L"SNESAPU.DLL の再生周波数変換機能よりもカーネルミキサの方が優秀だと思う場合は"
                L"32000Hz で DirectSound/waveOut を使用し、SNESAPU.DLL の方が優秀だと思う場合は "
                L"WASAPI/ASIO で対応する再生周波数で再生すると良いです。\r\n"
                L"\r\n"
                L"負の値を指定するとプレイヤーからの要求を無視します。例えば -32000 にすると 44100Hz で要求されても 32000Hz で再生します。\r\n"
                }, //help

        //SetApuOpt opts(「実機ローパスフィルタ」「実機エコー/FIR処理」「サラウンド無効」以外は指定時間経過後リセット)
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_LPF, L"実機ローパスフィルタ",
            SZ_LPF_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
             L"true にすると実機と同じカットオフ周波数のローパスフィルタをかけます。\r\n"
             L"SNESAPU.DLL 制作者様の推奨設定は true だそうです。\r\n"},
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_ECHOFIR, L"実機エコー/FIR 処理",
             SZ_ECHOFIR_DEFAULT, NULL, NULL, //default/min/max
             NULL, NULL, //list/listdesc
             L"true にするとエコーと FIR フィルタの動作を可能な限り実機に近づけます。\r\n"
             L"true：\r\n"
             L"  エコー作業用メモリ：64KB RAM にエコー処理途中の結果を書き込みます\r\n"
             L"  FIR フィルタ：16bit エミュレーション\r\n"
             L"false：\r\n"
             L"  エコー作業用メモリ：64KB RAM にエコー処理途中の結果を書き込みません\r\n"
             L"  FIR フィルタ：17bit エミュレーション、オーバーフロー処理あり(高音質)\r\n"
             L"SNESAPU.DLL 制作者様の推奨設定は false だそうです。\r\n"
             L"音質が実機に近いのは true です。\r\n"
             },
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_NOSURROUND, L"サラウンド無効",
            SZ_NOSURROUND_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると元々曲自体に設定されている逆位相サラウンド設定を無視します。\r\n"
            L"SNESAPU.DLL 制作者様の推奨設定は true (無効にする)だそうです。"},

        //ここから下は指定時間経過後リセット
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_RESET, L"24時間後に初期値に戻す",
            SZ_RESET_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"Temporary セクションの設定は常用するべきではありません。true にすると"
            L"設定を更新してから 24 時間後に初期値に戻します。初期値に戻したくない場合は false にします。"},
        {KPI_CFG_TYPE_INT, SEC_TEMPORARY, KEY_SPEED, L"再生速度",
            SZ_SPEED_DEFAULT, SZ_SPEED_MIN, SZ_SPEED_MAX, //default/min/max
            L"16384\t32768\t49152\t65536\t81920\t98304\t131072\t196608\t262144", //list
            L"16384(x0.25)\t32768(x0.5)\t49152(x0.75)\t65536(x1.0)\t81920(x1.25)\t98304(x1.5)\t131072(x2.0)\t196608(x3.0)\t262144(x4.0)", //listdesc
            L"再生速度を指定します。プレイヤー本体の再生速度変更機能よりも高音質低負荷ですが、再生位置の進み方が速度に連動しません。"
            L"例えば2分の曲を倍速再生しても1分で演奏は終わらず、2分間再生されます。"},
        {KPI_CFG_TYPE_INT, SEC_TEMPORARY, KEY_PITCH, L"音程",
            SZ_PITCH_DEFAULT, SZ_PITCH_MIN, SZ_PITCH_MAX, //default/min/max
            L"8000\t16000\t24000\t32000\t40000\t48000\t64000\t96000\t128000", //list
            L"8000(x0.25)\t16000(x0.5)\t24000(x0.75)\t32000(x1.0)\t40000(x1.25)\t48000(x1.5)\t64000(x2.0)\t96000(x3.0)\t128000(x4.0)", //listdesc
            L"音程を指定します。プレイヤー本体の音程変更機能よりも高音質低負荷です。"},
        {KPI_CFG_TYPE_INT, SEC_TEMPORARY, KEY_DSPSTEREO, L"左右拡散度",
            SZ_DSPSTEREO_DEFAULT, SZ_DSPSTEREO_MIN, SZ_DSPSTEREO_MAX, //default/min/max
            L"0\t6554\t13107\t16384\t19661\t21627\t26214\t32768\t39322\t43909\t45875\t49152\t52429\t58982\t65536", //list
            L"0%(≒モノラル)\t10%\t20%\t25%\t30%\t33%\t40%\t50%(標準)\t60%\t67%\t70%\t75%\t80%\t90%\t100%(拡散)", //listdesc
            L"左右拡散度 (パンの割り振りの度合い) を設定します。\r\n"
            L"  65536(100%): パンの割り振りを強調します。\r\n"
            L"  32768(50%) : パンの割り振りを標準レベルで行います。(通常)\r\n"
            L"      0(0%)  : パンの割り振りを行いません。(≒モノラル)"},
        { KPI_CFG_TYPE_INT, SEC_TEMPORARY, KEY_DSPEFBCT, L"フィードバック反転度",
            SZ_DSPEFBCT_DEFAULT, SZ_DSPEFBCT_MIN, SZ_DSPEFBCT_MAX, //default/min/max
            L"32768\t26214\t19661\t16384\t13107\t11141\t6554\t0\t-6554\t-11141\t-13107\t-16384\t-19661\t-26214\t-32768", //list
            L"0%(標準)\t10%\t20%\t25%\t30%\t33%\t40%\t50%(混合)\t60%\t67%\t70%\t75%\t80%\t90%\t100%(反転)", //listdesc
            L"エコーフィードバック反転度を設定します。\r\n"
            L"  32768(0%)  : 左右別々にエコーがかかります。(通常)\r\n"
            L"      0(50%) : 左右両方に同じエコーがかかります。\r\n"
            L" -32768(100%): 左右にかかるエコーが反転します。\r\n"
            L"エコーフィードバック反転度を強くすると(設定値を-32768に近づけると)\r\n"
            L"エコーがかかった際に音の幅が広がります。"},

        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_BASSBOOST, L"BASS BOOST",
            SZ_BASSBOOST_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると低音域を強調して重低音で出力します。 強調する強度は固定です。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_OLDADPCM, L"過去の ADPCM 互換",
            SZ_OLDADPCM_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると古い波形復元ロジック(BRR 復元)を使用します。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_REVERSE, L"左右反転",
            SZ_REVERSE_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると左右の出力を反対に設定します。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_SURROUND, L"逆位相サラウンド強制",
            SZ_SURROUND_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると左の出力だけ逆位相に設定してサラウンド効果をオンにします。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_NOECHO, L"エコー無効",
            SZ_NOECHO_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると元々曲自体に設定されているエコー設定を無視します。"
            L"プレイヤー側でエコーを有効にする場合は true にすることをお勧めします。"
            L"(二重にエコーがかかるのを防ぐため)"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_NOPITCHMOD, L"ピッチモジュレーション無効",
            SZ_NOPITCHMOD_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると元々曲自体に設定されているピッチモジュレーション設定を無視します。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_NOPITCHREAD, L"ピッチベンド無効",
            SZ_NOPITCHREAD_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると発音中の音程変更を無視します。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_NOFIR, L"FIR フィルタ無効",
            SZ_NOFIR_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にするとエコーで使用される FIR フィルタ処理を無視します。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_NOENV, L"エンベロープ無効",
            SZ_NOENV_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると元々曲自体に設定されているエンベロープ設定を無視して、 75 ％出力に固定します。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_NONOISE, L"ノイズ無効",
            SZ_NONOISE_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にすると元々曲自体に設定されているノイズ設定を無視します。"},
        //        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_ECHOMEM, L"エコー作業メモリ書き換え",  //KEY_ECHOFIR に変更して Generalセクションに移動
        //            SZ_ECHOMEM_DEFAULT, NULL, NULL, //default/min/max
        //            NULL, NULL, //list/listdesc
        //            L"true にすると64KB RAM 内にエコーで使用した内部メモリを格納して実機の動作に近づけます。"},
        {KPI_CFG_TYPE_BOOL, SEC_TEMPORARY, KEY_ENVSPD, L"エンベロープ速度を同期",
            SZ_ENVSPD_DEFAULT, NULL, NULL, //default/min/max
            NULL, NULL, //list/listdesc
            L"true にするとエンベロープの変化量を演奏速度に合わせます。\r\n"
            L"SNESAPU.DLL v2.19.3 以降で対応します。"},
        {KPI_CFG_TYPE_STR, SEC_TEMPORARY, KEY_CH_MUTE, L"チャンネルミュート",
            SZ_CH_MUTE_DEFAULT, NULL, L"9", //default/min/max(終端の 0 を含めて最大9文字)
            L"00000000", L"全てオフ", //list/listdesc
            L"ミュートするチャンネル(1〜8まで)を指定します。\r\n"
            L"例えばチャンネル1とチャンネル3をミュートする場合\r\n"
            L"10100000\r\n"
            L"のように指定します。" },
       { KPI_CFG_TYPE_STR, SEC_TEMPORARY, KEY_CH_NOISE, L"チャンネルノイズ",
            SZ_CH_NOISE_DEFAULT, NULL, L"9", //default/min/max(終端の 0 を含めて最大9文字)
            L"00000000", L"全てオフ", //list/listdesc
            L"強制ノイズをかけるチャンネル(1〜8まで)を指定します。\r\n"
            L"例えばチャンネル1とチャンネル3にノイズをかける場合\r\n"
            L"10100000\r\n"
            L"のように指定します。" },
       {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
    };
    //EnumSection と EnumKey の呼び出し順序は任意だが、本体の実装上は
    //EnumSection を先に呼んだ方がほんの少しだけ効率的となる。
    for (int i = 0; sec[i].cszSection; i++) {
        pEnumerator->EnumSection(&sec[i]);
    }
    for (int i = 0; key[i].cszSection; i++) {
        pEnumerator->EnumKey(&key[i]);
    }
    CheckTemporary(pConfig);
    return TRUE;
}
//////////////////////////////////////////////////////////////////////////////
DWORD __fastcall kbsnesapu_ApplyConfig(IKpiConfig *pConfig,
                              const wchar_t *cszSection, //セクション名
                              const wchar_t *cszKey, //キー名
                              INT64  nValue, //変更後の値(BOOL/INT の場合)
                              double dValue, //変更後の値(FLOAT の場合)
                              const wchar_t *cszValue)//変更後の値(文字列)
{
    static const struct CFGRET {//戻り値のリスト
        const wchar_t* cszKey;
        DWORD dwRet;
    }cfg_returns[] = {
        {KEY_IGNORE_DLLPATH, KPI_CFGRET_RELOAD_KPI},
        {KEY_DLLPATH,       KPI_CFGRET_RELOAD_KPI},
        {KEY_REPLAYGAIN,    KPI_CFGRET_RELOAD_DATA},
        {KEY_DEFAULT_AMP,   KPI_CFGRET_NOTIFY}, //KEY_REPLAYGAIN の設定によって変化
        {KEY_USE_XID6AMP,   KPI_CFGRET_NOTIFY}, //KEY_REPLAYGAIN の設定によって変化
        {KEY_SAMPLERATE,    KPI_CFGRET_RELOAD_DATA},
        {NULL,              KPI_CFGRET_NOTIFY},//上記以外全て KPI_CFGRET_NOTIFY
    };
    if (_wcsicmp(cszKey, KEY_SAMPLERATE) == 0) {//再生周波数
        //絶対値が 8000 より小さい場合は 8000 とする
        //絶対値が 192000 より大きい場合は 192000 とする
        INT64 nSampleRate = nValue;
        if (nSampleRate < 0) {
            if (nSampleRate > -8000) {
                nSampleRate = -8000;
            }
            else if (nSampleRate < -192000) {
                nSampleRate = -192000;
            }
        }
        else if (nSampleRate < 8000) {
            nSampleRate = 8000;
        }
        else if (nSampleRate > 192000) {
            nSampleRate = 192000;
        }
        pConfig->SetInt(SEC_GENERAL, KEY_SAMPLERATE, nSampleRate);
    }
    else if (_wcsicmp(cszKey, KEY_DEFAULT_AMP) == 0 ||//音量既定値
             _wcsicmp(cszKey, KEY_USE_XID6AMP) == 0) { //XID6 の音量値を使う
        //リプレイゲインを使うかどうかで戻り値判定
        DWORD dwRet = KPI_CFGRET_NOTIFY;
        BOOL bReplayGain = pConfig->GetInt(SEC_GENERAL, KEY_REPLAYGAIN, REPLAYGAIN_DEFAULT) != 0;
        if (bReplayGain) {//リプレイゲインを使う場合は開き直しが必要
            dwRet = KPI_CFGRET_RELOAD_DATA;
        }
        pConfig->SetStr(cszSection, cszKey, cszValue);
        return dwRet;
    }
    else if (_wcsicmp(cszKey, KEY_CH_MUTE) == 0 || //チャンネルミュート
             _wcsicmp(cszKey, KEY_CH_NOISE) == 0) {//チャンネルノイズ
        wchar_t szCh[9];
        CheckChMaskStr(szCh, _countof(szCh), cszValue);//正しい文字列に置換
        pConfig->SetStr(cszSection, cszKey, szCh);
    }
    else {
        pConfig->SetStr(cszSection, cszKey, cszValue);
    }
    if (_wcsicmp(cszSection, SEC_TEMPORARY) == 0) {//Temporary セクション
        //最終更新時刻を保存
        BOOL bReset = pConfig->GetInt(SEC_TEMPORARY, KEY_RESET, RESET_DEFAULT) != 0;
        UINT64 qwCurrentTime = 0;
        if (bReset) {
            SYSTEMTIME st;
            FILETIME ft;
            GetLocalTime(&st);
            SystemTimeToFileTime(&st, &ft);
            qwCurrentTime = *(UINT64*)&ft;
        }
        pConfig->SetInt(SEC_TEMPORARY, KEY_LASTUPDATED, qwCurrentTime);
    }
    int i;
    for (i = 0; cfg_returns[i].cszKey; i++) {
        if (_wcsicmp(cfg_returns[i].cszKey, cszKey) == 0) {
            return cfg_returns[i].dwRet;
        }
    }
    return cfg_returns[i].dwRet;
}
//////////////////////////////////////////////////////////////////////////////
/*
struct kbsnesapu_Settings
{//Genearal
    int nSampleRate;   //再生周波数
    int nInterpolation;//補間処理
    int nDefaultAmp;   //音量既定値
    int nSeekControl;  //シーク制御
    bool bUseXid6Amp;  //XID6 の音量値を使う
    bool bReplayGain;  //リプレイゲイン
    bool bLPF;         //実機ローパスフィルタ
    bool bNoSurround;  //サラウンド無効
    bool bEchoFir;     //実機エコー/FIR 処理
 //Temporary
    wchar_t szChMute[9]; //チャンネルミュート
    wchar_t szChNoise[9];//チャンネルノイズ
    int nSpeed;    //再生速度
    int nPitch;    //音程
    int nDspStereo;//左右拡散度
    int nDspEFBCT;//フィードバック反転度
    bool bBassBoost;//BASS BOOST
    bool bOldADPCM; //過去の ADPCM 互換;
    bool bReverse;  //左右反転
    bool bSurround; //逆位相サラウンド強制;
    bool bNoEcho;   //エコー無効
    bool bNoPitchMod;//ピッチモジュレーション無効
    bool bNoPitchRead;//ピッチベンド無効
    bool bNoFIR;    //FIR フィルタ無効
    bool bNoEnv;    //エンベロープ無効
    bool bNoNoise;  //ノイズ無効
    bool bEnvSpd;   //エンベロープ速度を同期
};
*/
void __fastcall kbsnesapu_GetSettings(IKpiConfig *pConfig, kbsnesapu_Settings *pSettings)
{
    CheckTemporary(pConfig);
//General
    pSettings->nSampleRate = pConfig->GetInt(SEC_GENERAL, KEY_SAMPLERATE, SAMPLERATE_DEFAULT);
    if(pSettings->nSampleRate < -192000){
        pSettings->nSampleRate = -192000;
    }
    else if(pSettings->nSampleRate > 192000){
        pSettings->nSampleRate = 192000;
    }
    pSettings->nInterpolation = pConfig->GetInt(SEC_GENERAL, KEY_INTERPOLATION, INT_DEFAULT);
    switch(pSettings->nInterpolation){
        case INT_NONE:  
        case INT_LINEAR: 
        case INT_CUBIC: 
        case INT_GAUSS:
        case INT_SINC:  
        case INT_GAUSS4: 
            break;
        default:
            pSettings->nInterpolation = INT_DEFAULT;break;
    }
    pSettings->nDefaultAmp = pConfig->GetInt(SEC_GENERAL, KEY_DEFAULT_AMP, DEFAULT_AMP_DEFAULT);
    if(pSettings->nDefaultAmp < DEFAULT_AMP_MIN){
        pSettings->nDefaultAmp = DEFAULT_AMP_MIN;
    }
    else if(pSettings->nDefaultAmp > DEFAULT_AMP_MAX){
        pSettings->nDefaultAmp = DEFAULT_AMP_MAX;
    }
    pSettings->nSeekControl = pConfig->GetInt(SEC_GENERAL, KEY_SEEK_CTRL, SEEK_CTRL_DEFAULT);
    if(pSettings->nSeekControl > SEEK_CTRL_MAX){
        pSettings->nSeekControl = SEEK_CTRL_MAX;
    }
    else if(pSettings->nSeekControl < SEEK_CTRL_MIN){
        pSettings->nSeekControl = SEEK_CTRL_MIN;
    }
    pSettings->bUseXid6Amp = pConfig->GetInt(SEC_GENERAL, KEY_USE_XID6AMP, USE_XID6AMP_DEFAULT) != 0;
    pSettings->bReplayGain = pConfig->GetInt(SEC_GENERAL, KEY_REPLAYGAIN, REPLAYGAIN_DEFAULT) != 0;
    pSettings->bLPF = pConfig->GetInt(SEC_GENERAL, KEY_LPF, LPF_DEFAULT) != 0;
    pSettings->bNoSurround = pConfig->GetInt(SEC_GENERAL, KEY_NOSURROUND, NOSURROUND_DEFAULT) != 0;
    pSettings->bEchoFir = pConfig->GetInt(SEC_GENERAL, KEY_ECHOFIR, ECHOFIR_DEFAULT) != 0;
 //Temporary
    pSettings->nSpeed = pConfig->GetInt(SEC_TEMPORARY, KEY_SPEED, SPEED_DEFAULT);
    if (pSettings->nSpeed < SPEED_MIN) {
        pSettings->nSpeed = SPEED_MIN;
    }
    else if (pSettings->nSpeed > SPEED_MAX) {
        pSettings->nSpeed = SPEED_MAX;
    }
    pSettings->nPitch = pConfig->GetInt(SEC_TEMPORARY, KEY_PITCH, PITCH_DEFAULT);
    if (pSettings->nPitch < PITCH_MIN) {
        pSettings->nPitch = PITCH_MIN;
    }
    else if (pSettings->nPitch > PITCH_MAX) {
        pSettings->nPitch = PITCH_MAX;
    }
    pSettings->nDspStereo = pConfig->GetInt(SEC_TEMPORARY, KEY_DSPSTEREO, DSPSTEREO_DEFAULT);
    if (pSettings->nDspStereo < DSPSTEREO_MIN) {
        pSettings->nDspStereo = DSPSTEREO_MIN;
    }
    else if (pSettings->nDspStereo > DSPSTEREO_MAX) {
        pSettings->nDspStereo = DSPSTEREO_MAX;
    }
    pSettings->nDspEFBCT = pConfig->GetInt(SEC_TEMPORARY, KEY_DSPEFBCT, DSPEFBCT_DEFAULT);
    if (pSettings->nDspEFBCT < DSPEFBCT_MIN) {
        pSettings->nDspEFBCT = DSPEFBCT_MIN;
    }
    else if (pSettings->nDspEFBCT > DSPEFBCT_MAX) {
        pSettings->nDspEFBCT = DSPEFBCT_MAX;
    }
    pSettings->bBassBoost = pConfig->GetInt(SEC_TEMPORARY, KEY_BASSBOOST, BASSBOOST_DEFAULT) != 0;
    pSettings->bOldADPCM = pConfig->GetInt(SEC_TEMPORARY, KEY_OLDADPCM, OLDADPCM_DEFAULT) != 0;
    pSettings->bReverse = pConfig->GetInt(SEC_TEMPORARY, KEY_REVERSE, REVERSE_DEFAULT) != 0;
    pSettings->bSurround = pConfig->GetInt(SEC_TEMPORARY, KEY_SURROUND, SURROUND_DEFAULT) != 0;
    pSettings->bNoEcho = pConfig->GetInt(SEC_TEMPORARY, KEY_NOECHO, NOECHO_DEFAULT) != 0;
    pSettings->bNoPitchMod = pConfig->GetInt(SEC_TEMPORARY, KEY_NOPITCHMOD, NOPITCHMOD_DEFAULT) != 0;
    pSettings->bNoPitchRead = pConfig->GetInt(SEC_TEMPORARY, KEY_NOPITCHREAD, NOPITCHREAD_DEFAULT) != 0;
    pSettings->bNoFIR = pConfig->GetInt(SEC_TEMPORARY, KEY_NOFIR, NOFIR_DEFAULT) != 0;
    pSettings->bNoEnv = pConfig->GetInt(SEC_TEMPORARY, KEY_NOENV, NOENV_DEFAULT) != 0;
    pSettings->bNoNoise = pConfig->GetInt(SEC_TEMPORARY, KEY_NONOISE, NONOISE_DEFAULT) != 0;
    pSettings->bEnvSpd = pConfig->GetInt(SEC_TEMPORARY, KEY_ENVSPD, ENVSPD_DEFAULT) != 0;
    wchar_t szChMute[9];
    wchar_t szChNoise[9];
    pConfig->GetStr(SEC_TEMPORARY, KEY_CH_MUTE, szChMute, sizeof(szChMute), CH_MUTE_DEFAULT);
    pConfig->GetStr(SEC_TEMPORARY, KEY_CH_NOISE, szChNoise, sizeof(szChNoise), CH_NOISE_DEFAULT);
    CheckChMaskStr(pSettings->szChMute, _countof(pSettings->szChMute), szChMute);
    CheckChMaskStr(pSettings->szChNoise, _countof(pSettings->szChNoise), szChNoise);
}
//////////////////////////////////////////////////////////////////////////////
void __fastcall kbsnesapu_GetDllPath(IKpiConfig* pConfig, wchar_t* pszPath, int nSize)
{//nSize は文字数、GetStr に渡すサイズはバッファサイズ
    bool bIgnoreDllPath = pConfig->GetInt(SEC_GENERAL, KEY_IGNORE_DLLPATH, IGNORE_DLLPATH_DEFAULT) != 0;
    if(bIgnoreDllPath){
        pszPath[0] = 0;
    }
    else{
        pConfig->GetStr(SEC_GENERAL, KEY_DLLPATH, pszPath, nSize * sizeof(wchar_t), DLLPATH_DEFAULT);
    }
}
//////////////////////////////////////////////////////////////////////////////
