13
0
livetrax/libs/fluidsynth/src/fluid_sffile.c
Robin Gareus d425f6dcb5
Update to fluidsynth-2.1
see https://github.com/FluidSynth/fluidsynth/releases/tag/v2.1.0

- new, less "ringing" reverb engine
- new, stereophonic chorus engine
- improved integrity checking of SoundFont modulators
...
2019-12-03 00:01:10 +01:00

2671 lines
67 KiB
C

/* 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 exeeds 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);
p->samfile = 0;
}
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 rountines 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;
}
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