mirror of
https://github.com/ZDoom/fluidsynth.git
synced 2024-11-10 06:51:54 +00:00
This PR adds regression tests for #727, ensuring that soundfonts are correctly unloaded via the lazy-timer-unloading mechanism.
This commit is contained in:
parent
6339feb106
commit
5c1cfe6a5f
6 changed files with 267 additions and 3 deletions
|
@ -1119,6 +1119,18 @@ delete_fluid_synth(fluid_synth_t *synth)
|
|||
|
||||
delete_fluid_list(synth->loaders);
|
||||
|
||||
/* wait for and delete all the lazy sfont unloading timers */
|
||||
|
||||
for(list = synth->fonts_to_be_unloaded; list; list = fluid_list_next(list))
|
||||
{
|
||||
fluid_timer_t* timer = fluid_list_get(list);
|
||||
// explicitly join to wait for the unload really to happen
|
||||
fluid_timer_join(timer);
|
||||
// delete_fluid_timer alone would stop the timer, even if it had not unloaded the soundfont yet
|
||||
delete_fluid_timer(timer);
|
||||
}
|
||||
|
||||
delete_fluid_list(synth->fonts_to_be_unloaded);
|
||||
|
||||
if(synth->channel != NULL)
|
||||
{
|
||||
|
@ -4846,7 +4858,8 @@ fluid_synth_sfont_unref(fluid_synth_t *synth, fluid_sfont_t *sfont)
|
|||
} /* spin off a timer thread to unload the sfont later (SoundFont loader blocked unload) */
|
||||
else
|
||||
{
|
||||
new_fluid_timer(100, fluid_synth_sfunload_callback, sfont, TRUE, TRUE, FALSE);
|
||||
fluid_timer_t* timer = new_fluid_timer(100, fluid_synth_sfunload_callback, sfont, TRUE, FALSE, FALSE);
|
||||
synth->fonts_to_be_unloaded = fluid_list_prepend(synth->fonts_to_be_unloaded, timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ struct _fluid_synth_t
|
|||
fluid_list_t *loaders; /**< the SoundFont loaders */
|
||||
fluid_list_t *sfont; /**< List of fluid_sfont_info_t for each loaded SoundFont (remains until SoundFont is unloaded) */
|
||||
int sfont_id; /**< Incrementing ID assigned to each loaded SoundFont */
|
||||
fluid_list_t *fonts_to_be_unloaded; /**< list of timers that try to unload a soundfont */
|
||||
|
||||
float gain; /**< master gain */
|
||||
fluid_channel_t **channel; /**< the channels */
|
||||
|
|
|
@ -60,6 +60,10 @@ typedef struct
|
|||
struct _fluid_timer_t
|
||||
{
|
||||
long msec;
|
||||
|
||||
// Pointer to a function to be executed by the timer.
|
||||
// This field is set to NULL once the timer is finished to indicate completion.
|
||||
// This allows for timed waits, rather than waiting forever as fluid_timer_join() does.
|
||||
fluid_timer_callback_t callback;
|
||||
void *data;
|
||||
fluid_thread_t *thread;
|
||||
|
@ -1096,6 +1100,7 @@ fluid_timer_run(void *data)
|
|||
}
|
||||
|
||||
FLUID_LOG(FLUID_DBG, "Timer thread finished");
|
||||
timer->callback = NULL;
|
||||
|
||||
if(timer->auto_destroy)
|
||||
{
|
||||
|
@ -1189,6 +1194,19 @@ fluid_timer_join(fluid_timer_t *timer)
|
|||
return FLUID_OK;
|
||||
}
|
||||
|
||||
int
|
||||
fluid_timer_is_running(const fluid_timer_t *timer)
|
||||
{
|
||||
// for unit test usage only
|
||||
return timer->callback != NULL;
|
||||
}
|
||||
|
||||
long fluid_timer_get_interval(const fluid_timer_t * timer)
|
||||
{
|
||||
// for unit test usage only
|
||||
return timer->msec;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************
|
||||
*
|
||||
|
|
|
@ -232,6 +232,8 @@ fluid_timer_t *new_fluid_timer(int msec, fluid_timer_callback_t callback,
|
|||
void delete_fluid_timer(fluid_timer_t *timer);
|
||||
int fluid_timer_join(fluid_timer_t *timer);
|
||||
int fluid_timer_stop(fluid_timer_t *timer);
|
||||
int fluid_timer_is_running(const fluid_timer_t *timer);
|
||||
long fluid_timer_get_interval(const fluid_timer_t * timer);
|
||||
|
||||
// Macros to use for pre-processor if statements to test which Glib thread API we have (pre or post 2.32)
|
||||
#define NEW_GLIB_THREAD_API GLIB_CHECK_VERSION(2,32,0)
|
||||
|
|
|
@ -20,9 +20,10 @@ ADD_FLUID_TEST(test_snprintf)
|
|||
ADD_FLUID_TEST(test_synth_process)
|
||||
ADD_FLUID_TEST(test_ct2hz)
|
||||
ADD_FLUID_TEST(test_sample_validate)
|
||||
ADD_FLUID_TEST(test_seq_event_queue_sort)
|
||||
ADD_FLUID_TEST(test_seq_scale)
|
||||
ADD_FLUID_TEST(test_jack_obtaining_synth)
|
||||
ADD_FLUID_TEST(test_sfont_unloading)
|
||||
ADD_FLUID_TEST(test_seq_scale)
|
||||
ADD_FLUID_TEST(test_seq_event_queue_sort)
|
||||
|
||||
# if ( LIBSNDFILE_HASVORBIS )
|
||||
# ADD_FLUID_TEST(test_sf3_sfont_loading)
|
||||
|
|
229
test/test_sfont_unloading.c
Normal file
229
test/test_sfont_unloading.c
Normal file
|
@ -0,0 +1,229 @@
|
|||
|
||||
#include "test.h"
|
||||
#include "fluidsynth.h"
|
||||
#include "synth/fluid_synth.h"
|
||||
#include "utils/fluid_sys.h"
|
||||
|
||||
void wait_and_free(fluid_synth_t* synth, int id, const char* calling_func)
|
||||
{
|
||||
fluid_list_t *list, *list_orig;
|
||||
list_orig = list = synth->fonts_to_be_unloaded;
|
||||
synth->fonts_to_be_unloaded = NULL;
|
||||
delete_fluid_synth(synth);
|
||||
|
||||
for(; list; list = fluid_list_next(list))
|
||||
{
|
||||
fluid_timer_t* timer = fluid_list_get(list);
|
||||
FLUID_LOG(FLUID_INFO, "%s(): Start waiting for soundfont %d to unload", calling_func, id);
|
||||
if(fluid_timer_is_running(timer))
|
||||
{
|
||||
/* timer still running, wait a bit*/
|
||||
fluid_msleep(50 * fluid_timer_get_interval(timer));
|
||||
TEST_ASSERT(!fluid_timer_is_running(timer));
|
||||
}
|
||||
delete_fluid_timer(timer);
|
||||
FLUID_LOG(FLUID_INFO, "%s(): End waiting for soundfont %d to unload", calling_func, id);
|
||||
}
|
||||
delete_fluid_list(list_orig);
|
||||
}
|
||||
|
||||
static void test_without_rendering(fluid_settings_t* settings)
|
||||
{
|
||||
int id;
|
||||
fluid_synth_t *synth = new_fluid_synth(settings);
|
||||
TEST_ASSERT(synth != NULL);
|
||||
|
||||
TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE);
|
||||
|
||||
// load a sfont to synth
|
||||
TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1));
|
||||
// one sfont loaded
|
||||
TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
|
||||
|
||||
TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127));
|
||||
|
||||
TEST_SUCCESS(fluid_synth_sfunload(synth, id, 1));
|
||||
|
||||
TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 60));
|
||||
|
||||
// there must be one font scheduled for lazy unloading
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
|
||||
|
||||
wait_and_free(synth, id, __func__);
|
||||
}
|
||||
|
||||
// this should work fine after applying JJCs fix a4ac56502fec5f0c20a60187d965c94ba1dc81c2
|
||||
static void test_after_polyphony_exceeded(fluid_settings_t* settings)
|
||||
{
|
||||
int id;
|
||||
fluid_synth_t *synth = new_fluid_synth(settings);
|
||||
TEST_ASSERT(synth != NULL);
|
||||
|
||||
TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE);
|
||||
|
||||
// load a sfont to synth
|
||||
TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1));
|
||||
// one sfont loaded
|
||||
TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
|
||||
|
||||
TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127));
|
||||
FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): note on C4, voice count=%d",
|
||||
fluid_synth_get_active_voice_count(synth));
|
||||
|
||||
// need to render a bit to make synth->ticks_since_start advance, to make the previous voice "killable"
|
||||
TEST_SUCCESS(fluid_synth_process(synth, 2048, 0, NULL, 0, NULL));
|
||||
|
||||
// polyphony exceeded - killing the killable voice from above
|
||||
TEST_SUCCESS(fluid_synth_noteon(synth, 0, 61, 127));
|
||||
|
||||
// need to render again, to make the synth thread assign rvoice->dsp.sample, so that sample_unref() later really unrefs
|
||||
TEST_SUCCESS(fluid_synth_process(synth, 2048, 0, NULL, 0, NULL));
|
||||
FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): note on C#4, voice count=%d",
|
||||
fluid_synth_get_active_voice_count(synth));
|
||||
|
||||
FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): unload sounfont");
|
||||
TEST_SUCCESS(fluid_synth_sfunload(synth, id, 1));
|
||||
|
||||
TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 61));
|
||||
|
||||
// need to render yet again, to make the synth thread release the rvoice so it can be reclaimed by
|
||||
// fluid_synth_check_finished_voices()
|
||||
// need to render may more samples this time, so the voice makes it pass the release phase...
|
||||
TEST_SUCCESS(fluid_synth_process(synth, 204800, 0, NULL, 0, NULL));
|
||||
|
||||
// make any API call to execute fluid_synth_check_finished_voices()
|
||||
FLUID_LOG(FLUID_INFO, "test_after_polyphony_exceeded(): note off C#4, voice count=%d",
|
||||
fluid_synth_get_active_voice_count(synth));
|
||||
|
||||
// there must be one font scheduled for lazy unloading
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
|
||||
|
||||
wait_and_free(synth, id, __func__);
|
||||
}
|
||||
|
||||
static void test_default_polyphony(fluid_settings_t* settings, int with_rendering)
|
||||
{
|
||||
enum { BUFSIZE = 128 };
|
||||
fluid_voice_t* buf[BUFSIZE];
|
||||
|
||||
int id;
|
||||
fluid_synth_t *synth = new_fluid_synth(settings);
|
||||
TEST_ASSERT(synth != NULL);
|
||||
|
||||
TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE);
|
||||
|
||||
// load a sfont to synth
|
||||
TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1));
|
||||
// one sfont loaded
|
||||
TEST_ASSERT(fluid_synth_sfcount(synth) == 1);
|
||||
|
||||
TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127));
|
||||
|
||||
if(with_rendering)
|
||||
{
|
||||
TEST_SUCCESS(fluid_synth_process(synth, fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
|
||||
}
|
||||
|
||||
TEST_SUCCESS(fluid_synth_noteon(synth, 0, 61, 127));
|
||||
|
||||
fluid_synth_get_voicelist(synth, buf, BUFSIZE, -1);
|
||||
|
||||
TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
|
||||
|
||||
if(with_rendering)
|
||||
{
|
||||
// make the synth thread assign rvoice->dsp.sample
|
||||
TEST_SUCCESS(fluid_synth_process(synth, 2 * fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
|
||||
|
||||
TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
|
||||
}
|
||||
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded == NULL);
|
||||
|
||||
TEST_SUCCESS(fluid_synth_sfunload(synth, id, 1));
|
||||
|
||||
// now, there must be one font scheduled for lazy unloading
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
|
||||
TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
|
||||
|
||||
// noteoff the second note and render something
|
||||
TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 61));
|
||||
if(with_rendering)
|
||||
{
|
||||
TEST_SUCCESS(fluid_synth_process(synth, fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
|
||||
}
|
||||
|
||||
// still 4 because key 61 is playing in release phase now
|
||||
TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
|
||||
// must be still running
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
|
||||
TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
|
||||
|
||||
// noteoff the first note and render something
|
||||
TEST_SUCCESS(fluid_synth_noteoff(synth, 0, 60));
|
||||
if(with_rendering)
|
||||
{
|
||||
TEST_SUCCESS(fluid_synth_process(synth, fluid_synth_get_internal_bufsize(synth), 0, NULL, 0, NULL));
|
||||
}
|
||||
|
||||
// still 4 because keys 60 + 61 are playing in release phase now
|
||||
TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 4);
|
||||
// must be still running
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
|
||||
TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
|
||||
|
||||
if(with_rendering)
|
||||
{
|
||||
// render enough, to make the synth thread release the rvoice so it can be reclaimed by
|
||||
// fluid_synth_check_finished_voices()
|
||||
TEST_SUCCESS(fluid_synth_process(synth, 2048000, 0, NULL, 0, NULL));
|
||||
|
||||
// this API call should reclaim the rvoices and call fluid_voice_stop()
|
||||
TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 0);
|
||||
}
|
||||
|
||||
TEST_ASSERT(synth->fonts_to_be_unloaded != NULL);
|
||||
if(with_rendering)
|
||||
{
|
||||
// We want to see that the timer thread unloads the soundfont before we call delete_fluid_synth().
|
||||
// Wait to give the timer thread a chance to unload and finish.
|
||||
fluid_msleep(10 * fluid_timer_get_interval(fluid_list_get(synth->fonts_to_be_unloaded)));
|
||||
TEST_ASSERT(!fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
|
||||
}
|
||||
else
|
||||
{
|
||||
TEST_ASSERT(fluid_timer_is_running(fluid_list_get(synth->fonts_to_be_unloaded)));
|
||||
}
|
||||
|
||||
wait_and_free(synth, id, __func__);
|
||||
}
|
||||
|
||||
// this tests the soundfont loading API of the synth.
|
||||
// might be expanded to test the soundfont loader as well...
|
||||
int main(void)
|
||||
{
|
||||
fluid_settings_t *settings = new_fluid_settings();
|
||||
TEST_ASSERT(settings != NULL);
|
||||
|
||||
FLUID_LOG(FLUID_INFO, "Begin test_default_polyphony() with rendering");
|
||||
test_default_polyphony(settings, TRUE);
|
||||
FLUID_LOG(FLUID_INFO, "End test_default_polyphony()\n");
|
||||
|
||||
FLUID_LOG(FLUID_INFO, "Begin test_default_polyphony() without rendering");
|
||||
test_default_polyphony(settings, FALSE);
|
||||
FLUID_LOG(FLUID_INFO, "End test_default_polyphony()\n");
|
||||
|
||||
fluid_settings_setint(settings, "synth.polyphony", 2);
|
||||
|
||||
FLUID_LOG(FLUID_INFO, "Begin test_after_polyphony_exceeded()");
|
||||
test_after_polyphony_exceeded(settings);
|
||||
FLUID_LOG(FLUID_INFO, "End test_after_polyphony_exceeded()\n");
|
||||
|
||||
FLUID_LOG(FLUID_INFO, "Begin test_without_rendering()");
|
||||
test_without_rendering(settings);
|
||||
FLUID_LOG(FLUID_INFO, "End test_without_rendering()");
|
||||
|
||||
delete_fluid_settings(settings);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
Loading…
Reference in a new issue