diff --git a/CMakeLists.txt b/CMakeLists.txt index 51d40f56..8cd4ac49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -504,6 +504,7 @@ endif ( enable-readline ) if(enable-tests) # manipulate some variables to setup a proper test env 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 # to test fluidsynths private/internal functions diff --git a/cmake_admin/FluidUnitTest.cmake b/cmake_admin/FluidUnitTest.cmake index 3191709f..dcced0fd 100644 --- a/cmake_admin/FluidUnitTest.cmake +++ b/cmake_admin/FluidUnitTest.cmake @@ -11,5 +11,10 @@ macro ( ADD_FLUID_TEST _test ) $ # include all other header search paths needed by libfluidsynth (esp. glib) ) + # add the test to ctest ADD_TEST(NAME ${_test} COMMAND ${_test}) + + # append the current unit test to check-target as dependency + add_dependencies(check ${_test}) + endmacro ( ADD_FLUID_TEST ) diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index 5b15570c..a4a1a84c 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -66,6 +66,15 @@ https://stackoverflow.com/a/6251757 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. + + dynamic-sample-loading + bool + 0 (FALSE) + + When set to 1 (TRUE), samples are loaded to and unloaded from memory + on demand. + + effects-channels int diff --git a/sf2/VintageDreamsWaves-v2.sf3 b/sf2/VintageDreamsWaves-v2.sf3 new file mode 100644 index 00000000..d0a699b5 Binary files /dev/null and b/sf2/VintageDreamsWaves-v2.sf3 differ diff --git a/src/config.cmake b/src/config.cmake index 8c60f8f6..49403121 100644 --- a/src/config.cmake +++ b/src/config.cmake @@ -193,6 +193,9 @@ /* Soundfont to load for unit testing */ #cmakedefine TEST_SOUNDFONT "@TEST_SOUNDFONT@" +/* SF3 Soundfont to load for unit testing */ +#cmakedefine TEST_SOUNDFONT_SF3 "@TEST_SOUNDFONT_SF3@" + /* Define to enable SIGFPE assertions */ #cmakedefine TRAP_ON_FPE @TRAP_ON_FPE@ diff --git a/src/sfloader/fluid_defsfont.c b/src/sfloader/fluid_defsfont.c index c1a5dacc..3d5d786d 100644 --- a/src/sfloader/fluid_defsfont.c +++ b/src/sfloader/fluid_defsfont.c @@ -33,6 +33,13 @@ * compatible as most existing soundfonts expect exactly this (strange, non-standard) behaviour. */ #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_settings_getint(settings, "synth.lock-memory", &defsfont->mlock); + fluid_settings_getint(settings, "synth.dynamic-sample-loading", &defsfont->dynamic_samples); return defsfont; } @@ -252,6 +260,115 @@ const char* fluid_defsfont_get_name(fluid_defsfont_t* defsfont) 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 @@ -271,13 +388,20 @@ int fluid_defsfont_load(fluid_defsfont_t* defsfont, const fluid_file_callbacks_t return FLUID_FAILED; } + defsfont->fcbs = fcbs; + /* 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) { FLUID_LOG(FLUID_ERR, "Couldn't load soundfont file"); 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 it's loaded separately (and might be unoaded/reloaded in future) */ 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->sample24size = sfdata->sample24size; - /* load sample data in one block */ - 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 */ + /* Create all samples from sample headers */ p = sfdata->sample; while (p != NULL) { 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) { fluid_defsfont_add_sample(defsfont, sample); - fluid_voice_optimize_sample(sample); } 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 */ sfsample->fluid_sample = sample; + 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 */ p = sfdata->preset; 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_delete); + if (defsfont->dynamic_samples) + { + preset->notify = dynamic_samples_preset_notify; + } + if (preset == NULL) { return FLUID_FAILED; } @@ -378,29 +510,6 @@ int fluid_defsfont_add_preset(fluid_defsfont_t* defsfont, fluid_defpreset_t* def 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 */ @@ -1518,6 +1627,7 @@ fluid_sample_in_rom(fluid_sample_t* sample) return (sample->sampletype & FLUID_SAMPLETYPE_ROM); } + /* * fluid_sample_import_sfont */ @@ -1525,29 +1635,197 @@ int fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* defsfont) { FLUID_STRCPY(sample->name, sfsample->name); - sample->data = defsfont->sampledata; - sample->data24 = defsfont->sample24data; - sample->start = sfsample->start; - sample->end = (sfsample->end > 0) ? sfsample->end - 1 : 0; /* marks last sample, contrary to SF spec. */ - sample->loopstart = sfsample->loopstart; - sample->loopend = sfsample->loopend; + + sample->source_start = sfsample->start; + sample->source_end = (sfsample->end > 0) ? sfsample->end - 1 : 0; /* marks last sample, contrary to SF spec. */ + sample->source_loopstart = sfsample->loopstart; + sample->source_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->origpitch = sfsample->origpitch; sample->pitchadj = sfsample->pitchadj; sample->sampletype = sfsample->sampletype; + if (defsfont->dynamic_samples) + { + sample->notify = dynamic_samples_sample_notify; + } + if (fluid_sample_validate(sample, defsfont->samplesize) == FLUID_FAILED) { return FLUID_FAILED; } - if ((sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS) - && fluid_sample_decompress_vorbis(sample) == FLUID_FAILED) - { - return FLUID_FAILED; - } - - fluid_sample_sanitize_loop(sample, defsfont->samplesize); - return FLUID_OK; } + +/* 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; +} + +/* 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; + } +} diff --git a/src/sfloader/fluid_defsfont.h b/src/sfloader/fluid_defsfont.h index c4c1087b..11755953 100644 --- a/src/sfloader/fluid_defsfont.h +++ b/src/sfloader/fluid_defsfont.h @@ -94,6 +94,7 @@ int fluid_zone_inside_range(fluid_zone_range_t* zone_range, int key, int vel); */ 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 */ 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 */ @@ -107,6 +108,7 @@ struct _fluid_defsfont_t fluid_list_t* sample; /* the samples in this soundfont */ fluid_list_t* preset; /* the presets of this soundfont */ 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 */ }; @@ -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); void fluid_defsfont_iteration_start(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_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); - int fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* defsfont); int fluid_sample_in_rom(fluid_sample_t* sample); diff --git a/src/sfloader/fluid_samplecache.c b/src/sfloader/fluid_samplecache.c index e1af5bb8..001e4a96 100644 --- a/src/sfloader/fluid_samplecache.c +++ b/src/sfloader/fluid_samplecache.c @@ -45,11 +45,13 @@ struct _fluid_samplecache_entry_t unsigned int sf_sample24pos; unsigned int sf_sample24size; unsigned int sample_start; - unsigned int sample_count; + unsigned int sample_end; + int sample_type; /* End of cache key members */ short *sample_data; char *sample_data24; + int sample_count; int num_references; int mlocked; @@ -58,8 +60,8 @@ struct _fluid_samplecache_entry_t static fluid_list_t *samplecache_list = NULL; 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 *get_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_end, int sample_type); static void delete_samplecache_entry(fluid_samplecache_entry_t *entry); 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 */ 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) { fluid_samplecache_entry_t *entry; @@ -76,13 +78,13 @@ int fluid_samplecache_load(SFData *sf, 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) { - entry = new_samplecache_entry(sf, sample_start, sample_count); + entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type); if (entry == NULL) { - ret = FLUID_FAILED; + ret = -1; 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 * 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) { @@ -106,7 +108,7 @@ int fluid_samplecache_load(SFData *sf, 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."); } } @@ -115,7 +117,7 @@ int fluid_samplecache_load(SFData *sf, entry->num_references++; *sample_data = entry->sample_data; *sample_data24 = entry->sample_data24; - ret = FLUID_OK; + ret = entry->sample_count; unlock_exit: fluid_mutex_unlock(samplecache_mutex); @@ -143,7 +145,7 @@ int fluid_samplecache_unload(const short *sample_data) { 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) { fluid_munlock(entry->sample_data24, entry->sample_count); @@ -173,7 +175,8 @@ unlock_exit: /* Private functions */ static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, - unsigned int sample_count) + unsigned int sample_end, + int sample_type) { 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_sample24size = sf->sample24size; 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_data, &entry->sample_data24) == FLUID_FAILED) + entry->sample_count = fluid_sffile_read_sample_data(sf, sample_start, sample_end, sample_type, + &entry->sample_data, &entry->sample_data24); + if (entry->sample_count < 0) { 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, unsigned int sample_start, - unsigned int sample_count) + unsigned int sample_end, + int sample_type) { time_t mtime; 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->sample24size == entry->sf_sample24size) && (sample_start == entry->sample_start) && - (sample_count == entry->sample_count)) + (sample_end == entry->sample_end) && + (sample_type == entry->sample_type)) { return entry; } diff --git a/src/sfloader/fluid_samplecache.h b/src/sfloader/fluid_samplecache.h index 704ddba1..49b802ba 100644 --- a/src/sfloader/fluid_samplecache.h +++ b/src/sfloader/fluid_samplecache.h @@ -26,7 +26,7 @@ #include "fluid_sffile.h" 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 fluid_samplecache_unload(const short *sample_data); diff --git a/src/sfloader/fluid_sffile.c b/src/sfloader/fluid_sffile.c index 337e1ca2..095629a5 100644 --- a/src/sfloader/fluid_sffile.c +++ b/src/sfloader/fluid_sffile.c @@ -26,6 +26,10 @@ #include "fluid_sfont.h" #include "fluid_sys.h" +#if LIBSNDFILE_SUPPORT +#include +#endif + /*=================================sfload.c======================== Borrowed from Smurf SoundFont Editor by Josh Green =================================================================*/ @@ -259,7 +263,8 @@ static const unsigned short invalid_preset_gen[] = { } 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_sdta(SFData *sf, unsigned 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_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. * * @param fname filename * @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; 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"); goto error_exit; } + sf->filesize = fsize; 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; } - if (!load_body(sf, fsize)) + if (!load_header(sf)) { goto error_exit; } @@ -353,110 +361,51 @@ error_exit: return NULL; } -/* Load sample data from the soundfont file - * - * @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 +/* + * Parse all preset information from the soundfont * * @return FLUID_OK on success, otherwise FLUID_FAILED */ -int fluid_sffile_read_sample_data(SFData *sf, unsigned int start, unsigned int count, - short **data, char **data24) +int fluid_sffile_parse_presets(SFData *sf) { - unsigned int end; - 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)) + if (!load_body(sf)) { - FLUID_LOG(FLUID_ERR, "Sample offsets exceed sample data chunk"); - goto error_exit; + return FLUID_FAILED; } - /* 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; +/* 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; -error_exit: - FLUID_FREE(loaded_data); - FLUID_FREE(loaded_data24); - return FLUID_FAILED; + 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; } -static int load_body(SFData *sf, unsigned int size) +static int load_header(SFData *sf) { SFChunk chunk; @@ -552,7 +501,7 @@ static int load_body(SFData *sf, unsigned int size) return FALSE; } - if (chunk.size != size - 8) + if (chunk.size != sf->filesize - 8) { FLUID_LOG(FLUID_ERR, "SoundFont file size mismatch"); 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"); 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; if (!fixup_pgen(sf)) return FALSE; + if (!fixup_igen(sf)) return FALSE; @@ -1900,3 +1865,275 @@ static int valid_preset_genid(unsigned short 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; + } + + 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 diff --git a/src/sfloader/fluid_sffile.h b/src/sfloader/fluid_sffile.h index eca67394..a1f0d850 100644 --- a/src/sfloader/fluid_sffile.h +++ b/src/sfloader/fluid_sffile.h @@ -134,6 +134,8 @@ struct _SFData SFVersion version; /* sound font version */ SFVersion romver; /* ROM version */ + unsigned int filesize; + unsigned int samplepos; /* position within sffd of the sample chunk */ unsigned int samplesize; /* length within sffd of the sample chunk */ @@ -141,6 +143,9 @@ struct _SFData sample support */ unsigned int sample24size; /* length within sffd of the sm24 chunk */ + unsigned int hydrapos; + unsigned int hydrasize; + char *fname; /* file name */ FILE *sffd; /* loaded sfont file descriptor */ const fluid_file_callbacks_t *fcbs; /* file callbacks used to read this file */ @@ -204,9 +209,10 @@ struct _SFShdr }; /* 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); -int fluid_sffile_read_sample_data(SFData *sf, unsigned int start, unsigned int count, - short **data, char **data24); +int fluid_sffile_parse_presets(SFData *sf); +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 */ diff --git a/src/sfloader/fluid_sfont.c b/src/sfloader/fluid_sfont.c index 185a72a8..d2984296 100644 --- a/src/sfloader/fluid_sfont.c +++ b/src/sfloader/fluid_sfont.c @@ -21,11 +21,6 @@ #include "fluid_sfont.h" #include "fluid_sys.h" -#if LIBSNDFILE_SUPPORT -#include -#endif - - 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? */ 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) { /* 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; } - -#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 diff --git a/src/sfloader/fluid_sfont.h b/src/sfloader/fluid_sfont.h index 69a10eb5..9d3ee6e6 100644 --- a/src/sfloader/fluid_sfont.h +++ b/src/sfloader/fluid_sfont.h @@ -26,7 +26,6 @@ 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_decompress_vorbis(fluid_sample_t *sample); /* * Utility macros to access soundfonts, presets, and samples @@ -177,10 +176,22 @@ struct _fluid_preset_t { struct _fluid_sample_t { 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 end; /**< End index, index of last valid sample point (contrary to SF spec) */ unsigned int loopstart; /**< Loop start index */ unsigned int loopend; /**< Loop end index, first point following the loop (superimposed on loopstart) */ + unsigned int samplerate; /**< Sample rate */ int origpitch; /**< Original pitch (MIDI note number, 0-127) */ 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. */ 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. diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index 44e3ecfa..3c10a760 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -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", "xg"); 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); } /** diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e8f9b12e..9efb8e60 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,18 +4,17 @@ project(fluidsynth-test) ENABLE_TESTING() include ( FluidUnitTest ) +# first define the test target, used by the macros below +add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) + + ## add unit tests here ## ADD_FLUID_TEST(test_sample_cache) ADD_FLUID_TEST(test_sfont_loading) ADD_FLUID_TEST(test_defsfont_preset_iteration) ADD_FLUID_TEST(test_sample_rate_change) - -add_custom_target(check -COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure -DEPENDS -test_sample_cache -test_sfont_loading -test_defsfont_preset_iteration -test_sample_rate_change -) +if ( LIBSNDFILE_HASVORBIS ) + ADD_FLUID_TEST(test_sf3_sfont_loading) + ADD_FLUID_TEST(test_sf3_defsfont_preset_iteration) +endif ( LIBSNDFILE_HASVORBIS ) diff --git a/test/test_sf3_defsfont_preset_iteration.c b/test/test_sf3_defsfont_preset_iteration.c new file mode 100644 index 00000000..9b131bd1 --- /dev/null +++ b/test/test_sf3_defsfont_preset_iteration.c @@ -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; +} diff --git a/test/test_sf3_sfont_loading.c b/test/test_sf3_sfont_loading.c new file mode 100644 index 00000000..d454a1e4 --- /dev/null +++ b/test/test_sf3_sfont_loading.c @@ -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; +}