// test/test_synth_render_s16.c
#include "test.h"
#include "fluidsynth.h"
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <time.h>

#if defined(_WIN32)
#include <windows.h>
#endif

#define TEST_ASSERT_MSG(COND, MSG)                                                               \
    do                                                                                           \
    {                                                                                            \
        if (!(COND))                                                                             \
        {                                                                                        \
            fprintf(stderr, __FILE__ ":%d assertion (%s) failed: %s\n", __LINE__, #COND, (MSG)); \
            TEST_ABORT;                                                                          \
        }                                                                                        \
    } while (0)

/*
 * Deterministic regression test for fluid_synth_write_s16() output.
 *
 * Notes on determinism:
 * - We explicitly set a few synth settings that are common sources of drift
 *   (chorus/reverb/sample-rate/gain/interpolation).
 * - We hash output in a platform-stable way by hashing little-endian bytes
 *   of int16 samples (not native-endian raw memory).
 * - Dither: if s16 output includes dithering, it must be deterministic for
 *   this test to be stable; if you later change dithering behavior, update
 *   the expected hash.
 */

enum
{
    MAX_ABS_DELTA_ACCEPTABLE = 2, /* max absolute sample delta accepted in rendering vs reference */
    TEST_SRATE = 48000,
    FRAMES = 256, /* keep small to make the test fast */
    NOTE_CH = 0,
    NOTE_KEY = 60, /* middle C */
    NOTE_VEL = 100
};

/* Reference samples obtained from a Windows Visual Studio release build of ver. 2.5. */
/* Generated by test_synth_render_s16.c (Release (NDEBUG defined))                    */
static const int16_t REF_S16_INTERLEAVED_LR[512] =
{
    0,     0,     1,     0,     0,     1,     5,     4,     11,    11,    22,    23,    35,
    35,    50,    50,    61,    60,    67,    67,    65,    65,    55,    54,    32,    32,
    2,     2,     -31,   -31,   -63,   -63,   -85,   -85,   -88,   -88,   -67,   -66,   -12,
    -12,   78,    78,    201,   203,   356,   358,   533,   535,   718,   721,   897,   897,
    1050,  1051,  1164,  1164,  1223,  1222,  1221,  1219,  1153,  1151,  1027,  1023,  853,
    849,   654,   650,   455,   451,   287,   283,   177,   175,   154,   154,   239,   242,
    443,   448,   767,   774,   1196,  1206,  1709,  1720,  2266,  2277,  2825,  2835,  3337,
    3345,  3754,  3759,  4034,  4034,  4146,  4141,  4071,  4063,  3815,  3800,  3392,  3374,
    2845,  2823,  2225,  2205,  1601,  1581,  1043,  1027,  619,   609,   392,   387,   403,
    408,   675,   689,   1209,  1229,  1971,  2000,  2909,  2940,  3942,  3976,  4983,  5013,
    5747,  5770,  6279,  6294,  6526,  6530,  6455,  6445,  6064,  6042,  5381,  5348,  4462,
    4421,  3382,  3335,  2235,  2188,  1120,  1074,  129,   90,    -652,  -680,  -1161, -1177,
    -1365, -1367, -1253, -1240, -845,  -821,  -193,  -159,  630,   671,   1534,  1578,  2423,
    2463,  3199,  3233,  3777,  3800,  4092,  4100,  4100,  4092,  3787,  3762,  3169,  3131,
    2294,  2242,  1227,  1168,  58,    -5,    -1115, -1175, -2194, -2250, -3094, -3137, -3742,
    -3769, -4089, -4099, -4117, -4109, -3835, -3810, -3280, -3241, -2520, -2470, -1636, -1583,
    -729,  -676,  107,   153,   782,   815,   1220,  1238,  1369,  1367,  1199,  1178,  718,
    678,   -40,   -97,   -1016, -1085, -2127, -2202, -3279, -3353, -4372, -4439, -5317, -5372,
    -6034, -6071, -6461, -6479, -6568, -6564, -6354, -6328, -5847, -5802, -5102, -5043, -4198,
    -4130, -3226, -3156, -2283, -2219, -1465, -1412, -854,  -820,  -515,  -502,  -484,  -494,
    -769,  -802,  -1348, -1401, -2166, -2236, -3154, -3235, -4222, -4304, -5270, -5347, -6201,
    -6266, -6930, -6975, -7387, -7411, -7531, -7529, -7349, -7320, -6857, -6805, -6103, -6032,
    -5156, -5072, -4101, -4012, -3037, -2951, -2060, -1987, -1265, -1209, -717,  -685,  -469,
    -462,  -538,  -558,  -910,  -956,  -1549, -1615, -2388, -2468, -3342, -3429, -4315, -4399,
    -5210, -5282, -5931, -5985, -6408, -6436, -6584, -6583, -6436, -6407, -5971, -5913, -5222,
    -5141, -4251, -4152, -3139, -3031, -1983, -1874, -878,  -780,  76,    157,   805,   861,
    1248,  1275,  1375,  1371,  1189,  1154,  715,   656,   11,    -68,   -846,  -936,  -1761,
    -1853, -2638, -2719, -3379, -3444, -3904, -3942, -4148, -4156, -4078, -4052, -3685, -3627,
    -2996, -2909, -2060, -1951, -953,  -831,  232,   359,   1399,  1517,  2451,  2552,  3300,
    3376,  3879,  3924,  4149,  4159,  4096,  4071,  3738,  3681,  3122,  3041,  2318,  2220,
    1413,  1310,  508,   409,   -302,  -383,  -924,  -982,  -1294, -1318, -1361, -1348, -1109,
    -1060, -548,  -465,  278,   388,   1304,  1432,  2441,  2577,  3595,  3726,  4663,  4779,
    5555,  5646,  6202,  6260,  6547,  6568,  6567,  6547,  6267,  6212,  5686,  5597,  4883,
    4774,  3946,  3824,  2965,  2847,  2046,  1940,  1274,  1193,  733,   684,   476,   465,
    534,   565,   908,   976,   1563,  1665,  2444,  2569,  3469,  3606,  4542,  4680,  5569,
    5695,  6455,  6554,  7114,  7180,  7486,  7512,  7537,  7519,  7262,  7202,  6686,  6588,
    5864,  5737,  4872,  4729,  3801,  3655,  2750,  2613,  1814,  1700,  1081,  999,   615,
    575,   458,   461,   619,   666,   1078,  1164,  1785,  1900,  2671,  2805,  3645,  3782,
    4608,  4737,  5462,  5570,  6121,  6194,  6515,  6546,  6596,  6581,  6350,  6287,  5792,
    5686,  4966,  4826,  3940,  3778,
};

/* Tiny stats helper for s16 interleaved buffers: max error, RMS error, SNR (dB).
 * expected/got are int16_t arrays of length frames*2 (LR interleaved).
 */
static void s16_diff_report(const int16_t *expected, const int16_t *got, int frames)
{
    const int n = frames * 2;
    int i;
    int max_abs = 0;
    double se = 0.0; /* sum of squared error */
    double ss = 0.0; /* sum of squared signal (expected) */

    for(i = 0; i < n; i++)
    {
        const int e = (int)expected[i];
        const int g = (int)got[i];
        const int d = g - e;
        const int ad = (d < 0) ? -d : d;

        if(ad > max_abs)
        {
            max_abs = ad;
        }

        se += (double)d * (double)d;
        ss += (double)e * (double)e;
    }

    {
        const double rms_err = (n > 0) ? sqrt(se / (double)n) : 0.0;
        const double rms_sig = (n > 0) ? sqrt(ss / (double)n) : 0.0;
        const double snr_db = (rms_err > 0.0 && rms_sig > 0.0) ? (20.0 * log10(rms_sig / rms_err)) : 0.0;

        fprintf(stderr,
                "\n    diff stats:\n"
                "     max_abs_delta = %d\n"
                "     rms_err = %.6f\n"
                "     snr_db = %.2f\n\n",
                max_abs,
                rms_err,
                snr_db);
    }
}

/*
 * Helper for tests: create a synth instance with settings pinned for
 * deterministic render output.
 */
static fluid_synth_t *new_locked_down_synth(fluid_settings_t **out_settings)
{
    fluid_settings_t *settings = NULL;
    fluid_synth_t *synth = NULL;

    TEST_ASSERT_MSG(out_settings != NULL, "new_locked_down_synth(): out_settings is NULL");

    settings = new_fluid_settings();
    TEST_ASSERT_MSG(settings != NULL, "new_fluid_settings() returned NULL");

    /* Lock down common "drift" knobs. */
    TEST_SUCCESS(fluid_settings_setnum(settings, "synth.sample-rate", (double)TEST_SRATE));
    TEST_SUCCESS(fluid_settings_setnum(settings, "synth.gain", 0.5));
    TEST_SUCCESS(fluid_settings_setint(settings, "synth.chorus.active", 0));
    TEST_SUCCESS(fluid_settings_setint(settings, "synth.reverb.active", 0));

    /* Do NOT try to force interpolation.
     * It is not a public setting in current FluidSynth releases. */

    synth = new_fluid_synth(settings);
    TEST_ASSERT_MSG(synth != NULL, "new_fluid_synth() returned NULL");

    /* Load known test soundfont and select it for all channels (reset=1). */
    {
        const int sfid = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1);
        TEST_ASSERT_MSG(sfid >= 0, "fluid_synth_sfload(TEST_SOUNDFONT) failed");
    }

    *out_settings = settings;
    return synth;
}

static uint32_t fnv1a32_update(uint32_t h, const uint8_t *p, size_t n)
{
    /* FNV-1a 32-bit */
    const uint32_t FNV_PRIME = 16777619u;
    size_t i;

    for(i = 0; i < n; i++)
    {
        h ^= (uint32_t)p[i];
        h *= FNV_PRIME;
    }

    return h;
}

static uint32_t hash_s16_interleaved_le(const int16_t *lr, int frames)
{
    uint32_t h = 2166136261u; /* FNV offset basis */
    int i;

    for(i = 0; i < frames * 2; i++)
    {
        /* Force little-endian byte order for stability across endianness. */
        uint16_t u = (uint16_t)lr[i];
        uint8_t b[2];
        b[0] = (uint8_t)(u & 0xFFu);
        b[1] = (uint8_t)((u >> 8) & 0xFFu);
        h = fnv1a32_update(h, b, sizeof(b));
    }

    return h;
}

static int report_first_delta_s16(const int16_t *expected, const int16_t *got, int frames)
{
    int i;
    int mismatches = 0;
    int max_abs_delta = 0;

    for(i = 0; i < frames * 2; i++)
    {
        int d = (int)got[i] - (int)expected[i];

        if(d != 0)
        {
            if(mismatches == 0)
            {
                fprintf(stderr,
                        "first sample mismatch @%d (interleaved index): exp=%d got=%d delta=%d\n",
                        i,
                        (int)expected[i],
                        (int)got[i],
                        d);
            }

            mismatches++;

            if(d < 0)
            {
                d = -d;
            }

            if(d > max_abs_delta)
            {
                max_abs_delta = d;
            }
        }
    }

    fprintf(stderr, "mismatches=%d max_abs_delta=%d\n", mismatches, max_abs_delta);
    return max_abs_delta;
}

static const char *build_config_string(void)
{
#if defined(NDEBUG)
    return "Release (NDEBUG defined)";
#else
    return "Debug (NDEBUG not defined)";
#endif
}

static int env_is_set(const char *name)
{
    const char *v = getenv(name);

    if(v == NULL)
    {
        return 0;
    }

    /* Treat any non-empty and not "0" as enabled. */
    if(v[0] == '\0')
    {
        return 0;
    }

    if(v[0] == '0' && v[1] == '\0')
    {
        return 0;
    }

    return 1;
}

static int env_get_int(const char *name, int defval)
{
    const char *v = getenv(name);

    if(!v || !*v)
    {
        return defval;
    }

    return atoi(v);
}

/* Monotonic-ish wall clock for perf; we only need relative numbers. */
static double now_seconds(void)
{
#if defined(_WIN32)
    LARGE_INTEGER freq, t;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&t);
    return (double)t.QuadPart / (double)freq.QuadPart;
#else
    return (double)clock() / (double)CLOCKS_PER_SEC;
#endif
}

/* Emit a C initializer for an interleaved s16 LR buffer. */
static void dump_s16_c_array(const char *name, const int16_t *lr, int frames)
{
    const int nsamp = frames * 2;
    int i;

    fprintf(stderr, "/* Generated by test_synth_render_s16.c (%s) */\n", build_config_string());
    fprintf(stderr, "static const int16_t %s[%d] = {\n", name, nsamp);

    for(i = 0; i < nsamp; i++)
    {
        if((i % 8) == 0)
        {
            fprintf(stderr, "    ");
        }

        fprintf(stderr, "%6d,", (int)lr[i]);

        if((i % 8) == 7 || i == nsamp - 1)
        {
            fprintf(stderr, "\n");
        }
        else
        {
            fprintf(stderr, " ");
        }
    }

    fprintf(stderr, "};\n");
}

int main(void)
{
    int failed = 0;
    const char *audio_driver_register = { NULL };

    fluid_audio_driver_register(&audio_driver_register);

    printf("FluidSynth runtime version: %s\n", fluid_version_str());

    /* Make sure our diagnostics always show up even when the test aborts. */
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    /* Existing deterministic hash regression test (unchanged behavior). */
    {
        fluid_settings_t *settings = NULL;
        fluid_synth_t *synth = NULL;

        /* Interleaved LR buffer: [L0 R0 L1 R1 ...] */
        int16_t out[FRAMES * 2];

        synth = new_locked_down_synth(&settings);

        /* Ensure we start from a known buffer state. */
        memset(out, 0, sizeof(out));

        /* Note on then render a fixed number of frames. */
        TEST_SUCCESS(fluid_synth_noteon(synth, NOTE_CH, NOTE_KEY, NOTE_VEL));

        /*
         * Render s16:
         * - left points to out[0], right points to out[1]
         * - increment 2 to interleave LR
         */
        TEST_SUCCESS(fluid_synth_write_s16(synth, FRAMES, out, 0, 2, out, 1, 2));

        /* Optional: dump a reference buffer as C code (local tooling aid). */
        if(env_is_set("FLUID_TEST_DUMP_REF_S16"))
        {
            dump_s16_c_array("REF_S16_INTERLEAVED_LR", out, FRAMES);

            /* Also print the hash so you can copy/paste EXPECTED easily. */
            fprintf(stderr,
                    "/* hash_s16_interleaved_le(FRAMES=%d) = 0x%08X */\n",
                    FRAMES,
                    hash_s16_interleaved_le(out, FRAMES));
        }

        /* Hash the output deterministically (little-endian). */
        {
            const uint32_t got = hash_s16_interleaved_le(out, FRAMES);

            /*
             * IMPORTANT:
             * Set EXPECTED to the value you get from a known-good baseline.
             * EXPECTED corresponds to Release; Debug may differ by small rounding drift. See diff stats.
             */
            /* const uint32_t EXPECTED = 0x77F11EEFu; */ /* VS Debug build of FluidSynth 2.5.2 */
            const uint32_t EXPECTED = 0x7FD92354u;       /* VS Release build of FluidSynth 2.5.2 */

            if(got != EXPECTED)
            {
                int first = -1;
                int i;

                fprintf(stderr, "hash mismatch: EXPECTED=0x%08X got=0x%08X\n", EXPECTED, got);

                /* Compare against reference buffer and report shape of differences. */
                for(i = 0; i < FRAMES * 2; i++)
                {
                    if(out[i] != REF_S16_INTERLEAVED_LR[i])
                    {
                        first = i;
                        break;
                    }
                }

                if(first >= 0)
                {
                    int max_delta;
                    fprintf(stderr, "s16 stream mismatch vs reference buffer\n");
                    max_delta = report_first_delta_s16(REF_S16_INTERLEAVED_LR, out, FRAMES);

                    if(max_delta > MAX_ABS_DELTA_ACCEPTABLE)
                    {
                        fprintf(stderr, "ERROR: max delta %d exceeds acceptable threshold\n", max_delta);
                        failed = 1;
                    }

                    s16_diff_report(REF_S16_INTERLEAVED_LR, out, FRAMES);
                }
                else
                {
                    /* Very unlikely: hash mismatch but samples match reference buffer. */
                    fprintf(stderr, "WARNING: hash mismatch but samples match reference buffer\n");
                }
            }
        }

        delete_fluid_synth(synth);
        delete_fluid_settings(settings);
    }

    /*
     * Continuity tests:
     * Compare "one call" vs "two successive calls".
     *
     * We do this for float (to see if the synth diverges before dithering),
     * and for s16 (to specifically catch dither-index / dither-state bugs).
     */

    /* Helper: print first mismatch for s16 buffers (interleaved LR). */
    {
        const int splits[] = { 1, 2, 7, 31, 63, 64, 65, 127, 128, 129, 191, 255 };
        const int nsplits = (int)(sizeof(splits) / sizeof(splits[0]));
        int si;

        for(si = 0; si < nsplits; si++)
        {
            const int half = splits[si];

            /* Skip invalid splits. */
            if(half <= 0 || half >= FRAMES)
            {
                continue;
            }

            /* --- FLOAT continuity probe --- */
            {
                fluid_settings_t *settings_a = NULL;
                fluid_synth_t *synth_a = NULL;

                fluid_settings_t *settings_b = NULL;
                fluid_synth_t *synth_b = NULL;

                float one_f[FRAMES * 2];
                float two_f[FRAMES * 2];

                int float_first = -1;

                synth_a = new_locked_down_synth(&settings_a);
                synth_b = new_locked_down_synth(&settings_b);

                memset(one_f, 0, sizeof(one_f));
                memset(two_f, 0, sizeof(two_f));

                TEST_SUCCESS(fluid_synth_noteon(synth_a, NOTE_CH, NOTE_KEY, NOTE_VEL));
                TEST_SUCCESS(fluid_synth_noteon(synth_b, NOTE_CH, NOTE_KEY, NOTE_VEL));

                /* One combined render. */
                TEST_SUCCESS(fluid_synth_write_float(synth_a, FRAMES, one_f, 0, 2, one_f, 1, 2));

                /* Two successive renders. */
                TEST_SUCCESS(fluid_synth_write_float(synth_b, half, two_f, 0, 2, two_f, 1, 2));
                TEST_SUCCESS(fluid_synth_write_float(
                                 synth_b, FRAMES - half, two_f + (half * 2), 0, 2, two_f + (half * 2), 1, 2));

                /* Compare floats with tight epsilon. */
                {
                    const float eps = 1.0e-8f;
                    int i;

                    for(i = 0; i < (int)(FRAMES * 2); i++)
                    {
                        float d = one_f[i] - two_f[i];

                        if(d < 0.0f)
                        {
                            d = -d;
                        }

                        if(d > eps)
                        {
                            float_first = i;
                            break;
                        }
                    }
                }

                if(float_first >= 0)
                {
                    const int frame = float_first / 2;
                    const int ch = float_first % 2;
                    printf("FLOAT continuity FAILED (split=%d): first mismatch i=%d (frame=%d "
                           "ch=%s): one=%g two=%g\n",
                           half,
                           float_first,
                           frame,
                           ch ? "R" : "L",
                           (double)one_f[float_first],
                           (double)two_f[float_first]);
                }
                else
                {
                    printf("FLOAT continuity OK (split=%d)\n", half);
                }

                delete_fluid_synth(synth_b);
                delete_fluid_settings(settings_b);
                delete_fluid_synth(synth_a);
                delete_fluid_settings(settings_a);

                /*
                 * If float continuity fails, s16 continuity is not a "pure dither"
                 * check anymore: synthesis itself is chunk-size dependent.
                 * Still run s16 test (it's informative), but report both.
                 */
            }

            /* --- S16 continuity test --- */
            {
                fluid_settings_t *settings_a = NULL;
                fluid_synth_t *synth_a = NULL;

                fluid_settings_t *settings_b = NULL;
                fluid_synth_t *synth_b = NULL;

                int16_t one_call[FRAMES * 2];
                int16_t two_calls[FRAMES * 2];

                int first = -1;
                int ok = 0;

                synth_a = new_locked_down_synth(&settings_a);
                synth_b = new_locked_down_synth(&settings_b);

                memset(one_call, 0, sizeof(one_call));
                memset(two_calls, 0, sizeof(two_calls));

                TEST_SUCCESS(fluid_synth_noteon(synth_a, NOTE_CH, NOTE_KEY, NOTE_VEL));
                TEST_SUCCESS(fluid_synth_noteon(synth_b, NOTE_CH, NOTE_KEY, NOTE_VEL));

                /* One combined render. */
                TEST_SUCCESS(fluid_synth_write_s16(synth_a, FRAMES, one_call, 0, 2, one_call, 1, 2));

                /* Two successive renders. */
                TEST_SUCCESS(fluid_synth_write_s16(synth_b, half, two_calls, 0, 2, two_calls, 1, 2));
                TEST_SUCCESS(fluid_synth_write_s16(
                                 synth_b, FRAMES - half, two_calls + (half * 2), 0, 2, two_calls + (half * 2), 1, 2));

                {
                    int i;

                    for(i = 0; i < (int)(FRAMES * 2); i++)
                    {
                        if(one_call[i] != two_calls[i])
                        {
                            first = i;
                            break;
                        }
                    }
                }

                if(first < 0)
                {
                    printf("S16 continuity OK (split=%d)\n", half);
                    ok = 1;
                }
                else
                {
                    const int frame = first / 2;
                    const int ch = first % 2;

                    printf("S16 continuity FAILED (split=%d): first mismatch i=%d (frame=%d "
                           "ch=%s): one=%d two=%d\n",
                           half,
                           first,
                           frame,
                           ch ? "R" : "L",
                           (int)one_call[first],
                           (int)two_calls[first]);

                    /* Print a small mismatch window for pattern recognition. */
                    {
                        int j;
                        const int start = (first - 12 < 0) ? 0 : first - 12;
                        const int end = (first + 12 > (int)(FRAMES * 2)) ? (int)(FRAMES * 2) : first + 12;

                        for(j = start; j < end; j++)
                        {
                            if(one_call[j] != two_calls[j])
                            {
                                printf("  [%4d] one=%6d two=%6d  <---\n", j, (int)one_call[j], (int)two_calls[j]);
                            }
                        }
                    }
                }

                delete_fluid_synth(synth_b);
                delete_fluid_settings(settings_b);
                delete_fluid_synth(synth_a);
                delete_fluid_settings(settings_a);

                /* Fail fast on first failing split to keep output readable. */
                TEST_ASSERT_MSG(ok, "S16 continuity failed");
            }
        }
    }

    /*
     * Optional local performance microbench.
     *
     * Enable with:
     *   FLUID_TEST_BENCH_S16=1
     *
     * Controls:
     *   FLUID_TEST_BENCH_ITERS   (default 20000)
     *   FLUID_TEST_BENCH_WARMUP  (default 2000)
     *
     * This is informational only: never fails the test.
     */
    if(env_is_set("FLUID_TEST_BENCH_S16"))
    {
        const int warmup = env_get_int("FLUID_TEST_BENCH_WARMUP", 2000);
        const int iters = env_get_int("FLUID_TEST_BENCH_ITERS", 20000);

        fluid_settings_t *settings = NULL;
        fluid_synth_t *synth = NULL;

        int16_t out[FRAMES * 2];
        int i;
        volatile uint32_t sink = 0; /* prevent dead-code elimination */

        synth = new_locked_down_synth(&settings);
        memset(out, 0, sizeof(out));
        TEST_SUCCESS(fluid_synth_noteon(synth, NOTE_CH, NOTE_KEY, NOTE_VEL));

        /* Warmup */
        for(i = 0; i < warmup; i++)
        {
            TEST_SUCCESS(fluid_synth_write_s16(synth, FRAMES, out, 0, 2, out, 1, 2));
            sink ^= hash_s16_interleaved_le(out, FRAMES);
        }

        /* Timed run */
        {
            const double t0 = now_seconds();

            for(i = 0; i < iters; i++)
            {
                TEST_SUCCESS(fluid_synth_write_s16(synth, FRAMES, out, 0, 2, out, 1, 2));
                sink ^= hash_s16_interleaved_le(out, FRAMES);
            }

            {
                const double t1 = now_seconds();
                const double dt = t1 - t0;
                const double frames_total = (double)iters * (double)FRAMES;
                const double ns_per_frame = (dt * 1.0e9) / frames_total;
                const double frames_per_s = frames_total / dt;

                fprintf(stderr,
                        "\n    BENCH s16 (%s):\n"
                        "     iters = %d\n"
                        "     frames/iter = %d\n"
                        "     total_frames = %.0f\n"
                        "     TOTAL TIME = %.3f sec\n"
                        "     nSec/frame = %.2f\n"
                        "     frames/s = %.0f\n"
                        "     sink = 0x%08X\n\n",
                        build_config_string(),
                        iters,
                        FRAMES,
                        frames_total,
                        dt,
                        ns_per_frame,
                        frames_per_s,
                        (uint32_t)sink);
            }
        }

        delete_fluid_synth(synth);
        delete_fluid_settings(settings);
    }

    return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
