/* FluidSynth - A Software Synthesizer
 *
 * Copyright (C) 2003  Peter Hanappe and others.
 *
 * SoundFont file loading code borrowed from Smurf SoundFont Editor
 * Copyright (C) 1999-2001 Josh Green
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 */


#include "fluid_sffile.h"
#include "fluid_sfont.h"
#include "fluid_sys.h"

#if LIBSNDFILE_SUPPORT
#include <sndfile.h>
#endif

#if LIBINSTPATCH_SUPPORT
#include <libinstpatch/libinstpatch.h>
#endif

/*=================================sfload.c========================
  Borrowed from Smurf SoundFont Editor by Josh Green
  =================================================================*/

/* FOURCC definitions */
#define RIFF_FCC    FLUID_FOURCC('R','I','F','F')
#define LIST_FCC    FLUID_FOURCC('L','I','S','T')
#define SFBK_FCC    FLUID_FOURCC('s','f','b','k')
#define INFO_FCC    FLUID_FOURCC('I','N','F','O')
#define SDTA_FCC    FLUID_FOURCC('s','d','t','a')
#define PDTA_FCC    FLUID_FOURCC('p','d','t','a') /* info/sample/preset */

#define IFIL_FCC    FLUID_FOURCC('i','f','i','l')
#define ISNG_FCC    FLUID_FOURCC('i','s','n','g')
#define INAM_FCC    FLUID_FOURCC('I','N','A','M')
#define IROM_FCC    FLUID_FOURCC('i','r','o','m') /* info ids (1st byte of info strings) */
#define IVER_FCC    FLUID_FOURCC('i','v','e','r')
#define ICRD_FCC    FLUID_FOURCC('I','C','R','D')
#define IENG_FCC    FLUID_FOURCC('I','E','N','G')
#define IPRD_FCC    FLUID_FOURCC('I','P','R','D') /* more info ids */
#define ICOP_FCC    FLUID_FOURCC('I','C','O','P')
#define ICMT_FCC    FLUID_FOURCC('I','C','M','T')
#define ISFT_FCC    FLUID_FOURCC('I','S','F','T') /* and yet more info ids */

#define SNAM_FCC    FLUID_FOURCC('s','n','a','m')
#define SMPL_FCC    FLUID_FOURCC('s','m','p','l') /* sample ids */
#define PHDR_FCC    FLUID_FOURCC('p','h','d','r')
#define PBAG_FCC    FLUID_FOURCC('p','b','a','g')
#define PMOD_FCC    FLUID_FOURCC('p','m','o','d')
#define PGEN_FCC    FLUID_FOURCC('p','g','e','n') /* preset ids */
#define IHDR_FCC    FLUID_FOURCC('i','n','s','t')
#define IBAG_FCC    FLUID_FOURCC('i','b','a','g')
#define IMOD_FCC    FLUID_FOURCC('i','m','o','d')
#define IGEN_FCC    FLUID_FOURCC('i','g','e','n') /* instrument ids */
#define SHDR_FCC    FLUID_FOURCC('s','h','d','r') /* sample info */
#define SM24_FCC    FLUID_FOURCC('s','m','2','4')

/* Set when the FCC code is unknown */
#define UNKN_ID     FLUID_N_ELEMENTS(idlist)

/*
 * This declares a uint32_t array containing the SF2 chunk identifiers.
 */
static const uint32_t idlist[] =
{
    RIFF_FCC,
    LIST_FCC,
    SFBK_FCC,
    INFO_FCC,
    SDTA_FCC,
    PDTA_FCC,

    IFIL_FCC,
    ISNG_FCC,
    INAM_FCC,
    IROM_FCC,
    IVER_FCC,
    ICRD_FCC,
    IENG_FCC,
    IPRD_FCC,
    ICOP_FCC,
    ICMT_FCC,
    ISFT_FCC,

    SNAM_FCC,
    SMPL_FCC,
    PHDR_FCC,
    PBAG_FCC,
    PMOD_FCC,
    PGEN_FCC,
    IHDR_FCC,
    IBAG_FCC,
    IMOD_FCC,
    IGEN_FCC,
    SHDR_FCC,
    SM24_FCC
};

/* generator types */
typedef enum
{
    Gen_StartAddrOfs,
    Gen_EndAddrOfs,
    Gen_StartLoopAddrOfs,
    Gen_EndLoopAddrOfs,
    Gen_StartAddrCoarseOfs,
    Gen_ModLFO2Pitch,
    Gen_VibLFO2Pitch,
    Gen_ModEnv2Pitch,
    Gen_FilterFc,
    Gen_FilterQ,
    Gen_ModLFO2FilterFc,
    Gen_ModEnv2FilterFc,
    Gen_EndAddrCoarseOfs,
    Gen_ModLFO2Vol,
    Gen_Unused1,
    Gen_ChorusSend,
    Gen_ReverbSend,
    Gen_Pan,
    Gen_Unused2,
    Gen_Unused3,
    Gen_Unused4,
    Gen_ModLFODelay,
    Gen_ModLFOFreq,
    Gen_VibLFODelay,
    Gen_VibLFOFreq,
    Gen_ModEnvDelay,
    Gen_ModEnvAttack,
    Gen_ModEnvHold,
    Gen_ModEnvDecay,
    Gen_ModEnvSustain,
    Gen_ModEnvRelease,
    Gen_Key2ModEnvHold,
    Gen_Key2ModEnvDecay,
    Gen_VolEnvDelay,
    Gen_VolEnvAttack,
    Gen_VolEnvHold,
    Gen_VolEnvDecay,
    Gen_VolEnvSustain,
    Gen_VolEnvRelease,
    Gen_Key2VolEnvHold,
    Gen_Key2VolEnvDecay,
    Gen_Instrument,
    Gen_Reserved1,
    Gen_KeyRange,
    Gen_VelRange,
    Gen_StartLoopAddrCoarseOfs,
    Gen_Keynum,
    Gen_Velocity,
    Gen_Attenuation,
    Gen_Reserved2,
    Gen_EndLoopAddrCoarseOfs,
    Gen_CoarseTune,
    Gen_FineTune,
    Gen_SampleId,
    Gen_SampleModes,
    Gen_Reserved3,
    Gen_ScaleTune,
    Gen_ExclusiveClass,
    Gen_OverrideRootKey,
    Gen_Dummy
} Gen_Type;

#define Gen_MaxValid Gen_Dummy - 1 /* maximum valid generator */
#define Gen_Count Gen_Dummy /* count of generators */
#define GenArrSize sizeof(SFGenAmount) * Gen_Count /* gen array size */


static const unsigned short invalid_inst_gen[] =
{
    Gen_Unused1,
    Gen_Unused2,
    Gen_Unused3,
    Gen_Unused4,
    Gen_Reserved1,
    Gen_Reserved2,
    Gen_Reserved3,
    0
};

static const unsigned short invalid_preset_gen[] =
{
    Gen_StartAddrOfs,
    Gen_EndAddrOfs,
    Gen_StartLoopAddrOfs,
    Gen_EndLoopAddrOfs,
    Gen_StartAddrCoarseOfs,
    Gen_EndAddrCoarseOfs,
    Gen_StartLoopAddrCoarseOfs,
    Gen_Keynum,
    Gen_Velocity,
    Gen_EndLoopAddrCoarseOfs,
    Gen_SampleModes,
    Gen_ExclusiveClass,
    Gen_OverrideRootKey,
    0
};


/* sfont file chunk sizes */
#define SF_PHDR_SIZE (38)
#define SF_BAG_SIZE  (4)
#define SF_MOD_SIZE  (10)
#define SF_GEN_SIZE  (4)
#define SF_IHDR_SIZE (22)
#define SF_SHDR_SIZE (46)


#define READCHUNK(sf, var)                                                  \
    do                                                                      \
    {                                                                       \
        if (sf->fcbs->fread(var, 8, sf->sffd) == FLUID_FAILED)              \
            return FALSE;                                                   \
        ((SFChunk *)(var))->size = FLUID_LE32TOH(((SFChunk *)(var))->size); \
    } while (0)

#define READD(sf, var)                                            \
    do                                                            \
    {                                                             \
        uint32_t _temp;                                           \
        if (sf->fcbs->fread(&_temp, 4, sf->sffd) == FLUID_FAILED) \
            return FALSE;                                         \
        var = FLUID_LE32TOH(_temp);                               \
    } while (0)

#define READW(sf, var)                                            \
    do                                                            \
    {                                                             \
        uint16_t _temp;                                           \
        if (sf->fcbs->fread(&_temp, 2, sf->sffd) == FLUID_FAILED) \
            return FALSE;                                         \
        var = FLUID_LE16TOH(_temp);                               \
    } while (0)

#define READID(sf, var)                                        \
    do                                                         \
    {                                                          \
        if (sf->fcbs->fread(var, 4, sf->sffd) == FLUID_FAILED) \
            return FALSE;                                      \
    } while (0)

#define READSTR(sf, var)                                        \
    do                                                          \
    {                                                           \
        if (sf->fcbs->fread(var, 20, sf->sffd) == FLUID_FAILED) \
            return FALSE;                                       \
        (*var)[20] = '\0';                                      \
    } while (0)

#define READB(sf, var)                                          \
    do                                                          \
    {                                                           \
        if (sf->fcbs->fread(&var, 1, sf->sffd) == FLUID_FAILED) \
            return FALSE;                                       \
    } while (0)

#define FSKIP(sf, size)                                                \
    do                                                                 \
    {                                                                  \
        if (sf->fcbs->fseek(sf->sffd, size, SEEK_CUR) == FLUID_FAILED) \
            return FALSE;                                              \
    } while (0)

#define FSKIPW(sf)                                                  \
    do                                                              \
    {                                                               \
        if (sf->fcbs->fseek(sf->sffd, 2, SEEK_CUR) == FLUID_FAILED) \
            return FALSE;                                           \
    } while (0)

/* removes and advances a fluid_list_t pointer */
#define SLADVREM(list, item)                        \
    do                                              \
    {                                               \
        fluid_list_t *_temp = item;                 \
        item = fluid_list_next(item);               \
        list = fluid_list_remove_link(list, _temp); \
        delete1_fluid_list(_temp);                  \
    } while (0)


static int load_header(SFData *sf);
static int load_body(SFData *sf);
static int process_info(SFData *sf, int size);
static int process_sdta(SFData *sf, unsigned int size);
static int process_pdta(SFData *sf, int size);
static int load_phdr(SFData *sf, int size);
static int load_pbag(SFData *sf, int size);
static int load_pmod(SFData *sf, int size);
static int load_pgen(SFData *sf, int size);
static int load_ihdr(SFData *sf, int size);
static int load_ibag(SFData *sf, int size);
static int load_imod(SFData *sf, int size);
static int load_igen(SFData *sf, int size);
static int load_shdr(SFData *sf, unsigned int size);
static int fixup_pgen(SFData *sf);
static int fixup_igen(SFData *sf);

static int chunkid(uint32_t id);
static int read_listchunk(SFData *sf, SFChunk *chunk);
static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size);
static int preset_compare_func(void *a, void *b);
static fluid_list_t *find_gen_by_id(int gen, fluid_list_t *genlist);
static int valid_inst_genid(unsigned short genid);
static int valid_preset_genid(unsigned short genid);


static void delete_preset(SFPreset *preset);
static void delete_inst(SFInst *inst);
static void delete_zone(SFZone *zone);

static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data);
static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24);

/**
 * Check if a file is a SoundFont file.
 *
 * If fluidsynth was built with DLS support, this function will also identify DLS files.
 * @param filename Path to the file to check
 * @return TRUE if it could be a SF2, SF3 or DLS file, FALSE otherwise
 *
 * @note This function only checks whether header(s) in the RIFF chunk are present.
 * A call to fluid_synth_sfload() might still fail.
 */
int fluid_is_soundfont(const char *filename)
{
    FILE    *fp;
    uint32_t fcc;
    int      retcode = FALSE;

    do
    {
        if((fp = fluid_file_open(filename, NULL)) == NULL)
        {
            return retcode;
        }

        if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1)
        {
            break;
        }

        if(fcc != RIFF_FCC)
        {
            break;
        }

        if(FLUID_FSEEK(fp, 4, SEEK_CUR))
        {
            break;
        }

        if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1)
        {
            break;
        }

        retcode = (fcc == SFBK_FCC);
        if(retcode)
        {
            break;  // seems to be SF2, stop here
        }
#ifdef LIBINSTPATCH_SUPPORT
        else
        {
            IpatchFileHandle *fhandle = ipatch_file_identify_open(filename, NULL);
            if(fhandle != NULL)
            {
                retcode = (ipatch_file_identify(fhandle->file, NULL) == IPATCH_TYPE_DLS_FILE);
                ipatch_file_close(fhandle);
            }
        }
#endif
    }
    while(0);

    FLUID_FCLOSE(fp);

    return retcode;
}

/*
 * Open a SoundFont file and parse it's contents into a SFData structure.
 *
 * @param fname filename
 * @param fcbs file callback structure
 * @return the partially parsed SoundFont as SFData structure or NULL on error
 */
SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs)
{
    SFData *sf;
    int fsize = 0;

    if(!(sf = FLUID_NEW(SFData)))
    {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        return NULL;
    }

    FLUID_MEMSET(sf, 0, sizeof(SFData));

    sf->fcbs = fcbs;

    if((sf->sffd = fcbs->fopen(fname)) == NULL)
    {
        FLUID_LOG(FLUID_ERR, "Unable to open file '%s'", fname);
        goto error_exit;
    }

    sf->fname = FLUID_STRDUP(fname);

    if(sf->fname == NULL)
    {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        goto error_exit;
    }

    /* get size of file by seeking to end */
    if(fcbs->fseek(sf->sffd, 0L, SEEK_END) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Seek to end of file failed");
        goto error_exit;
    }

    if((fsize = fcbs->ftell(sf->sffd)) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Get end of file position failed");
        goto error_exit;
    }

    sf->filesize = fsize;

    if(fcbs->fseek(sf->sffd, 0, SEEK_SET) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Rewind to start of file failed");
        goto error_exit;
    }

    if(!load_header(sf))
    {
        goto error_exit;
    }

    return sf;

error_exit:
    fluid_sffile_close(sf);
    return NULL;
}

/*
 * Parse all preset information from the soundfont
 *
 * @return FLUID_OK on success, otherwise FLUID_FAILED
 */
int fluid_sffile_parse_presets(SFData *sf)
{
    if(!load_body(sf))
    {
        return FLUID_FAILED;
    }

    return FLUID_OK;
}

/* Load sample data from the soundfont file
 *
 * This function will always return the sample data in WAV format. If the sample_type specifies an
 * Ogg Vorbis compressed sample, it will be decompressed automatically before returning.
 *
 * @param sf SFData instance
 * @param sample_start index of first sample point in Soundfont sample chunk
 * @param sample_end index of last sample point in Soundfont sample chunk
 * @param sample_type type of the sample in Soundfont
 * @param data pointer to sample data pointer, will point to loaded sample data on success
 * @param data24 pointer to 24-bit sample data pointer if 24-bit data present, will point to loaded
 *               24-bit sample data on success or NULL if no 24-bit data is present in file
 *
 * @return The number of sample words in returned buffers or -1 on failure
 */
int fluid_sffile_read_sample_data(SFData *sf, unsigned int sample_start, unsigned int sample_end,
                                  int sample_type, short **data, char **data24)
{
    int num_samples;

    if(sample_type & FLUID_SAMPLETYPE_OGG_VORBIS)
    {
        num_samples = fluid_sffile_read_vorbis(sf, sample_start, sample_end, data);
    }
    else
    {
        num_samples = fluid_sffile_read_wav(sf, sample_start, sample_end, data, data24);
    }

    return num_samples;
}

/*
 * Close a SoundFont file and free the SFData structure.
 *
 * @param sf pointer to SFData structure
 * @param fcbs file callback structure
 */
void fluid_sffile_close(SFData *sf)
{
    fluid_list_t *entry;
    SFPreset *preset;
    SFInst *inst;

    if(sf->sffd)
    {
        sf->fcbs->fclose(sf->sffd);
    }

    FLUID_FREE(sf->fname);

    entry = sf->info;

    while(entry)
    {
        FLUID_FREE(fluid_list_get(entry));
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(sf->info);

    entry = sf->preset;

    while(entry)
    {
        preset = (SFPreset *)fluid_list_get(entry);
        delete_preset(preset);
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(sf->preset);

    entry = sf->inst;

    while(entry)
    {
        inst = (SFInst *)fluid_list_get(entry);
        delete_inst(inst);
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(sf->inst);

    entry = sf->sample;

    while(entry)
    {
        FLUID_FREE(fluid_list_get(entry));
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(sf->sample);

    FLUID_FREE(sf);
}


/*
 * Private functions
 */

/* sound font file load functions */
static int chunkid(uint32_t id)
{
    unsigned int i;

    for(i = 0; i < FLUID_N_ELEMENTS(idlist); i++)
    {
        if(idlist[i] == id)
        {
            break;
        }
    }

    /* Return chunk id or UNKN_ID if not found */
    return i;
}

static int load_header(SFData *sf)
{
    SFChunk chunk;

    READCHUNK(sf, &chunk); /* load RIFF chunk */

    if(chunk.id != RIFF_FCC)
    {
        /* error if not RIFF */
        FLUID_LOG(FLUID_ERR, "Not a RIFF file");
        return FALSE;
    }

    READID(sf, &chunk.id); /* load file ID */

    if(chunk.id != SFBK_FCC)
    {
        /* error if not SFBK_ID */
        FLUID_LOG(FLUID_ERR, "Not a SoundFont file");
        return FALSE;
    }

    if(chunk.size != sf->filesize - 8)
    {
        FLUID_LOG(FLUID_ERR, "SoundFont file size mismatch");
        return FALSE;
    }

    /* Process INFO block */
    if(!read_listchunk(sf, &chunk))
    {
        return FALSE;
    }

    if(chunk.id != INFO_FCC)
    {
        FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting INFO chunk");
        return FALSE;
    }

    if(!process_info(sf, chunk.size))
    {
        return FALSE;
    }

    /* Process sample chunk */
    if(!read_listchunk(sf, &chunk))
    {
        return FALSE;
    }

    if(chunk.id != SDTA_FCC)
    {
        FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting SAMPLE chunk");
        return FALSE;
    }

    if(!process_sdta(sf, chunk.size))
    {
        return FALSE;
    }

    /* process HYDRA chunk */
    if(!read_listchunk(sf, &chunk))
    {
        return FALSE;
    }

    if(chunk.id != PDTA_FCC)
    {
        FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting HYDRA chunk");
        return FALSE;
    }

    sf->hydrapos = sf->fcbs->ftell(sf->sffd);
    sf->hydrasize = chunk.size;

    return TRUE;
}

static int load_body(SFData *sf)
{
    if(sf->fcbs->fseek(sf->sffd, sf->hydrapos, SEEK_SET) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Failed to seek to HYDRA position");
        return FALSE;
    }

    if(!process_pdta(sf, sf->hydrasize))
    {
        return FALSE;
    }

    if(!fixup_pgen(sf))
    {
        return FALSE;
    }

    if(!fixup_igen(sf))
    {
        return FALSE;
    }

    /* sort preset list by bank, preset # */
    sf->preset = fluid_list_sort(sf->preset, (fluid_compare_func_t)preset_compare_func);

    return TRUE;
}

static int read_listchunk(SFData *sf, SFChunk *chunk)
{
    READCHUNK(sf, chunk); /* read list chunk */

    if(chunk->id != LIST_FCC)  /* error if ! list chunk */
    {
        FLUID_LOG(FLUID_ERR, "Invalid chunk id in level 0 parse");
        return FALSE;
    }

    READID(sf, &chunk->id); /* read id string */
    chunk->size -= 4;
    return TRUE;
}

static int process_info(SFData *sf, int size)
{
    SFChunk chunk;
    union
    {
        char *chr;
        uint32_t *fcc;
    } item;
    unsigned short ver;

    while(size > 0)
    {
        READCHUNK(sf, &chunk);
        size -= 8;

        if(chunk.id == IFIL_FCC)
        {
            /* sound font version chunk? */
            if(chunk.size != 4)
            {
                FLUID_LOG(FLUID_ERR, "Sound font version info chunk has invalid size");
                return FALSE;
            }

            READW(sf, ver);
            sf->version.major = ver;
            READW(sf, ver);
            sf->version.minor = ver;

            if(sf->version.major < 2)
            {
                FLUID_LOG(FLUID_ERR, "Sound font version is %d.%d which is not"
                          " supported, convert to version 2.0x",
                          sf->version.major, sf->version.minor);
                return FALSE;
            }

            if(sf->version.major == 3)
            {
#if !LIBSNDFILE_SUPPORT
                FLUID_LOG(FLUID_WARN,
                          "Sound font version is %d.%d but fluidsynth was compiled without"
                          " support for (v3.x)",
                          sf->version.major, sf->version.minor);
                return FALSE;
#endif
            }
            else if(sf->version.major > 2)
            {
                FLUID_LOG(FLUID_WARN,
                          "Sound font version is %d.%d which is newer than"
                          " what this version of fluidsynth was designed for (v2.0x)",
                          sf->version.major, sf->version.minor);
                return FALSE;
            }
        }
        else if(chunk.id == IVER_FCC)
        {
            /* ROM version chunk? */
            if(chunk.size != 4)
            {
                FLUID_LOG(FLUID_ERR, "ROM version info chunk has invalid size");
                return FALSE;
            }

            READW(sf, ver);
            sf->romver.major = ver;
            READW(sf, ver);
            sf->romver.minor = ver;
        }
        else if(chunkid(chunk.id) != UNKN_ID)
        {
            if((chunk.id != ICMT_FCC && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2))
            {
                FLUID_LOG(FLUID_ERR, "INFO sub chunk %.4s has invalid chunk size of %d bytes",
                          (char*)&chunk.id, chunk.size);
                return FALSE;
            }

            /* alloc for chunk fcc and da chunk */
            if(!(item.fcc = FLUID_MALLOC(chunk.size + sizeof(uint32_t) + 1)))
            {
                FLUID_LOG(FLUID_ERR, "Out of memory");
                return FALSE;
            }

            /* attach to INFO list, fluid_sffile_close will cleanup if FAIL occurs */
            sf->info = fluid_list_append(sf->info, item.fcc);

            /* save chunk fcc and update pointer to data value */
            *item.fcc++ = chunk.id;

            if(sf->fcbs->fread(item.chr, chunk.size, sf->sffd) == FLUID_FAILED)
            {
                return FALSE;
            }

            /* force terminate info item */
            item.chr[chunk.size] = '\0';
        }
        else
        {
            FLUID_LOG(FLUID_ERR, "Invalid chunk id in INFO chunk");
            return FALSE;
        }

        size -= chunk.size;
    }

    if(size < 0)
    {
        FLUID_LOG(FLUID_ERR, "INFO chunk size mismatch");
        return FALSE;
    }

    return TRUE;
}

static int process_sdta(SFData *sf, unsigned int size)
{
    SFChunk chunk;

    if(size == 0)
    {
        return TRUE;    /* no sample data? */
    }

    /* read sub chunk */
    READCHUNK(sf, &chunk);
    size -= 8;

    if(chunk.id != SMPL_FCC)
    {
        FLUID_LOG(FLUID_ERR, "Expected SMPL chunk found invalid id instead");
        return FALSE;
    }

    /* SDTA chunk may also contain sm24 chunk for 24 bit samples
     * (not yet supported), only an error if SMPL chunk size is
     * greater than SDTA. */
    if(chunk.size > size)
    {
        FLUID_LOG(FLUID_ERR, "SDTA chunk size mismatch");
        return FALSE;
    }

    /* sample data follows */
    sf->samplepos = sf->fcbs->ftell(sf->sffd);

    /* used to check validity of sample headers */
    sf->samplesize = chunk.size;

    FSKIP(sf, chunk.size);
    size -= chunk.size;

    if(sf->version.major >= 2 && sf->version.minor >= 4)
    {
        /* any chance to find another chunk here? */
        if(size > 8)
        {
            /* read sub chunk */
            READCHUNK(sf, &chunk);
            size -= 8;

            if(chunk.id == SM24_FCC)
            {
                int sm24size, sdtahalfsize;

                FLUID_LOG(FLUID_DBG, "Found SM24 chunk");

                if(chunk.size > size)
                {
                    FLUID_LOG(FLUID_WARN, "SM24 exceeds SDTA chunk, ignoring SM24");
                    goto ret; // no error
                }

                sdtahalfsize = sf->samplesize / 2;
                /* + 1 byte in the case that half the size of smpl chunk is an odd value */
                sdtahalfsize += sdtahalfsize % 2;
                sm24size = chunk.size;

                if(sdtahalfsize != sm24size)
                {
                    FLUID_LOG(FLUID_WARN, "SM24 not equal to half the size of SMPL chunk (0x%X != "
                              "0x%X), ignoring SM24",
                              sm24size, sdtahalfsize);
                    goto ret; // no error
                }

                /* sample data24 follows */
                sf->sample24pos = sf->fcbs->ftell(sf->sffd);
                sf->sample24size = sm24size;
            }
        }
    }

ret:
    FSKIP(sf, size);

    return TRUE;
}

static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size)
{
    READCHUNK(sf, chunk);
    *size -= 8;

    if(chunk->id != expid)
    {
        FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", (char*)&expid);
        return FALSE;
    }

    if(chunk->size % reclen)  /* valid chunk size? */
    {
        FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", (char*)&expid, reclen);
        return FALSE;
    }

    if((*size -= chunk->size) < 0)
    {
        FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", (char*)&expid);
        return FALSE;
    }

    return TRUE;
}

static int process_pdta(SFData *sf, int size)
{
    SFChunk chunk;

    if(!pdtahelper(sf, PHDR_FCC, SF_PHDR_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_phdr(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, PBAG_FCC, SF_BAG_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_pbag(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, PMOD_FCC, SF_MOD_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_pmod(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, PGEN_FCC, SF_GEN_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_pgen(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, IHDR_FCC, SF_IHDR_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_ihdr(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, IBAG_FCC, SF_BAG_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_ibag(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, IMOD_FCC, SF_MOD_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_imod(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, IGEN_FCC, SF_GEN_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_igen(sf, chunk.size))
    {
        return FALSE;
    }

    if(!pdtahelper(sf, SHDR_FCC, SF_SHDR_SIZE, &chunk, &size))
    {
        return FALSE;
    }

    if(!load_shdr(sf, chunk.size))
    {
        return FALSE;
    }

    return TRUE;
}

/* preset header loader */
static int load_phdr(SFData *sf, int size)
{
    int i, i2;
    SFPreset *preset, *prev_preset = NULL;
    unsigned short pbag_idx, prev_pbag_idx = 0;

    if(size % SF_PHDR_SIZE || size == 0)
    {
        FLUID_LOG(FLUID_ERR, "Preset header chunk size is invalid");
        return FALSE;
    }

    i = size / SF_PHDR_SIZE - 1;

    if(i == 0)
    {
        /* at least one preset + term record */
        FLUID_LOG(FLUID_WARN, "File contains no presets");
        FSKIP(sf, SF_PHDR_SIZE);
        return TRUE;
    }

    for(; i > 0; i--)
    {
        /* load all preset headers */
        if((preset = FLUID_NEW(SFPreset)) == NULL)
        {
            FLUID_LOG(FLUID_ERR, "Out of memory");
            return FALSE;
        }

        sf->preset = fluid_list_append(sf->preset, preset);
        preset->zone = NULL; /* In case of failure, fluid_sffile_close can cleanup */
        READSTR(sf, &preset->name); /* possible read failure ^ */
        READW(sf, preset->prenum);
        READW(sf, preset->bank);
        READW(sf, pbag_idx);
        READD(sf, preset->libr);
        READD(sf, preset->genre);
        READD(sf, preset->morph);

        if(prev_preset)
        {
            /* not first preset? */
            if(pbag_idx < prev_pbag_idx)
            {
                FLUID_LOG(FLUID_ERR, "Preset header indices not monotonic");
                return FALSE;
            }

            i2 = pbag_idx - prev_pbag_idx;

            while(i2--)
            {
                prev_preset->zone = fluid_list_prepend(prev_preset->zone, NULL);
            }
        }
        else if(pbag_idx > 0)  /* 1st preset, warn if ofs >0 */
        {
            FLUID_LOG(FLUID_WARN, "%d preset zones not referenced, discarding", pbag_idx);
        }

        prev_preset = preset; /* update preset ptr */
        prev_pbag_idx = pbag_idx;
    }

    FSKIP(sf, 24);
    READW(sf, pbag_idx); /* Read terminal generator index */
    FSKIP(sf, 12);

    if(pbag_idx < prev_pbag_idx)
    {
        FLUID_LOG(FLUID_ERR, "Preset header indices not monotonic");
        return FALSE;
    }

    i2 = pbag_idx - prev_pbag_idx;

    while(i2--)
    {
        prev_preset->zone = fluid_list_prepend(prev_preset->zone, NULL);
    }

    return TRUE;
}

/* preset bag loader */
static int load_pbag(SFData *sf, int size)
{
    fluid_list_t *p, *p2;
    SFZone *z, *pz = NULL;
    unsigned short genndx, modndx;
    unsigned short pgenndx = 0, pmodndx = 0;
    unsigned short i;

    if(size % SF_BAG_SIZE || size == 0)  /* size is multiple of SF_BAG_SIZE? */
    {
        FLUID_LOG(FLUID_ERR, "Preset bag chunk size is invalid");
        return FALSE;
    }

    p = sf->preset;

    while(p)
    {
        /* traverse through presets */
        p2 = ((SFPreset *)(p->data))->zone;

        while(p2)
        {
            /* traverse preset's zones */
            if((size -= SF_BAG_SIZE) < 0)
            {
                FLUID_LOG(FLUID_ERR, "Preset bag chunk size mismatch");
                return FALSE;
            }

            if((z = FLUID_NEW(SFZone)) == NULL)
            {
                FLUID_LOG(FLUID_ERR, "Out of memory");
                return FALSE;
            }

            p2->data = z;
            z->gen = NULL; /* Init gen and mod before possible failure, */
            z->mod = NULL; /* to ensure proper cleanup (fluid_sffile_close) */
            READW(sf, genndx); /* possible read failure ^ */
            READW(sf, modndx);
            z->instsamp = NULL;

            if(pz)
            {
                /* if not first zone */
                if(genndx < pgenndx)
                {
                    FLUID_LOG(FLUID_ERR, "Preset bag generator indices not monotonic");
                    return FALSE;
                }

                if(modndx < pmodndx)
                {
                    FLUID_LOG(FLUID_ERR, "Preset bag modulator indices not monotonic");
                    return FALSE;
                }

                i = genndx - pgenndx;

                while(i--)
                {
                    pz->gen = fluid_list_prepend(pz->gen, NULL);
                }

                i = modndx - pmodndx;

                while(i--)
                {
                    pz->mod = fluid_list_prepend(pz->mod, NULL);
                }
            }

            pz = z; /* update previous zone ptr */
            pgenndx = genndx; /* update previous zone gen index */
            pmodndx = modndx; /* update previous zone mod index */
            p2 = fluid_list_next(p2);
        }

        p = fluid_list_next(p);
    }

    size -= SF_BAG_SIZE;

    if(size != 0)
    {
        FLUID_LOG(FLUID_ERR, "Preset bag chunk size mismatch");
        return FALSE;
    }

    READW(sf, genndx);
    READW(sf, modndx);

    if(!pz)
    {
        if(genndx > 0)
        {
            FLUID_LOG(FLUID_WARN, "No preset generators and terminal index not 0");
        }

        if(modndx > 0)
        {
            FLUID_LOG(FLUID_WARN, "No preset modulators and terminal index not 0");
        }

        return TRUE;
    }

    if(genndx < pgenndx)
    {
        FLUID_LOG(FLUID_ERR, "Preset bag generator indices not monotonic");
        return FALSE;
    }

    if(modndx < pmodndx)
    {
        FLUID_LOG(FLUID_ERR, "Preset bag modulator indices not monotonic");
        return FALSE;
    }

    i = genndx - pgenndx;

    while(i--)
    {
        pz->gen = fluid_list_prepend(pz->gen, NULL);
    }

    i = modndx - pmodndx;

    while(i--)
    {
        pz->mod = fluid_list_prepend(pz->mod, NULL);
    }

    return TRUE;
}

/* preset modulator loader */
static int load_pmod(SFData *sf, int size)
{
    fluid_list_t *p, *p2, *p3;
    SFMod *m;

    p = sf->preset;

    while(p)
    {
        /* traverse through all presets */
        p2 = ((SFPreset *)(p->data))->zone;

        while(p2)
        {
            /* traverse this preset's zones */
            p3 = ((SFZone *)(p2->data))->mod;

            while(p3)
            {
                /* load zone's modulators */
                if((size -= SF_MOD_SIZE) < 0)
                {
                    FLUID_LOG(FLUID_ERR, "Preset modulator chunk size mismatch");
                    return FALSE;
                }

                if((m = FLUID_NEW(SFMod)) == NULL)
                {
                    FLUID_LOG(FLUID_ERR, "Out of memory");
                    return FALSE;
                }

                p3->data = m;
                READW(sf, m->src);
                READW(sf, m->dest);
                READW(sf, m->amount);
                READW(sf, m->amtsrc);
                READW(sf, m->trans);
                p3 = fluid_list_next(p3);
            }

            p2 = fluid_list_next(p2);
        }

        p = fluid_list_next(p);
    }

    /*
       If there isn't even a terminal record
       Hmmm, the specs say there should be one, but..
     */
    if(size == 0)
    {
        return TRUE;
    }

    size -= SF_MOD_SIZE;

    if(size != 0)
    {
        FLUID_LOG(FLUID_ERR, "Preset modulator chunk size mismatch");
        return FALSE;
    }

    FSKIP(sf, SF_MOD_SIZE); /* terminal mod */

    return TRUE;
}

/* -------------------------------------------------------------------
 * preset generator loader
 * generator (per preset) loading rules:
 * Zones with no generators or modulators shall be annihilated
 * Global zone must be 1st zone, discard additional ones (instrumentless zones)
 *
 * generator (per zone) loading rules (in order of decreasing precedence):
 * KeyRange is 1st in list (if exists), else discard
 * if a VelRange exists only preceded by a KeyRange, else discard
 * if a generator follows an instrument discard it
 * if a duplicate generator exists replace previous one
 * ------------------------------------------------------------------- */
static int load_pgen(SFData *sf, int size)
{
    fluid_list_t *p, *p2, *p3, *dup, **hz = NULL;
    SFZone *z;
    SFGen *g;
    SFGenAmount genval;
    unsigned short genid;
    int level, skip, drop, gzone, discarded;

    p = sf->preset;

    while(p)
    {
        /* traverse through all presets */
        gzone = FALSE;
        discarded = FALSE;
        p2 = ((SFPreset *)(p->data))->zone;

        if(p2)
        {
            hz = &p2;
        }

        while(p2)
        {
            /* traverse preset's zones */
            level = 0;
            z = (SFZone *)(p2->data);
            p3 = z->gen;

            while(p3)
            {
                /* load zone's generators */
                dup = NULL;
                skip = FALSE;
                drop = FALSE;

                if((size -= SF_GEN_SIZE) < 0)
                {
                    FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch");
                    return FALSE;
                }

                READW(sf, genid);

                if(genid == Gen_KeyRange)
                {
                    /* nothing precedes */
                    if(level == 0)
                    {
                        level = 1;
                        READB(sf, genval.range.lo);
                        READB(sf, genval.range.hi);
                    }
                    else
                    {
                        skip = TRUE;
                    }
                }
                else if(genid == Gen_VelRange)
                {
                    /* only KeyRange precedes */
                    if(level <= 1)
                    {
                        level = 2;
                        READB(sf, genval.range.lo);
                        READB(sf, genval.range.hi);
                    }
                    else
                    {
                        skip = TRUE;
                    }
                }
                else if(genid == Gen_Instrument)
                {
                    /* inst is last gen */
                    level = 3;
                    READW(sf, genval.uword);
                    ((SFZone *)(p2->data))->instsamp = FLUID_INT_TO_POINTER(genval.uword + 1);
                    break; /* break out of generator loop */
                }
                else
                {
                    level = 2;

                    if(valid_preset_genid(genid))
                    {
                        /* generator valid? */
                        READW(sf, genval.sword);
                        dup = find_gen_by_id(genid, z->gen);
                    }
                    else
                    {
                        skip = TRUE;
                    }
                }

                if(!skip)
                {
                    if(!dup)
                    {
                        /* if gen ! dup alloc new */
                        if((g = FLUID_NEW(SFGen)) == NULL)
                        {
                            FLUID_LOG(FLUID_ERR, "Out of memory");
                            return FALSE;
                        }

                        p3->data = g;
                        g->id = genid;
                    }
                    else
                    {
                        g = (SFGen *)(dup->data); /* ptr to orig gen */
                        drop = TRUE;
                    }

                    g->amount = genval;
                }
                else
                {
                    /* Skip this generator */
                    discarded = TRUE;
                    drop = TRUE;
                    FSKIPW(sf);
                }

                if(!drop)
                {
                    p3 = fluid_list_next(p3);    /* next gen */
                }
                else
                {
                    SLADVREM(z->gen, p3);    /* drop place holder */
                }

            } /* generator loop */

            if(level == 3)
            {
                SLADVREM(z->gen, p3);    /* zone has inst? */
            }
            else
            {
                /* congratulations its a global zone */
                if(!gzone)
                {
                    /* Prior global zones? */
                    gzone = TRUE;

                    /* if global zone is not 1st zone, relocate */
                    if(*hz != p2)
                    {
                        void *save = p2->data;
                        FLUID_LOG(FLUID_WARN, "Preset '%s': Global zone is not first zone",
                                  ((SFPreset *)(p->data))->name);
                        SLADVREM(*hz, p2);
                        *hz = fluid_list_prepend(*hz, save);
                        continue;
                    }
                }
                else
                {
                    /* previous global zone exists, discard */
                    FLUID_LOG(FLUID_WARN, "Preset '%s': Discarding invalid global zone",
                              ((SFPreset *)(p->data))->name);
                    *hz = fluid_list_remove(*hz, p2->data);
                    delete_zone((SFZone *)fluid_list_get(p2));
                }
            }

            while(p3)
            {
                /* Kill any zones following an instrument */
                discarded = TRUE;

                if((size -= SF_GEN_SIZE) < 0)
                {
                    FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch");
                    return FALSE;
                }

                FSKIP(sf, SF_GEN_SIZE);
                SLADVREM(z->gen, p3);
            }

            p2 = fluid_list_next(p2); /* next zone */
        }

        if(discarded)
        {
            FLUID_LOG(FLUID_WARN,
                      "Preset '%s': Some invalid generators were discarded",
                      ((SFPreset *)(p->data))->name);
        }

        p = fluid_list_next(p);
    }

    /* in case there isn't a terminal record */
    if(size == 0)
    {
        return TRUE;
    }

    size -= SF_GEN_SIZE;

    if(size != 0)
    {
        FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch");
        return FALSE;
    }

    FSKIP(sf, SF_GEN_SIZE); /* terminal gen */

    return TRUE;
}

/* instrument header loader */
static int load_ihdr(SFData *sf, int size)
{
    int i, i2;
    SFInst *p, *pr = NULL; /* ptr to current & previous instrument */
    unsigned short zndx, pzndx = 0;

    if(size % SF_IHDR_SIZE || size == 0)  /* chunk size is valid? */
    {
        FLUID_LOG(FLUID_ERR, "Instrument header has invalid size");
        return FALSE;
    }

    size = size / SF_IHDR_SIZE - 1;

    if(size == 0)
    {
        /* at least one preset + term record */
        FLUID_LOG(FLUID_WARN, "File contains no instruments");
        FSKIP(sf, SF_IHDR_SIZE);
        return TRUE;
    }

    for(i = 0; i < size; i++)
    {
        /* load all instrument headers */
        if((p = FLUID_NEW(SFInst)) == NULL)
        {
            FLUID_LOG(FLUID_ERR, "Out of memory");
            return FALSE;
        }

        sf->inst = fluid_list_append(sf->inst, p);
        p->zone = NULL; /* For proper cleanup if fail (fluid_sffile_close) */
        p->idx = i;
        READSTR(sf, &p->name); /* Possible read failure ^ */
        READW(sf, zndx);

        if(pr)
        {
            /* not first instrument? */
            if(zndx < pzndx)
            {
                FLUID_LOG(FLUID_ERR, "Instrument header indices not monotonic");
                return FALSE;
            }

            i2 = zndx - pzndx;

            while(i2--)
            {
                pr->zone = fluid_list_prepend(pr->zone, NULL);
            }
        }
        else if(zndx > 0)  /* 1st inst, warn if ofs >0 */
        {
            FLUID_LOG(FLUID_WARN, "%d instrument zones not referenced, discarding", zndx);
        }

        pzndx = zndx;
        pr = p; /* update instrument ptr */
    }

    FSKIP(sf, 20);
    READW(sf, zndx);

    if(zndx < pzndx)
    {
        FLUID_LOG(FLUID_ERR, "Instrument header indices not monotonic");
        return FALSE;
    }

    i2 = zndx - pzndx;

    while(i2--)
    {
        pr->zone = fluid_list_prepend(pr->zone, NULL);
    }

    return TRUE;
}

/* instrument bag loader */
static int load_ibag(SFData *sf, int size)
{
    fluid_list_t *p, *p2;
    SFZone *z, *pz = NULL;
    unsigned short genndx, modndx, pgenndx = 0, pmodndx = 0;
    int i;

    if(size % SF_BAG_SIZE || size == 0)  /* size is multiple of SF_BAG_SIZE? */
    {
        FLUID_LOG(FLUID_ERR, "Instrument bag chunk size is invalid");
        return FALSE;
    }

    p = sf->inst;

    while(p)
    {
        /* traverse through inst */
        p2 = ((SFInst *)(p->data))->zone;

        while(p2)
        {
            /* load this inst's zones */
            if((size -= SF_BAG_SIZE) < 0)
            {
                FLUID_LOG(FLUID_ERR, "Instrument bag chunk size mismatch");
                return FALSE;
            }

            if((z = FLUID_NEW(SFZone)) == NULL)
            {
                FLUID_LOG(FLUID_ERR, "Out of memory");
                return FALSE;
            }

            p2->data = z;
            z->gen = NULL; /* In case of failure, */
            z->mod = NULL; /* fluid_sffile_close can clean up */
            READW(sf, genndx); /* READW = possible read failure */
            READW(sf, modndx);
            z->instsamp = NULL;

            if(pz)
            {
                /* if not first zone */
                if(genndx < pgenndx)
                {
                    FLUID_LOG(FLUID_ERR, "Instrument generator indices not monotonic");
                    return FALSE;
                }

                if(modndx < pmodndx)
                {
                    FLUID_LOG(FLUID_ERR, "Instrument modulator indices not monotonic");
                    return FALSE;
                }

                i = genndx - pgenndx;

                while(i--)
                {
                    pz->gen = fluid_list_prepend(pz->gen, NULL);
                }

                i = modndx - pmodndx;

                while(i--)
                {
                    pz->mod = fluid_list_prepend(pz->mod, NULL);
                }
            }

            pz = z; /* update previous zone ptr */
            pgenndx = genndx;
            pmodndx = modndx;
            p2 = fluid_list_next(p2);
        }

        p = fluid_list_next(p);
    }

    size -= SF_BAG_SIZE;

    if(size != 0)
    {
        FLUID_LOG(FLUID_ERR, "Instrument chunk size mismatch");
        return FALSE;
    }

    READW(sf, genndx);
    READW(sf, modndx);

    if(!pz)
    {
        /* in case that all are no zoners */
        if(genndx > 0)
        {
            FLUID_LOG(FLUID_WARN, "No instrument generators and terminal index not 0");
        }

        if(modndx > 0)
        {
            FLUID_LOG(FLUID_WARN, "No instrument modulators and terminal index not 0");
        }

        return TRUE;
    }

    if(genndx < pgenndx)
    {
        FLUID_LOG(FLUID_ERR, "Instrument generator indices not monotonic");
        return FALSE;
    }

    if(modndx < pmodndx)
    {
        FLUID_LOG(FLUID_ERR, "Instrument modulator indices not monotonic");
        return FALSE;
    }

    i = genndx - pgenndx;

    while(i--)
    {
        pz->gen = fluid_list_prepend(pz->gen, NULL);
    }

    i = modndx - pmodndx;

    while(i--)
    {
        pz->mod = fluid_list_prepend(pz->mod, NULL);
    }

    return TRUE;
}

/* instrument modulator loader */
static int load_imod(SFData *sf, int size)
{
    fluid_list_t *p, *p2, *p3;
    SFMod *m;

    p = sf->inst;

    while(p)
    {
        /* traverse through all inst */
        p2 = ((SFInst *)(p->data))->zone;

        while(p2)
        {
            /* traverse this inst's zones */
            p3 = ((SFZone *)(p2->data))->mod;

            while(p3)
            {
                /* load zone's modulators */
                if((size -= SF_MOD_SIZE) < 0)
                {
                    FLUID_LOG(FLUID_ERR, "Instrument modulator chunk size mismatch");
                    return FALSE;
                }

                if((m = FLUID_NEW(SFMod)) == NULL)
                {
                    FLUID_LOG(FLUID_ERR, "Out of memory");
                    return FALSE;
                }

                p3->data = m;
                READW(sf, m->src);
                READW(sf, m->dest);
                READW(sf, m->amount);
                READW(sf, m->amtsrc);
                READW(sf, m->trans);
                p3 = fluid_list_next(p3);
            }

            p2 = fluid_list_next(p2);
        }

        p = fluid_list_next(p);
    }

    /*
       If there isn't even a terminal record
       Hmmm, the specs say there should be one, but..
     */
    if(size == 0)
    {
        return TRUE;
    }

    size -= SF_MOD_SIZE;

    if(size != 0)
    {
        FLUID_LOG(FLUID_ERR, "Instrument modulator chunk size mismatch");
        return FALSE;
    }

    FSKIP(sf, SF_MOD_SIZE); /* terminal mod */

    return TRUE;
}

/* load instrument generators (see load_pgen for loading rules) */
static int load_igen(SFData *sf, int size)
{
    fluid_list_t *p, *p2, *p3, *dup, **hz = NULL;
    SFZone *z;
    SFGen *g;
    SFGenAmount genval;
    unsigned short genid;
    int level, skip, drop, gzone, discarded;

    p = sf->inst;

    while(p)
    {
        /* traverse through all instruments */
        gzone = FALSE;
        discarded = FALSE;
        p2 = ((SFInst *)(p->data))->zone;

        if(p2)
        {
            hz = &p2;
        }

        while(p2)
        {
            /* traverse this instrument's zones */
            level = 0;
            z = (SFZone *)(p2->data);
            p3 = z->gen;

            while(p3)
            {
                /* load zone's generators */
                dup = NULL;
                skip = FALSE;
                drop = FALSE;

                if((size -= SF_GEN_SIZE) < 0)
                {
                    FLUID_LOG(FLUID_ERR, "IGEN chunk size mismatch");
                    return FALSE;
                }

                READW(sf, genid);

                if(genid == Gen_KeyRange)
                {
                    /* nothing precedes */
                    if(level == 0)
                    {
                        level = 1;
                        READB(sf, genval.range.lo);
                        READB(sf, genval.range.hi);
                    }
                    else
                    {
                        skip = TRUE;
                    }
                }
                else if(genid == Gen_VelRange)
                {
                    /* only KeyRange precedes */
                    if(level <= 1)
                    {
                        level = 2;
                        READB(sf, genval.range.lo);
                        READB(sf, genval.range.hi);
                    }
                    else
                    {
                        skip = TRUE;
                    }
                }
                else if(genid == Gen_SampleId)
                {
                    /* sample is last gen */
                    level = 3;
                    READW(sf, genval.uword);
                    ((SFZone *)(p2->data))->instsamp = FLUID_INT_TO_POINTER(genval.uword + 1);
                    break; /* break out of generator loop */
                }
                else
                {
                    level = 2;

                    if(valid_inst_genid(genid))
                    {
                        /* gen valid? */
                        READW(sf, genval.sword);
                        dup = find_gen_by_id(genid, z->gen);
                    }
                    else
                    {
                        skip = TRUE;
                    }
                }

                if(!skip)
                {
                    if(!dup)
                    {
                        /* if gen ! dup alloc new */
                        if((g = FLUID_NEW(SFGen)) == NULL)
                        {
                            FLUID_LOG(FLUID_ERR, "Out of memory");
                            return FALSE;
                        }

                        p3->data = g;
                        g->id = genid;
                    }
                    else
                    {
                        g = (SFGen *)(dup->data);
                        drop = TRUE;
                    }

                    g->amount = genval;
                }
                else
                {
                    /* skip this generator */
                    discarded = TRUE;
                    drop = TRUE;
                    FSKIPW(sf);
                }

                if(!drop)
                {
                    p3 = fluid_list_next(p3);    /* next gen */
                }
                else
                {
                    SLADVREM(z->gen, p3);
                }

            } /* generator loop */

            if(level == 3)
            {
                SLADVREM(z->gen, p3);    /* zone has sample? */
            }
            else
            {
                /* its a global zone */
                if(!gzone)
                {
                    gzone = TRUE;

                    /* if global zone is not 1st zone, relocate */
                    if(*hz != p2)
                    {
                        void *save = p2->data;
                        FLUID_LOG(FLUID_WARN, "Instrument '%s': Global zone is not first zone",
                                  ((SFPreset *)(p->data))->name);
                        SLADVREM(*hz, p2);
                        *hz = fluid_list_prepend(*hz, save);
                        continue;
                    }
                }
                else
                {
                    /* previous global zone exists, discard */
                    FLUID_LOG(FLUID_WARN, "Instrument '%s': Discarding invalid global zone",
                              ((SFInst *)(p->data))->name);
                    *hz = fluid_list_remove(*hz, p2->data);
                    delete_zone((SFZone *)fluid_list_get(p2));
                }
            }

            while(p3)
            {
                /* Kill any zones following a sample */
                discarded = TRUE;

                if((size -= SF_GEN_SIZE) < 0)
                {
                    FLUID_LOG(FLUID_ERR, "Instrument generator chunk size mismatch");
                    return FALSE;
                }

                FSKIP(sf, SF_GEN_SIZE);
                SLADVREM(z->gen, p3);
            }

            p2 = fluid_list_next(p2); /* next zone */
        }

        if(discarded)
        {
            FLUID_LOG(FLUID_WARN,
                      "Instrument '%s': Some invalid generators were discarded",
                      ((SFInst *)(p->data))->name);
        }

        p = fluid_list_next(p);
    }

    /* for those non-terminal record cases, grr! */
    if(size == 0)
    {
        return TRUE;
    }

    size -= SF_GEN_SIZE;

    if(size != 0)
    {
        FLUID_LOG(FLUID_ERR, "IGEN chunk size mismatch");
        return FALSE;
    }

    FSKIP(sf, SF_GEN_SIZE); /* terminal gen */

    return TRUE;
}

/* sample header loader */
static int load_shdr(SFData *sf, unsigned int size)
{
    unsigned int i;
    SFSample *p;

    if(size % SF_SHDR_SIZE || size == 0)  /* size is multiple of SHDR size? */
    {
        FLUID_LOG(FLUID_ERR, "Sample header has invalid size");
        return FALSE;
    }

    size = size / SF_SHDR_SIZE - 1;

    if(size == 0)
    {
        /* at least one sample + term record? */
        FLUID_LOG(FLUID_WARN, "File contains no samples");
        FSKIP(sf, SF_SHDR_SIZE);
        return TRUE;
    }

    /* load all sample headers */
    for(i = 0; i < size; i++)
    {
        if((p = FLUID_NEW(SFSample)) == NULL)
        {
            FLUID_LOG(FLUID_ERR, "Out of memory");
            return FALSE;
        }

        sf->sample = fluid_list_append(sf->sample, p);
        READSTR(sf, &p->name);
        READD(sf, p->start);
        READD(sf, p->end);
        READD(sf, p->loopstart);
        READD(sf, p->loopend);
        READD(sf, p->samplerate);
        READB(sf, p->origpitch);
        READB(sf, p->pitchadj);
        FSKIPW(sf); /* skip sample link */
        READW(sf, p->sampletype);
    }

    FSKIP(sf, SF_SHDR_SIZE); /* skip terminal shdr */

    return TRUE;
}

/* "fixup" (inst # -> inst ptr) instrument references in preset list */
static int fixup_pgen(SFData *sf)
{
    fluid_list_t *p, *p2, *p3;
    SFZone *z;
    int i;

    p = sf->preset;

    while(p)
    {
        p2 = ((SFPreset *)(p->data))->zone;

        while(p2)
        {
            /* traverse this preset's zones */
            z = (SFZone *)(p2->data);

            if((i = FLUID_POINTER_TO_INT(z->instsamp)))
            {
                /* load instrument # */
                p3 = fluid_list_nth(sf->inst, i - 1);

                if(!p3)
                {
                    FLUID_LOG(FLUID_ERR, "Preset %03d %03d: Invalid instrument reference",
                              ((SFPreset *)(p->data))->bank, ((SFPreset *)(p->data))->prenum);
                    return FALSE;
                }

                z->instsamp = p3;
            }
            else
            {
                z->instsamp = NULL;
            }

            p2 = fluid_list_next(p2);
        }

        p = fluid_list_next(p);
    }

    return TRUE;
}

/* "fixup" (sample # -> sample ptr) sample references in instrument list */
static int fixup_igen(SFData *sf)
{
    fluid_list_t *p, *p2, *p3;
    SFZone *z;
    int i;

    p = sf->inst;

    while(p)
    {
        p2 = ((SFInst *)(p->data))->zone;

        while(p2)
        {
            /* traverse instrument's zones */
            z = (SFZone *)(p2->data);

            if((i = FLUID_POINTER_TO_INT(z->instsamp)))
            {
                /* load sample # */
                p3 = fluid_list_nth(sf->sample, i - 1);

                if(!p3)
                {
                    FLUID_LOG(FLUID_ERR, "Instrument '%s': Invalid sample reference",
                              ((SFInst *)(p->data))->name);
                    return FALSE;
                }

                z->instsamp = p3;
            }

            p2 = fluid_list_next(p2);
        }

        p = fluid_list_next(p);
    }

    return TRUE;
}

static void delete_preset(SFPreset *preset)
{
    fluid_list_t *entry;
    SFZone *zone;

    if(!preset)
    {
        return;
    }

    entry = preset->zone;

    while(entry)
    {
        zone = (SFZone *)fluid_list_get(entry);
        delete_zone(zone);
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(preset->zone);

    FLUID_FREE(preset);
}

static void delete_inst(SFInst *inst)
{
    fluid_list_t *entry;
    SFZone *zone;

    if(!inst)
    {
        return;
    }

    entry = inst->zone;

    while(entry)
    {
        zone = (SFZone *)fluid_list_get(entry);
        delete_zone(zone);
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(inst->zone);

    FLUID_FREE(inst);
}


/* Free all elements of a zone (Preset or Instrument) */
static void delete_zone(SFZone *zone)
{
    fluid_list_t *entry;

    if(!zone)
    {
        return;
    }

    entry = zone->gen;

    while(entry)
    {
        FLUID_FREE(fluid_list_get(entry));
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(zone->gen);

    entry = zone->mod;

    while(entry)
    {
        FLUID_FREE(fluid_list_get(entry));
        entry = fluid_list_next(entry);
    }

    delete_fluid_list(zone->mod);

    FLUID_FREE(zone);
}

/* preset sort function, first by bank, then by preset # */
static int preset_compare_func(void *a, void *b)
{
    int aval, bval;

    aval = (int)(((SFPreset *)a)->bank) << 16 | ((SFPreset *)a)->prenum;
    bval = (int)(((SFPreset *)b)->bank) << 16 | ((SFPreset *)b)->prenum;

    return (aval - bval);
}

/* Find a generator by its id in the passed in list.
 *
 * @return pointer to SFGen if found, otherwise NULL
 */
static fluid_list_t *find_gen_by_id(int gen, fluid_list_t *genlist)
{
    /* is generator in gen list? */
    fluid_list_t *p;

    p = genlist;

    while(p)
    {
        if(p->data == NULL)
        {
            return NULL;
        }

        if(gen == ((SFGen *)p->data)->id)
        {
            break;
        }

        p = fluid_list_next(p);
    }

    return p;
}

/* check validity of instrument generator */
static int valid_inst_genid(unsigned short genid)
{
    int i = 0;

    if(genid > Gen_MaxValid)
    {
        return FALSE;
    }

    while(invalid_inst_gen[i] && invalid_inst_gen[i] != genid)
    {
        i++;
    }

    return (invalid_inst_gen[i] == 0);
}

/* check validity of preset generator */
static int valid_preset_genid(unsigned short genid)
{
    int i = 0;

    if(!valid_inst_genid(genid))
    {
        return FALSE;
    }

    while(invalid_preset_gen[i] && invalid_preset_gen[i] != genid)
    {
        i++;
    }

    return (invalid_preset_gen[i] == 0);
}


static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24)
{
    short *loaded_data = NULL;
    char *loaded_data24 = NULL;

    int num_samples = (end + 1) - start;
    fluid_return_val_if_fail(num_samples > 0, -1);

    if((start * sizeof(short) > sf->samplesize) || (end * sizeof(short) > sf->samplesize))
    {
        FLUID_LOG(FLUID_ERR, "Sample offsets exceed sample data chunk");
        goto error_exit;
    }

    /* Load 16-bit sample data */
    if(sf->fcbs->fseek(sf->sffd, sf->samplepos + (start * sizeof(short)), SEEK_SET) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Failed to seek to sample position");
        goto error_exit;
    }

    loaded_data = FLUID_ARRAY(short, num_samples);

    if(loaded_data == NULL)
    {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        goto error_exit;
    }

    if(sf->fcbs->fread(loaded_data, num_samples * sizeof(short), sf->sffd) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Failed to read sample data");
        goto error_exit;
    }

    /* If this machine is big endian, byte swap the 16 bit samples */
    if(FLUID_IS_BIG_ENDIAN)
    {
        int i;

        for(i = 0; i < num_samples; i++)
        {
            loaded_data[i] = FLUID_LE16TOH(loaded_data[i]);
        }
    }

    *data = loaded_data;

    /* Optionally load additional 8 bit sample data for 24-bit support. Any failures while loading
     * the 24-bit sample data will be logged as errors but won't prevent the sample reading to
     * fail, as sound output is still possible with the 16-bit sample data. */
    if(sf->sample24pos)
    {
        if((start > sf->sample24size) || (end > sf->sample24size))
        {
            FLUID_LOG(FLUID_ERR, "Sample offsets exceed 24-bit sample data chunk");
            goto error24_exit;
        }

        if(sf->fcbs->fseek(sf->sffd, sf->sample24pos + start, SEEK_SET) == FLUID_FAILED)
        {
            FLUID_LOG(FLUID_ERR, "Failed to seek position for 24-bit sample data in data file");
            goto error24_exit;
        }

        loaded_data24 = FLUID_ARRAY(char, num_samples);

        if(loaded_data24 == NULL)
        {
            FLUID_LOG(FLUID_ERR, "Out of memory reading 24-bit sample data");
            goto error24_exit;
        }

        if(sf->fcbs->fread(loaded_data24, num_samples, sf->sffd) == FLUID_FAILED)
        {
            FLUID_LOG(FLUID_ERR, "Failed to read 24-bit sample data");
            goto error24_exit;
        }
    }

    *data24 = loaded_data24;

    return num_samples;

error24_exit:
    FLUID_LOG(FLUID_WARN, "Ignoring 24-bit sample data, sound quality might suffer");
    FLUID_FREE(loaded_data24);
    *data24 = NULL;
    return num_samples;

error_exit:
    FLUID_FREE(loaded_data);
    FLUID_FREE(loaded_data24);
    return -1;
}


/* Ogg Vorbis loading and decompression */
#if LIBSNDFILE_SUPPORT

/* Virtual file access routines to allow loading individually compressed
 * samples from the Soundfont sample data chunk using the file callbacks
 * passing in during opening of the file */
typedef struct _sfvio_data_t
{
    SFData *sffile;
    sf_count_t start;  /* start byte offset of compressed data */
    sf_count_t end;    /* end byte offset of compressed data */
    sf_count_t offset; /* current virtual file offset from start byte offset */

} sfvio_data_t;

static sf_count_t sfvio_get_filelen(void *user_data)
{
    sfvio_data_t *data = user_data;

    return (data->end + 1) - data->start;
}

static sf_count_t sfvio_seek(sf_count_t offset, int whence, void *user_data)
{
    sfvio_data_t *data = user_data;
    SFData *sf = data->sffile;
    sf_count_t new_offset;

    switch(whence)
    {
    case SEEK_SET:
        new_offset = offset;
        break;

    case SEEK_CUR:
        new_offset = data->offset + offset;
        break;

    case SEEK_END:
        new_offset = sfvio_get_filelen(user_data) + offset;
        break;

    default:
        goto fail; /* proper error handling not possible?? */
    }

    if(sf->fcbs->fseek(sf->sffd, sf->samplepos + data->start + new_offset, SEEK_SET) != FLUID_FAILED)
    {
        data->offset = new_offset;
    }

fail:
    return data->offset;
}

static sf_count_t sfvio_read(void *ptr, sf_count_t count, void *user_data)
{
    sfvio_data_t *data = user_data;
    SFData *sf = data->sffile;
    sf_count_t remain;

    remain = sfvio_get_filelen(user_data) - data->offset;

    if(count > remain)
    {
        count = remain;
    }

    if(count == 0)
    {
        return count;
    }

    if(sf->fcbs->fread(ptr, count, sf->sffd) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Failed to read compressed sample data");
        return 0;
    }

    data->offset += count;

    return count;
}

static sf_count_t sfvio_tell(void *user_data)
{
    sfvio_data_t *data = user_data;

    return data->offset;
}

/**
 * Read Ogg Vorbis compressed data from the Soundfont and decompress it, returning the number of samples
 * in the decompressed WAV. Only 16-bit mono samples are supported.
 *
 * Note that this function takes byte indices for start and end source data. The sample headers in SF3
 * files use byte indices, so those pointers can be passed directly to this function.
 *
 * This function uses a virtual file structure in order to read the Ogg Vorbis
 * data from arbitrary locations in the source file.
 */
static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data)
{
    SNDFILE *sndfile;
    SF_INFO sfinfo;
    SF_VIRTUAL_IO sfvio =
    {
        sfvio_get_filelen,
        sfvio_seek,
        sfvio_read,
        NULL,
        sfvio_tell
    };
    sfvio_data_t sfdata;
    short *wav_data = NULL;

    if((start_byte > sf->samplesize) || (end_byte > sf->samplesize))
    {
        FLUID_LOG(FLUID_ERR, "Ogg Vorbis data offsets exceed sample data chunk");
        return -1;
    }

    // Initialize file position indicator and SF_INFO structure
    sfdata.sffile = sf;
    sfdata.start = start_byte;
    sfdata.end = end_byte;
    sfdata.offset = 0;

    FLUID_MEMSET(&sfinfo, 0, sizeof(sfinfo));

    /* Seek to beginning of Ogg Vorbis data in Soundfont */
    if(sf->fcbs->fseek(sf->sffd, sf->samplepos + start_byte, SEEK_SET) == FLUID_FAILED)
    {
        FLUID_LOG(FLUID_ERR, "Failed to seek to compressd sample position");
        return -1;
    }

    // Open sample as a virtual file
    sndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, &sfdata);

    if(!sndfile)
    {
        FLUID_LOG(FLUID_ERR, "%s", sf_strerror(sndfile));
        return -1;
    }

    // Empty sample
    if(sfinfo.frames <= 0 || sfinfo.channels <= 0)
    {
        FLUID_LOG(FLUID_DBG, "Empty decompressed sample");
        *data = NULL;
        sf_close(sndfile);
        return 0;
    }

    // Mono sample
    if(sfinfo.channels != 1)
    {
        FLUID_LOG(FLUID_DBG, "Unsupported channel count %d in ogg sample", sfinfo.channels);
        goto error_exit;
    }

    if((sfinfo.format & SF_FORMAT_OGG) == 0)
    {
        FLUID_LOG(FLUID_WARN, "OGG sample is not OGG compressed, this is not officially supported");
    }

    wav_data = FLUID_ARRAY(short, sfinfo.frames * sfinfo.channels);

    if(!wav_data)
    {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        goto error_exit;
    }

    /* Automatically decompresses the Ogg Vorbis data to 16-bit PCM */
    if(sf_readf_short(sndfile, wav_data, sfinfo.frames) < sfinfo.frames)
    {
        FLUID_LOG(FLUID_DBG, "Decompression failed!");
        FLUID_LOG(FLUID_ERR, "%s", sf_strerror(sndfile));
        goto error_exit;
    }

    sf_close(sndfile);

    *data = wav_data;

    return sfinfo.frames;

error_exit:
    FLUID_FREE(wav_data);
    sf_close(sndfile);
    return -1;
}
#else
static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data)
{
    return -1;
}
#endif
