Merge pull request #366 from FluidSynth/dynamic-sample-loading

Dynamic sample loading
This commit is contained in:
Marcus Weseloh 2018-04-22 19:06:53 +02:00 committed by GitHub
commit a95a4864fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 874 additions and 321 deletions

View file

@ -504,6 +504,7 @@ endif ( enable-readline )
if(enable-tests) if(enable-tests)
# manipulate some variables to setup a proper test env # manipulate some variables to setup a proper test env
set(TEST_SOUNDFONT "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf2") set(TEST_SOUNDFONT "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf2")
set(TEST_SOUNDFONT_SF3 "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf3")
# force to build a static lib, in order to bypass the visibility control, allowing us # force to build a static lib, in order to bypass the visibility control, allowing us
# to test fluidsynths private/internal functions # to test fluidsynths private/internal functions

View file

@ -11,5 +11,10 @@ macro ( ADD_FLUID_TEST _test )
$<TARGET_PROPERTY:libfluidsynth,INCLUDE_DIRECTORIES> # include all other header search paths needed by libfluidsynth (esp. glib) $<TARGET_PROPERTY:libfluidsynth,INCLUDE_DIRECTORIES> # include all other header search paths needed by libfluidsynth (esp. glib)
) )
# add the test to ctest
ADD_TEST(NAME ${_test} COMMAND ${_test}) ADD_TEST(NAME ${_test} COMMAND ${_test})
# append the current unit test to check-target as dependency
add_dependencies(check ${_test})
endmacro ( ADD_FLUID_TEST ) endmacro ( ADD_FLUID_TEST )

View file

@ -66,6 +66,15 @@ https://stackoverflow.com/a/6251757
<desc> <desc>
Device identifier used for SYSEX commands, such as MIDI Tuning Standard commands. Only those SYSEX commands destined for this ID or to all devices will be acted upon.</desc> Device identifier used for SYSEX commands, such as MIDI Tuning Standard commands. Only those SYSEX commands destined for this ID or to all devices will be acted upon.</desc>
</setting> </setting>
<setting>
<name>dynamic-sample-loading</name>
<type>bool</type>
<def>0 (FALSE)</def>
<desc>
When set to 1 (TRUE), samples are loaded to and unloaded from memory
on demand.
</desc>
</setting>
<setting> <setting>
<name>effects-channels</name> <name>effects-channels</name>
<type>int</type> <type>int</type>

Binary file not shown.

View file

@ -193,6 +193,9 @@
/* Soundfont to load for unit testing */ /* Soundfont to load for unit testing */
#cmakedefine TEST_SOUNDFONT "@TEST_SOUNDFONT@" #cmakedefine TEST_SOUNDFONT "@TEST_SOUNDFONT@"
/* SF3 Soundfont to load for unit testing */
#cmakedefine TEST_SOUNDFONT_SF3 "@TEST_SOUNDFONT_SF3@"
/* Define to enable SIGFPE assertions */ /* Define to enable SIGFPE assertions */
#cmakedefine TRAP_ON_FPE @TRAP_ON_FPE@ #cmakedefine TRAP_ON_FPE @TRAP_ON_FPE@

View file

@ -33,6 +33,13 @@
* compatible as most existing soundfonts expect exactly this (strange, non-standard) behaviour. */ * compatible as most existing soundfonts expect exactly this (strange, non-standard) behaviour. */
#define EMU_ATTENUATION_FACTOR (0.4f) #define EMU_ATTENUATION_FACTOR (0.4f)
/* Dynamic sample loading functions */
static int load_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset);
static int unload_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset);
static void unload_sample(fluid_sample_t *sample);
static int dynamic_samples_preset_notify(fluid_preset_t *preset, int reason, int chan);
static int dynamic_samples_sample_notify(fluid_sample_t *sample, int reason);
/*************************************************************** /***************************************************************
* *
@ -195,6 +202,7 @@ fluid_defsfont_t* new_fluid_defsfont(fluid_settings_t* settings)
FLUID_MEMSET(defsfont, 0, sizeof(*defsfont)); FLUID_MEMSET(defsfont, 0, sizeof(*defsfont));
fluid_settings_getint(settings, "synth.lock-memory", &defsfont->mlock); fluid_settings_getint(settings, "synth.lock-memory", &defsfont->mlock);
fluid_settings_getint(settings, "synth.dynamic-sample-loading", &defsfont->dynamic_samples);
return defsfont; return defsfont;
} }
@ -252,6 +260,115 @@ const char* fluid_defsfont_get_name(fluid_defsfont_t* defsfont)
return defsfont->filename; return defsfont->filename;
} }
/* Load sample data for a single sample from the Soundfont file.
* Returns FLUID_OK on error, otherwise FLUID_FAILED
*/
int fluid_defsfont_load_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata, fluid_sample_t *sample)
{
int num_samples;
int source_end = sample->source_end;
/* For uncompressed samples we want to include the 46 zero sample word area following each sample
* in the Soundfont. Otherwise samples with loopend > end, which we have decided not to correct, would
* be corrected after all in fluid_sample_sanitize_loop */
if (!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS))
{
source_end += 46; /* Length of zero sample word after each sample, according to SF specs */
/* Safeguard against Soundfonts that are not quite valid and don't include 46 sample words after the
* last sample */
if (source_end >= (defsfont->samplesize / sizeof(short)))
{
source_end = defsfont->samplesize / sizeof(short);
}
}
num_samples = fluid_samplecache_load(
sfdata, sample->source_start, source_end, sample->sampletype,
defsfont->mlock, &sample->data, &sample->data24);
if (num_samples < 0)
{
return FLUID_FAILED;
}
if (num_samples == 0)
{
sample->start = sample->end = 0;
sample->loopstart= sample->loopend = 0;
return FLUID_OK;
}
/* Ogg Vorbis samples already have loop pointers relative to the invididual decompressed sample,
* but SF2 samples are relative to sample chunk start, so they need to be adjusted */
if (!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS))
{
sample->loopstart = sample->source_loopstart - sample->source_start;
sample->loopend = sample->source_loopend - sample->source_start;
}
/* As we've just loaded an individual sample into it's own buffer, we need to adjust the start
* and end pointers */
sample->start = 0;
sample->end = num_samples - 1;
return FLUID_OK;
}
/* Loads the sample data for all samples from the Soundfont file. For SF2 files, it loads the data in
* one large block. For SF3 files, each compressed sample gets loaded individually.
* Returns FLUID_OK on success, otherwise FLUID_FAILED
*/
int fluid_defsfont_load_all_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata)
{
fluid_list_t *list;
fluid_sample_t *sample;
int sf3_file = (sfdata->version.major == 3);
/* For SF2 files, we load the sample data in one large block */
if (!sf3_file)
{
int read_samples;
int num_samples = sfdata->samplesize / sizeof(short);
read_samples = fluid_samplecache_load(sfdata, 0, num_samples - 1, 0, defsfont->mlock,
&defsfont->sampledata, &defsfont->sample24data);
if (read_samples != num_samples)
{
FLUID_LOG(FLUID_ERR, "Attempted to read %d words of sample data, but got %d instead",
num_samples, read_samples);
return FLUID_FAILED;
}
}
for (list = defsfont->sample; list; list = fluid_list_next(list))
{
sample = fluid_list_get(list);
if (sf3_file)
{
/* SF3 samples get loaded individually, as most (or all) of them are in Ogg Vorbis format
* anyway */
if (fluid_defsfont_load_sampledata(defsfont, sfdata, sample) == FLUID_FAILED)
{
FLUID_LOG(FLUID_ERR, "Failed to load sample '%s'", sample->name);
return FLUID_FAILED;
}
fluid_sample_sanitize_loop(sample, (sample->end + 1) * sizeof(short));
}
else
{
/* Data pointers of SF2 samples point to large sample data block loaded above */
sample->data = defsfont->sampledata;
sample->data24 = defsfont->sample24data;
fluid_sample_sanitize_loop(sample, defsfont->samplesize);
}
fluid_voice_optimize_sample(sample);
}
return FLUID_OK;
}
/* /*
* fluid_defsfont_load * fluid_defsfont_load
@ -271,13 +388,20 @@ int fluid_defsfont_load(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t
return FLUID_FAILED; return FLUID_FAILED;
} }
defsfont->fcbs = fcbs;
/* The actual loading is done in the sfont and sffile files */ /* The actual loading is done in the sfont and sffile files */
sfdata = fluid_sffile_load(file, fcbs); sfdata = fluid_sffile_open(file, fcbs);
if (sfdata == NULL) { if (sfdata == NULL) {
FLUID_LOG(FLUID_ERR, "Couldn't load soundfont file"); FLUID_LOG(FLUID_ERR, "Couldn't load soundfont file");
return FLUID_FAILED; return FLUID_FAILED;
} }
if (fluid_sffile_parse_presets(sfdata) == FLUID_FAILED) {
FLUID_LOG(FLUID_ERR, "Couldn't parse presets from soundfont file");
goto err_exit;
}
/* Keep track of the position and size of the sample data because /* Keep track of the position and size of the sample data because
it's loaded separately (and might be unoaded/reloaded in future) */ it's loaded separately (and might be unoaded/reloaded in future) */
defsfont->samplepos = sfdata->samplepos; defsfont->samplepos = sfdata->samplepos;
@ -285,14 +409,7 @@ int fluid_defsfont_load(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t
defsfont->sample24pos = sfdata->sample24pos; defsfont->sample24pos = sfdata->sample24pos;
defsfont->sample24size = sfdata->sample24size; defsfont->sample24size = sfdata->sample24size;
/* load sample data in one block */ /* Create all samples from sample headers */
if (fluid_samplecache_load(sfdata, 0, sfdata->samplesize / 2, defsfont->mlock,
&defsfont->sampledata, &defsfont->sample24data) == FLUID_FAILED)
{
goto err_exit;
}
/* Create all the sample headers */
p = sfdata->sample; p = sfdata->sample;
while (p != NULL) { while (p != NULL) {
sfsample = (SFSample *)fluid_list_get(p); sfsample = (SFSample *)fluid_list_get(p);
@ -303,7 +420,6 @@ int fluid_defsfont_load(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t
if (fluid_sample_import_sfont(sample, sfsample, defsfont) == FLUID_OK) if (fluid_sample_import_sfont(sample, sfsample, defsfont) == FLUID_OK)
{ {
fluid_defsfont_add_sample(defsfont, sample); fluid_defsfont_add_sample(defsfont, sample);
fluid_voice_optimize_sample(sample);
} }
else else
{ {
@ -313,9 +429,20 @@ int fluid_defsfont_load(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t
/* Store reference to FluidSynth sample in SFSample for later IZone fixups */ /* Store reference to FluidSynth sample in SFSample for later IZone fixups */
sfsample->fluid_sample = sample; sfsample->fluid_sample = sample;
p = fluid_list_next(p); p = fluid_list_next(p);
} }
/* If dynamic sample loading is disabled, load all samples in the Soundfont */
if (!defsfont->dynamic_samples)
{
if (fluid_defsfont_load_all_sampledata(defsfont, sfdata) == FLUID_FAILED)
{
FLUID_LOG(FLUID_ERR, "Unable to load all sample data");
goto err_exit;
}
}
/* Load all the presets */ /* Load all the presets */
p = sfdata->preset; p = sfdata->preset;
while (p != NULL) { while (p != NULL) {
@ -367,6 +494,11 @@ int fluid_defsfont_add_preset(fluid_defsfont_t* defsfont, fluid_defpreset_t* def
fluid_defpreset_preset_noteon, fluid_defpreset_preset_noteon,
fluid_defpreset_preset_delete); fluid_defpreset_preset_delete);
if (defsfont->dynamic_samples)
{
preset->notify = dynamic_samples_preset_notify;
}
if (preset == NULL) { if (preset == NULL) {
return FLUID_FAILED; return FLUID_FAILED;
} }
@ -378,29 +510,6 @@ int fluid_defsfont_add_preset(fluid_defsfont_t* defsfont, fluid_defpreset_t* def
return FLUID_OK; return FLUID_OK;
} }
/*
* fluid_defsfont_load_sampledata
*/
int
fluid_defsfont_load_sampledata(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t* fcbs)
{
SFData *sfdata;
int ret;
sfdata = fluid_sffile_load(defsfont->filename, fcbs);
if (sfdata == NULL) {
FLUID_LOG(FLUID_ERR, "Couldn't load soundfont file");
return FLUID_FAILED;
}
/* load sample data in one block */
ret = fluid_samplecache_load(sfdata, 0, sfdata->samplesize / 2, defsfont->mlock,
&defsfont->sampledata, &defsfont->sample24data);
fluid_sffile_close (sfdata);
return ret;
}
/* /*
* fluid_defsfont_get_preset * fluid_defsfont_get_preset
*/ */
@ -1518,6 +1627,7 @@ fluid_sample_in_rom(fluid_sample_t* sample)
return (sample->sampletype & FLUID_SAMPLETYPE_ROM); return (sample->sampletype & FLUID_SAMPLETYPE_ROM);
} }
/* /*
* fluid_sample_import_sfont * fluid_sample_import_sfont
*/ */
@ -1525,29 +1635,197 @@ int
fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* defsfont) fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* defsfont)
{ {
FLUID_STRCPY(sample->name, sfsample->name); FLUID_STRCPY(sample->name, sfsample->name);
sample->data = defsfont->sampledata;
sample->data24 = defsfont->sample24data; sample->source_start = sfsample->start;
sample->start = sfsample->start; sample->source_end = (sfsample->end > 0) ? sfsample->end - 1 : 0; /* marks last sample, contrary to SF spec. */
sample->end = (sfsample->end > 0) ? sfsample->end - 1 : 0; /* marks last sample, contrary to SF spec. */ sample->source_loopstart = sfsample->loopstart;
sample->loopstart = sfsample->loopstart; sample->source_loopend = sfsample->loopend;
sample->loopend = sfsample->loopend;
sample->start = sample->source_start;
sample->end = sample->source_end;
sample->loopstart = sample->source_loopstart;
sample->loopend = sample->source_loopend;
sample->samplerate = sfsample->samplerate; sample->samplerate = sfsample->samplerate;
sample->origpitch = sfsample->origpitch; sample->origpitch = sfsample->origpitch;
sample->pitchadj = sfsample->pitchadj; sample->pitchadj = sfsample->pitchadj;
sample->sampletype = sfsample->sampletype; sample->sampletype = sfsample->sampletype;
if (defsfont->dynamic_samples)
{
sample->notify = dynamic_samples_sample_notify;
}
if (fluid_sample_validate(sample, defsfont->samplesize) == FLUID_FAILED) if (fluid_sample_validate(sample, defsfont->samplesize) == FLUID_FAILED)
{ {
return FLUID_FAILED; return FLUID_FAILED;
} }
if ((sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS) return FLUID_OK;
&& fluid_sample_decompress_vorbis(sample) == FLUID_FAILED) }
{
return FLUID_FAILED;
}
fluid_sample_sanitize_loop(sample, defsfont->samplesize); /* Called if a sample is no longer used by a voice. Used by dynamic sample loading
* to unload a sample that is not used by any loaded presets anymore but couldn't
* be unloaded straight away because it was still in use by a voice. */
static int dynamic_samples_sample_notify(fluid_sample_t *sample, int reason)
{
if (reason == FLUID_SAMPLE_DONE && sample->preset_count == 0)
{
unload_sample(sample);
}
return FLUID_OK; return FLUID_OK;
} }
/* Called if a preset has been selected for or unselected from a channel. Used by
* dynamic sample loading to load and unload samples on demand. */
static int dynamic_samples_preset_notify(fluid_preset_t *preset, int reason, int chan)
{
fluid_defsfont_t *defsfont;
if (reason == FLUID_PRESET_SELECTED)
{
FLUID_LOG(FLUID_DBG, "Selected preset '%s' on channel %d", fluid_preset_get_name(preset), chan);
defsfont = fluid_sfont_get_data(preset->sfont);
load_preset_samples(defsfont, preset);
}
else if (reason == FLUID_PRESET_UNSELECTED)
{
FLUID_LOG(FLUID_DBG, "Deselected preset '%s' from channel %d", fluid_preset_get_name(preset), chan);
defsfont = fluid_sfont_get_data(preset->sfont);
unload_preset_samples(defsfont, preset);
}
return FLUID_OK;
}
/* Walk through all samples used by the passed in preset and make sure that the
* sample data is loaded for each sample. Used by dynamic sample loading. */
static int load_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset)
{
fluid_defpreset_t *defpreset;
fluid_preset_zone_t *preset_zone;
fluid_inst_t *inst;
fluid_inst_zone_t *inst_zone;
fluid_sample_t *sample;
SFData *sffile = NULL;
defpreset = fluid_preset_get_data(preset);
preset_zone = fluid_defpreset_get_zone(defpreset);
while (preset_zone != NULL)
{
inst = fluid_preset_zone_get_inst(preset_zone);
inst_zone = fluid_inst_get_zone(inst);
while (inst_zone != NULL) {
sample = fluid_inst_zone_get_sample(inst_zone);
if ((sample != NULL) && (sample->start != sample->end))
{
sample->preset_count++;
/* If this is the first time this sample has been selected,
* load the sampledata */
if (sample->preset_count == 1)
{
/* Make sure we have an open Soundfont file. Do this here
* to avoid having to open the file if no loading is necessary
* for a preset */
if (sffile == NULL)
{
sffile = fluid_sffile_open(defsfont->filename, defsfont->fcbs);
if (sffile == NULL)
{
FLUID_LOG(FLUID_ERR, "Unable to open Soundfont file");
return FLUID_FAILED;
}
}
if (fluid_defsfont_load_sampledata(defsfont, sffile, sample) == FLUID_OK)
{
fluid_sample_sanitize_loop(sample, (sample->end + 1) * sizeof(short));
fluid_voice_optimize_sample(sample);
}
else
{
FLUID_LOG(FLUID_ERR, "Unable to load sample '%s', disabling", sample->name);
sample->start = sample->end = 0;
}
}
}
inst_zone = fluid_inst_zone_next(inst_zone);
}
preset_zone = fluid_preset_zone_next(preset_zone);
}
if (sffile != NULL)
{
fluid_sffile_close(sffile);
}
return FLUID_OK;
}
/* Walk through all samples used by the passed in preset and unload the sample data
* of each sample that is not used by any selected preset anymore. Used by dynamic
* sample loading. */
static int unload_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset)
{
fluid_defpreset_t *defpreset;
fluid_preset_zone_t *preset_zone;
fluid_inst_t *inst;
fluid_inst_zone_t *inst_zone;
fluid_sample_t *sample;
defpreset = fluid_preset_get_data(preset);
preset_zone = fluid_defpreset_get_zone(defpreset);
while (preset_zone != NULL)
{
inst = fluid_preset_zone_get_inst(preset_zone);
inst_zone = fluid_inst_get_zone(inst);
while (inst_zone != NULL) {
sample = fluid_inst_zone_get_sample(inst_zone);
if ((sample != NULL) && (sample->preset_count > 0))
{
sample->preset_count--;
/* If the sample is not used by any preset or used by a
* sounding voice, unload it from the sample cache. If it's
* still in use by a voice, dynamic_samples_sample_notify will
* take care of unloading the sample as soon as the voice is
* finished with it (but only on the next API call). */
if (sample->preset_count == 0 && sample->refcount == 0)
{
unload_sample(sample);
}
}
inst_zone = fluid_inst_zone_next(inst_zone);
}
preset_zone = fluid_preset_zone_next(preset_zone);
}
return FLUID_OK;
}
/* Unload an unused sample from the samplecache */
static void unload_sample(fluid_sample_t *sample)
{
fluid_return_if_fail(sample != NULL);
fluid_return_if_fail(sample->data != NULL);
fluid_return_if_fail(sample->preset_count == 0);
fluid_return_if_fail(sample->refcount == 0);
FLUID_LOG(FLUID_DBG, "Unloading sample '%s'", sample->name);
if (fluid_samplecache_unload(sample->data) == FLUID_FAILED)
{
FLUID_LOG(FLUID_ERR, "Unable to unload sample '%s'", sample->name);
}
else
{
sample->data = NULL;
sample->data24 = NULL;
}
}

View file

@ -94,6 +94,7 @@ int fluid_zone_inside_range(fluid_zone_range_t* zone_range, int key, int vel);
*/ */
struct _fluid_defsfont_t struct _fluid_defsfont_t
{ {
const fluid_file_callbacks_t* fcbs; /* the file callbacks used to load this Soundfont */
char* filename; /* the filename of this soundfont */ char* filename; /* the filename of this soundfont */
unsigned int samplepos; /* the position in the file at which the sample data starts */ unsigned int samplepos; /* the position in the file at which the sample data starts */
unsigned int samplesize; /* the size of the sample data in bytes */ unsigned int samplesize; /* the size of the sample data in bytes */
@ -107,6 +108,7 @@ struct _fluid_defsfont_t
fluid_list_t* sample; /* the samples in this soundfont */ fluid_list_t* sample; /* the samples in this soundfont */
fluid_list_t* preset; /* the presets of this soundfont */ fluid_list_t* preset; /* the presets of this soundfont */
int mlock; /* Should we try memlock (avoid swapping)? */ int mlock; /* Should we try memlock (avoid swapping)? */
int dynamic_samples; /* Enables dynamic sample loading if set */
fluid_list_t *preset_iter_cur; /* the current preset in the iteration */ fluid_list_t *preset_iter_cur; /* the current preset in the iteration */
}; };
@ -119,7 +121,9 @@ const char* fluid_defsfont_get_name(fluid_defsfont_t* defsfont);
fluid_preset_t* fluid_defsfont_get_preset(fluid_defsfont_t* defsfont, unsigned int bank, unsigned int prenum); fluid_preset_t* fluid_defsfont_get_preset(fluid_defsfont_t* defsfont, unsigned int bank, unsigned int prenum);
void fluid_defsfont_iteration_start(fluid_defsfont_t* defsfont); void fluid_defsfont_iteration_start(fluid_defsfont_t* defsfont);
fluid_preset_t *fluid_defsfont_iteration_next(fluid_defsfont_t* defsfont); fluid_preset_t *fluid_defsfont_iteration_next(fluid_defsfont_t* defsfont);
int fluid_defsfont_load_sampledata(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t* file_callbacks); int fluid_defsfont_load_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata, fluid_sample_t *sample);
int fluid_defsfont_load_all_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata);
int fluid_defsfont_add_sample(fluid_defsfont_t* defsfont, fluid_sample_t* sample); int fluid_defsfont_add_sample(fluid_defsfont_t* defsfont, fluid_sample_t* sample);
int fluid_defsfont_add_preset(fluid_defsfont_t* defsfont, fluid_defpreset_t* defpreset); int fluid_defsfont_add_preset(fluid_defsfont_t* defsfont, fluid_defpreset_t* defpreset);
@ -211,7 +215,6 @@ int fluid_inst_zone_import_sfont(fluid_preset_zone_t* preset_zone,
fluid_sample_t* fluid_inst_zone_get_sample(fluid_inst_zone_t* zone); fluid_sample_t* fluid_inst_zone_get_sample(fluid_inst_zone_t* zone);
int fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* defsfont); int fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* defsfont);
int fluid_sample_in_rom(fluid_sample_t* sample); int fluid_sample_in_rom(fluid_sample_t* sample);

View file

@ -45,11 +45,13 @@ struct _fluid_samplecache_entry_t
unsigned int sf_sample24pos; unsigned int sf_sample24pos;
unsigned int sf_sample24size; unsigned int sf_sample24size;
unsigned int sample_start; unsigned int sample_start;
unsigned int sample_count; unsigned int sample_end;
int sample_type;
/* End of cache key members */ /* End of cache key members */
short *sample_data; short *sample_data;
char *sample_data24; char *sample_data24;
int sample_count;
int num_references; int num_references;
int mlocked; int mlocked;
@ -58,8 +60,8 @@ struct _fluid_samplecache_entry_t
static fluid_list_t *samplecache_list = NULL; static fluid_list_t *samplecache_list = NULL;
static fluid_mutex_t samplecache_mutex = FLUID_MUTEX_INIT; static fluid_mutex_t samplecache_mutex = FLUID_MUTEX_INIT;
static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_count); static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type);
static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_count); static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type);
static void delete_samplecache_entry(fluid_samplecache_entry_t *entry); static void delete_samplecache_entry(fluid_samplecache_entry_t *entry);
static int fluid_get_file_modification_time(char *filename, time_t *modification_time); static int fluid_get_file_modification_time(char *filename, time_t *modification_time);
@ -68,7 +70,7 @@ static int fluid_get_file_modification_time(char *filename, time_t *modification
/* PUBLIC INTERFACE */ /* PUBLIC INTERFACE */
int fluid_samplecache_load(SFData *sf, int fluid_samplecache_load(SFData *sf,
unsigned int sample_start, unsigned int sample_count, unsigned int sample_start, unsigned int sample_end, int sample_type,
int try_mlock, short **sample_data, char **sample_data24) int try_mlock, short **sample_data, char **sample_data24)
{ {
fluid_samplecache_entry_t *entry; fluid_samplecache_entry_t *entry;
@ -76,13 +78,13 @@ int fluid_samplecache_load(SFData *sf,
fluid_mutex_lock(samplecache_mutex); fluid_mutex_lock(samplecache_mutex);
entry = get_samplecache_entry(sf, sample_start, sample_count); entry = get_samplecache_entry(sf, sample_start, sample_end, sample_type);
if (entry == NULL) if (entry == NULL)
{ {
entry = new_samplecache_entry(sf, sample_start, sample_count); entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type);
if (entry == NULL) if (entry == NULL)
{ {
ret = FLUID_FAILED; ret = -1;
goto unlock_exit; goto unlock_exit;
} }
@ -93,7 +95,7 @@ int fluid_samplecache_load(SFData *sf,
{ {
/* Lock the memory to disable paging. It's okay if this fails. It /* Lock the memory to disable paging. It's okay if this fails. It
* probably means that the user doesn't have the required permission. */ * probably means that the user doesn't have the required permission. */
if (fluid_mlock(entry->sample_data, entry->sample_count * 2) == 0) if (fluid_mlock(entry->sample_data, entry->sample_count * sizeof(short)) == 0)
{ {
if (entry->sample_data24 != NULL) if (entry->sample_data24 != NULL)
{ {
@ -106,7 +108,7 @@ int fluid_samplecache_load(SFData *sf,
if (!entry->mlocked) if (!entry->mlocked)
{ {
fluid_munlock(entry->sample_data, entry->sample_count * 2); fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short));
FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible."); FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible.");
} }
} }
@ -115,7 +117,7 @@ int fluid_samplecache_load(SFData *sf,
entry->num_references++; entry->num_references++;
*sample_data = entry->sample_data; *sample_data = entry->sample_data;
*sample_data24 = entry->sample_data24; *sample_data24 = entry->sample_data24;
ret = FLUID_OK; ret = entry->sample_count;
unlock_exit: unlock_exit:
fluid_mutex_unlock(samplecache_mutex); fluid_mutex_unlock(samplecache_mutex);
@ -143,7 +145,7 @@ int fluid_samplecache_unload(const short *sample_data)
{ {
if (entry->mlocked) if (entry->mlocked)
{ {
fluid_munlock(entry->sample_data, entry->sample_count * 2); fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short));
if (entry->sample_data24 != NULL) if (entry->sample_data24 != NULL)
{ {
fluid_munlock(entry->sample_data24, entry->sample_count); fluid_munlock(entry->sample_data24, entry->sample_count);
@ -173,7 +175,8 @@ unlock_exit:
/* Private functions */ /* Private functions */
static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf,
unsigned int sample_start, unsigned int sample_start,
unsigned int sample_count) unsigned int sample_end,
int sample_type)
{ {
fluid_samplecache_entry_t *entry; fluid_samplecache_entry_t *entry;
@ -203,10 +206,12 @@ static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf,
entry->sf_sample24pos = sf->sample24pos; entry->sf_sample24pos = sf->sample24pos;
entry->sf_sample24size = sf->sample24size; entry->sf_sample24size = sf->sample24size;
entry->sample_start = sample_start; entry->sample_start = sample_start;
entry->sample_count = sample_count; entry->sample_end = sample_end;
entry->sample_type = sample_type;
if (fluid_sffile_read_sample_data(sf, sample_start, sample_count, entry->sample_count = fluid_sffile_read_sample_data(sf, sample_start, sample_end, sample_type,
&entry->sample_data, &entry->sample_data24) == FLUID_FAILED) &entry->sample_data, &entry->sample_data24);
if (entry->sample_count < 0)
{ {
goto error_exit; goto error_exit;
} }
@ -230,7 +235,8 @@ static void delete_samplecache_entry(fluid_samplecache_entry_t *entry)
static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf,
unsigned int sample_start, unsigned int sample_start,
unsigned int sample_count) unsigned int sample_end,
int sample_type)
{ {
time_t mtime; time_t mtime;
fluid_list_t *entry_list; fluid_list_t *entry_list;
@ -254,7 +260,8 @@ static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf,
(sf->sample24pos == entry->sf_sample24pos) && (sf->sample24pos == entry->sf_sample24pos) &&
(sf->sample24size == entry->sf_sample24size) && (sf->sample24size == entry->sf_sample24size) &&
(sample_start == entry->sample_start) && (sample_start == entry->sample_start) &&
(sample_count == entry->sample_count)) (sample_end == entry->sample_end) &&
(sample_type == entry->sample_type))
{ {
return entry; return entry;
} }

View file

@ -26,7 +26,7 @@
#include "fluid_sffile.h" #include "fluid_sffile.h"
int fluid_samplecache_load(SFData *sf, int fluid_samplecache_load(SFData *sf,
unsigned int sample_start, unsigned int sample_count, unsigned int sample_start, unsigned int sample_end, int sample_type,
int try_mlock, short **data, char **data24); int try_mlock, short **data, char **data24);
int fluid_samplecache_unload(const short *sample_data); int fluid_samplecache_unload(const short *sample_data);

View file

@ -26,6 +26,10 @@
#include "fluid_sfont.h" #include "fluid_sfont.h"
#include "fluid_sys.h" #include "fluid_sys.h"
#if LIBSNDFILE_SUPPORT
#include <sndfile.h>
#endif
/*=================================sfload.c======================== /*=================================sfload.c========================
Borrowed from Smurf SoundFont Editor by Josh Green Borrowed from Smurf SoundFont Editor by Josh Green
=================================================================*/ =================================================================*/
@ -259,7 +263,8 @@ static const unsigned short invalid_preset_gen[] = {
} while (0) } while (0)
static int load_body(SFData *sf, unsigned int size); static int load_header(SFData *sf);
static int load_body(SFData *sf);
static int process_info(SFData *sf, int size); static int process_info(SFData *sf, int size);
static int process_sdta(SFData *sf, unsigned int size); static int process_sdta(SFData *sf, unsigned int size);
static int process_pdta(SFData *sf, int size); static int process_pdta(SFData *sf, int size);
@ -288,15 +293,17 @@ static void delete_preset(SFPreset *preset);
static void delete_inst(SFInst *inst); static void delete_inst(SFInst *inst);
static void delete_zone(SFZone *zone); 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);
/* /*
* Open a SoundFont file and parse it's contents into a SFData structure. * Open a SoundFont file and parse it's contents into a SFData structure.
* *
* @param fname filename * @param fname filename
* @param fcbs file callback structure * @param fcbs file callback structure
* @return the parsed SoundFont as SFData structure or NULL on error * @return the partially parsed SoundFont as SFData structure or NULL on error
*/ */
SFData *fluid_sffile_load(const char *fname, const fluid_file_callbacks_t *fcbs) SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs)
{ {
SFData *sf; SFData *sf;
int fsize = 0; int fsize = 0;
@ -334,6 +341,7 @@ SFData *fluid_sffile_load(const char *fname, const fluid_file_callbacks_t *fcbs)
FLUID_LOG(FLUID_ERR, "Get end of file position failed"); FLUID_LOG(FLUID_ERR, "Get end of file position failed");
goto error_exit; goto error_exit;
} }
sf->filesize = fsize;
if (fcbs->fseek(sf->sffd, 0, SEEK_SET) == FLUID_FAILED) if (fcbs->fseek(sf->sffd, 0, SEEK_SET) == FLUID_FAILED)
{ {
@ -341,7 +349,7 @@ SFData *fluid_sffile_load(const char *fname, const fluid_file_callbacks_t *fcbs)
goto error_exit; goto error_exit;
} }
if (!load_body(sf, fsize)) if (!load_header(sf))
{ {
goto error_exit; goto error_exit;
} }
@ -353,110 +361,51 @@ error_exit:
return NULL; return NULL;
} }
/* Load sample data from the soundfont file /*
* * Parse all preset information from the soundfont
* @param sf SFFile instance
* @param start start offset of sample data (in sample words from start of sample data chunk)
* @param count number of samples to read (in sample words)
* @param data pointer to sample data pointer, set on success
* @param data24 pointer to 24-bit sample data pointer if 24-bit data present, set on success
* *
* @return FLUID_OK on success, otherwise FLUID_FAILED * @return FLUID_OK on success, otherwise FLUID_FAILED
*/ */
int fluid_sffile_read_sample_data(SFData *sf, unsigned int start, unsigned int count, int fluid_sffile_parse_presets(SFData *sf)
short **data, char **data24)
{ {
unsigned int end; if (!load_body(sf))
short *loaded_data = NULL;
char *loaded_data24 = NULL;
fluid_return_val_if_fail(count != 0, FLUID_FAILED);
end = start + count;
if (((start * 2) > sf->samplesize) || ((end * 2) > 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 * 2), SEEK_SET) == FLUID_FAILED)
{
FLUID_LOG(FLUID_ERR, "Failed to seek position in data file");
goto error_exit;
}
loaded_data = FLUID_ARRAY(short, count);
if (loaded_data == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
goto error_exit;
}
if (sf->fcbs->fread(loaded_data, count * 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)
{
unsigned int i;
for (i = 0; i < count; 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, count);
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, count, sf->sffd) == FLUID_FAILED)
{
FLUID_LOG(FLUID_ERR, "Failed to read 24-bit sample data");
goto error24_exit;
}
}
*data24 = loaded_data24;
return FLUID_OK;
error24_exit:
FLUID_LOG(FLUID_WARN, "Ignoring 24-bit sample data, sound quality might suffer");
FLUID_FREE(loaded_data24);
*data24 = NULL;
return FLUID_OK;
error_exit:
FLUID_FREE(loaded_data);
FLUID_FREE(loaded_data24);
return FLUID_FAILED; 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;
} }
/* /*
@ -534,7 +483,7 @@ static int chunkid(unsigned int id)
return UNKN_ID; return UNKN_ID;
} }
static int load_body(SFData *sf, unsigned int size) static int load_header(SFData *sf)
{ {
SFChunk chunk; SFChunk chunk;
@ -552,7 +501,7 @@ static int load_body(SFData *sf, unsigned int size)
return FALSE; return FALSE;
} }
if (chunk.size != size - 8) if (chunk.size != sf->filesize - 8)
{ {
FLUID_LOG(FLUID_ERR, "SoundFont file size mismatch"); FLUID_LOG(FLUID_ERR, "SoundFont file size mismatch");
return FALSE; return FALSE;
@ -588,11 +537,27 @@ static int load_body(SFData *sf, unsigned int size)
FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting HYDRA chunk"); FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting HYDRA chunk");
return FALSE; return FALSE;
} }
if (!process_pdta(sf, chunk.size))
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; return FALSE;
if (!fixup_pgen(sf)) if (!fixup_pgen(sf))
return FALSE; return FALSE;
if (!fixup_igen(sf)) if (!fixup_igen(sf))
return FALSE; return FALSE;
@ -1900,3 +1865,275 @@ static int valid_preset_genid(unsigned short genid)
i++; i++;
return (invalid_preset_gen[i] == 0); 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;
}
if (sf->fcbs->fseek(sf->sffd, sf->samplepos + data->start + new_offset, SEEK_SET) != FLUID_FAILED) {
data->offset = new_offset;
}
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;
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, sf_strerror(sndfile));
return -1;
}
// Empty sample
if (!sfinfo.frames || !sfinfo.channels)
{
FLUID_LOG(FLUID_DBG, "Empty decompressed sample");
*data = NULL;
sf_close(sndfile);
return 0;
}
/* FIXME: ensure that the decompressed WAV data is 16-bit mono? */
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 WAV */
if (sf_readf_short(sndfile, wav_data, sfinfo.frames) < sfinfo.frames)
{
FLUID_LOG(FLUID_DBG, "Decompression failed!");
FLUID_LOG(FLUID_ERR, 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

View file

@ -134,6 +134,8 @@ struct _SFData
SFVersion version; /* sound font version */ SFVersion version; /* sound font version */
SFVersion romver; /* ROM version */ SFVersion romver; /* ROM version */
unsigned int filesize;
unsigned int samplepos; /* position within sffd of the sample chunk */ unsigned int samplepos; /* position within sffd of the sample chunk */
unsigned int samplesize; /* length within sffd of the sample chunk */ unsigned int samplesize; /* length within sffd of the sample chunk */
@ -141,6 +143,9 @@ struct _SFData
sample support */ sample support */
unsigned int sample24size; /* length within sffd of the sm24 chunk */ unsigned int sample24size; /* length within sffd of the sm24 chunk */
unsigned int hydrapos;
unsigned int hydrasize;
char *fname; /* file name */ char *fname; /* file name */
FILE *sffd; /* loaded sfont file descriptor */ FILE *sffd; /* loaded sfont file descriptor */
const fluid_file_callbacks_t *fcbs; /* file callbacks used to read this file */ const fluid_file_callbacks_t *fcbs; /* file callbacks used to read this file */
@ -204,9 +209,10 @@ struct _SFShdr
}; };
/* Public functions */ /* Public functions */
SFData *fluid_sffile_load(const char *fname, const fluid_file_callbacks_t *fcbs); SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs);
void fluid_sffile_close(SFData *sf); void fluid_sffile_close(SFData *sf);
int fluid_sffile_read_sample_data(SFData *sf, unsigned int start, unsigned int count, int fluid_sffile_parse_presets(SFData *sf);
short **data, char **data24); int fluid_sffile_read_sample_data(SFData *sf, unsigned int sample_start, unsigned int sample_end,
int sample_type, short **data, char **data24);
#endif /* _FLUID_SFFILE_H */ #endif /* _FLUID_SFFILE_H */

View file

@ -21,11 +21,6 @@
#include "fluid_sfont.h" #include "fluid_sfont.h"
#include "fluid_sys.h" #include "fluid_sys.h"
#if LIBSNDFILE_SUPPORT
#include <sndfile.h>
#endif
void * default_fopen(const char * path) void * default_fopen(const char * path)
{ {
@ -607,16 +602,6 @@ int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int buffer_size)
* to the data word after the last sample. FIXME: why? */ * to the data word after the last sample. FIXME: why? */
unsigned int sample_end = sample->end + 1; unsigned int sample_end = sample->end + 1;
/* Checking loops on compressed samples makes no sense at all and is really
* a programming error. Disable the loop to be on the safe side. */
if (sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)
{
FLUID_LOG(FLUID_ERR, "Sample '%s': checking loop on compressed sample, disabling loop",
sample->name);
sample->loopstart = sample->loopend = 0;
return TRUE;
}
if (sample->loopstart == sample->loopend) if (sample->loopstart == sample->loopend)
{ {
/* Some SoundFonts disable loops by setting loopstart = loopend. While /* Some SoundFonts disable loops by setting loopstart = loopend. While
@ -672,128 +657,3 @@ int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int buffer_size)
return modified; return modified;
} }
#if LIBSNDFILE_SUPPORT
// virtual file access rountines to allow for handling
// samples as virtual files in memory
static sf_count_t
sfvio_get_filelen(void* user_data)
{
fluid_sample_t *sample = (fluid_sample_t *)user_data;
return (sf_count_t)(sample->end + 1 - sample->start);
}
static sf_count_t
sfvio_seek(sf_count_t offset, int whence, void* user_data)
{
fluid_sample_t *sample = (fluid_sample_t *)user_data;
switch (whence)
{
case SEEK_SET:
sample->userdata = (void *)offset;
break;
case SEEK_CUR:
sample->userdata = (void *)((sf_count_t)sample->userdata + offset);
break;
case SEEK_END:
sample->userdata = (void *)(sfvio_get_filelen(user_data) + offset);
break;
}
return (sf_count_t)sample->userdata;
}
static sf_count_t
sfvio_read(void* ptr, sf_count_t count, void* user_data)
{
fluid_sample_t *sample = (fluid_sample_t *)user_data;
sf_count_t remain = sfvio_get_filelen(user_data) - (sf_count_t)sample->userdata;
if (count > remain)
count = remain;
memcpy(ptr, (char *)sample->data + sample->start + (sf_count_t)sample->userdata, count);
sample->userdata = (void *)((sf_count_t)sample->userdata + count);
return count;
}
static sf_count_t
sfvio_tell (void* user_data)
{
fluid_sample_t *sample = (fluid_sample_t *)user_data;
return (sf_count_t)sample->userdata;
}
int fluid_sample_decompress_vorbis(fluid_sample_t *sample)
{
SNDFILE *sndfile;
SF_INFO sfinfo;
SF_VIRTUAL_IO sfvio = {
sfvio_get_filelen,
sfvio_seek,
sfvio_read,
NULL,
sfvio_tell
};
short *sampledata_ogg;
// initialize file position indicator and SF_INFO structure
g_assert(sample->userdata == NULL);
memset(&sfinfo, 0, sizeof(sfinfo));
// open sample as a virtual file in memory
sndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, sample);
if (!sndfile)
{
FLUID_LOG(FLUID_ERR, sf_strerror(sndfile));
return FLUID_FAILED;
}
// empty sample
if (!sfinfo.frames || !sfinfo.channels)
{
sample->start = sample->end = 0;
sample->loopstart = sample->loopend = 0;
sample->data = NULL;
sf_close(sndfile);
return FLUID_OK;
}
// allocate memory for uncompressed sample data stream
sampledata_ogg = (short *)FLUID_MALLOC(sfinfo.frames * sfinfo.channels * sizeof(short));
if (!sampledata_ogg)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
sf_close(sndfile);
return FLUID_FAILED;
}
// uncompress sample data stream
if (sf_readf_short(sndfile, sampledata_ogg, sfinfo.frames) < sfinfo.frames)
{
FLUID_FREE(sampledata_ogg);
FLUID_LOG(FLUID_ERR, sf_strerror(sndfile));
sf_close(sndfile);
return FLUID_FAILED;
}
sf_close(sndfile);
// point sample data to uncompressed data stream
sample->data = sampledata_ogg;
sample->auto_free = TRUE;
sample->start = 0;
sample->end = sfinfo.frames - 1;
return FLUID_OK;
}
#else
int fluid_sample_decompress_vorbis(fluid_sample_t *sample)
{
return FLUID_FAILED;
}
#endif

View file

@ -26,7 +26,6 @@
int fluid_sample_validate(fluid_sample_t *sample, unsigned int max_end); int fluid_sample_validate(fluid_sample_t *sample, unsigned int max_end);
int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int max_end); int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int max_end);
int fluid_sample_decompress_vorbis(fluid_sample_t *sample);
/* /*
* Utility macros to access soundfonts, presets, and samples * Utility macros to access soundfonts, presets, and samples
@ -177,10 +176,22 @@ struct _fluid_preset_t {
struct _fluid_sample_t struct _fluid_sample_t
{ {
char name[21]; /**< Sample name */ char name[21]; /**< Sample name */
/* The following for sample pointers store the original pointers from the Soundfont
* file. They are never changed after loading and are used to re-create the
* actual sample pointers after a sample has been unloaded and loaded again. The
* actual sample pointers get modified during loading for SF3 (compressed) samples
* and individually loaded SF2 samples. */
unsigned int source_start;
unsigned int source_end;
unsigned int source_loopstart;
unsigned int source_loopend;
unsigned int start; /**< Start index */ unsigned int start; /**< Start index */
unsigned int end; /**< End index, index of last valid sample point (contrary to SF spec) */ unsigned int end; /**< End index, index of last valid sample point (contrary to SF spec) */
unsigned int loopstart; /**< Loop start index */ unsigned int loopstart; /**< Loop start index */
unsigned int loopend; /**< Loop end index, first point following the loop (superimposed on loopstart) */ unsigned int loopend; /**< Loop end index, first point following the loop (superimposed on loopstart) */
unsigned int samplerate; /**< Sample rate */ unsigned int samplerate; /**< Sample rate */
int origpitch; /**< Original pitch (MIDI note number, 0-127) */ int origpitch; /**< Original pitch (MIDI note number, 0-127) */
int pitchadj; /**< Fine pitch adjustment (+/- 99 cents) */ int pitchadj; /**< Fine pitch adjustment (+/- 99 cents) */
@ -193,6 +204,7 @@ struct _fluid_sample_t
double amplitude_that_reaches_noise_floor; /**< The amplitude at which the sample's loop will be below the noise floor. For voice off optimization, calculated automatically. */ double amplitude_that_reaches_noise_floor; /**< The amplitude at which the sample's loop will be below the noise floor. For voice off optimization, calculated automatically. */
unsigned int refcount; /**< Count of voices using this sample */ unsigned int refcount; /**< Count of voices using this sample */
int preset_count; /**< Count of selected presets using this sample (used for dynamic sample loading) */
/** /**
* Implement this function to receive notification when sample is no longer used. * Implement this function to receive notification when sample is no longer used.

View file

@ -228,6 +228,8 @@ void fluid_synth_settings(fluid_settings_t* settings)
fluid_settings_add_option(settings, "synth.midi-bank-select", "gs"); fluid_settings_add_option(settings, "synth.midi-bank-select", "gs");
fluid_settings_add_option(settings, "synth.midi-bank-select", "xg"); fluid_settings_add_option(settings, "synth.midi-bank-select", "xg");
fluid_settings_add_option(settings, "synth.midi-bank-select", "mma"); fluid_settings_add_option(settings, "synth.midi-bank-select", "mma");
fluid_settings_register_int(settings, "synth.dynamic-sample-loading", 0, 0, 1, FLUID_HINT_TOGGLED);
} }
/** /**

View file

@ -4,18 +4,17 @@ project(fluidsynth-test)
ENABLE_TESTING() ENABLE_TESTING()
include ( FluidUnitTest ) include ( FluidUnitTest )
# first define the test target, used by the macros below
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $<CONFIG> --output-on-failure)
## add unit tests here ## ## add unit tests here ##
ADD_FLUID_TEST(test_sample_cache) ADD_FLUID_TEST(test_sample_cache)
ADD_FLUID_TEST(test_sfont_loading) ADD_FLUID_TEST(test_sfont_loading)
ADD_FLUID_TEST(test_defsfont_preset_iteration) ADD_FLUID_TEST(test_defsfont_preset_iteration)
ADD_FLUID_TEST(test_sample_rate_change) ADD_FLUID_TEST(test_sample_rate_change)
if ( LIBSNDFILE_HASVORBIS )
add_custom_target(check ADD_FLUID_TEST(test_sf3_sfont_loading)
COMMAND ${CMAKE_CTEST_COMMAND} -C $<CONFIG> --output-on-failure ADD_FLUID_TEST(test_sf3_defsfont_preset_iteration)
DEPENDS endif ( LIBSNDFILE_HASVORBIS )
test_sample_cache
test_sfont_loading
test_defsfont_preset_iteration
test_sample_rate_change
)

View file

@ -0,0 +1,61 @@
#include "test.h"
#include "fluidsynth.h"
#include "sfloader/fluid_sfont.h"
#include "sfloader/fluid_defsfont.h"
#include "utils/fluidsynth_priv.h"
#include "utils/fluid_list.h"
int main(void)
{
int id;
fluid_sfont_t *sfont;
fluid_list_t *list;
fluid_preset_t *preset;
fluid_preset_t *prev_preset = NULL;
fluid_defsfont_t *defsfont;
fluid_sample_t *sample;
fluid_sample_t *prev_sample = NULL;
int count = 0;
/* setup */
fluid_settings_t *settings = new_fluid_settings();
fluid_synth_t *synth = new_fluid_synth(settings);
id = fluid_synth_sfload(synth, TEST_SOUNDFONT_SF3, 1);
sfont = fluid_synth_get_sfont_by_id(synth, id);
defsfont = fluid_sfont_get_data(sfont);
/* Make sure we have the right number of presets */
fluid_sfont_iteration_start(sfont);
while ((preset = fluid_sfont_iteration_next(sfont)) != NULL) {
count++;
/* make sure we actually got a different preset */
TEST_ASSERT(preset != prev_preset);
prev_preset = preset;
}
/* VintageDreams has 136 presets */
TEST_ASSERT(count == 136);
/* Make sure we have the right number of samples */
count = 0;
for (list = defsfont->sample; list; list = fluid_list_next(list))
{
sample = fluid_list_get(list);
if (sample->data != NULL)
{
count++;
}
/* Make sure we actually got a different sample */
TEST_ASSERT(sample != prev_sample);
prev_sample = sample;
}
/* VintageDreams has 123 valid samples (one is a ROM sample and ignored) */
TEST_ASSERT(count == 123);
/* teardown */
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,70 @@
#include "test.h"
#include "fluidsynth.h"
#include "sfloader/fluid_sfont.h"
#include "utils/fluidsynth_priv.h"
int main(void)
{
int id;
fluid_sfont_t *sfont;
fluid_settings_t *settings = new_fluid_settings();
fluid_synth_t *synth = new_fluid_synth(settings);
TEST_ASSERT(settings != NULL);
TEST_ASSERT(synth != NULL);
// no sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 0);
FLUID_LOG(FLUID_INFO, "Attempt to open %s", TEST_SOUNDFONT_SF3);
// load a sfont to synth
TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT_SF3, 1));
// one sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
sfont = fluid_synth_get_sfont_by_id(synth, id);
TEST_ASSERT(sfont != NULL);
// this is still the same filename as we've put in
TEST_ASSERT(FLUID_STRCMP(TEST_SOUNDFONT_SF3, fluid_sfont_get_name(sfont)) == 0);
TEST_ASSERT(fluid_sfont_get_id(sfont) == id);
// still the same id?
TEST_ASSERT(fluid_synth_sfreload(synth, id) == id);
// one sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
sfont = fluid_synth_get_sfont_by_id(synth, id);
TEST_ASSERT(sfont != NULL);
// still the same filename?
TEST_ASSERT(FLUID_STRCMP(TEST_SOUNDFONT_SF3, fluid_sfont_get_name(sfont)) == 0);
// correct id stored?
TEST_ASSERT(fluid_sfont_get_id(sfont) == id);
// remove the sfont without deleting
TEST_SUCCESS(fluid_synth_remove_sfont(synth, sfont));
// no sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 0);
// re-add the sfont without deleting
TEST_SUCCESS(id = fluid_synth_add_sfont(synth, sfont));
// one sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
// destroy the sfont
TEST_SUCCESS(fluid_synth_sfunload(synth, id, 0));
// no sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 0);
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return EXIT_SUCCESS;
}