Robin Gareus
d425f6dcb5
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 ...
2671 lines
67 KiB
C
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
|