Merge branch '2.1.x' into master

This commit is contained in:
derselbst 2021-01-03 21:37:32 +01:00
commit e04cd572cb
16 changed files with 488 additions and 29 deletions

View File

@ -194,8 +194,8 @@ jobs:
set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
pkg-config --list-all
mkdir build && cd build || exit -1
cmake -Werror=dev -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory) $(CMAKE_FLAGS) -Denable-readline=0 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=1 -DNO_GUI=1 .. || exit -1
mingw32-make.exe all || exit -1
cmake -Werror=dev -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory) $(CMAKE_FLAGS) -Denable-readline=0 -Denable-floats=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=1 -DNO_GUI=1 .. || exit -1
mingw32-make.exe -j4 all || exit -1
displayName: 'Compile fluidsynth'
- script: |
@ECHO ON
@ -204,14 +204,14 @@ jobs:
set PATH=%PATH:C:\Program Files\Git\bin;=%
set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
cd build || exit -1
mingw32-make.exe check || exit -1
mingw32-make.exe -j4 check || exit -1
displayName: 'Execute Unittests'
continueOnError: 'true'
- script: |
@ECHO ON
cd build
mingw32-make.exe install || exit -1
xcopy test $(Build.ArtifactStagingDirectory)\bin /s
REM xcopy test $(Build.ArtifactStagingDirectory)\bin /s
del $(Build.ArtifactStagingDirectory)\bin\concrt*.dll
del $(Build.ArtifactStagingDirectory)\bin\vcruntime*.dll
del $(Build.ArtifactStagingDirectory)\bin\msvcp*.dll

View File

@ -29,7 +29,7 @@ set ( PACKAGE "fluidsynth" )
# FluidSynth package version
set ( FLUIDSYNTH_VERSION_MAJOR 2 )
set ( FLUIDSYNTH_VERSION_MINOR 1 )
set ( FLUIDSYNTH_VERSION_MICRO 5 )
set ( FLUIDSYNTH_VERSION_MICRO 6 )
set ( VERSION "${FLUIDSYNTH_VERSION_MAJOR}.${FLUIDSYNTH_VERSION_MINOR}.${FLUIDSYNTH_VERSION_MICRO}" )
set ( FLUIDSYNTH_VERSION "\"${VERSION}\"" )

View File

@ -7,9 +7,9 @@
\author Josh Green
\author David Henningsson
\author Tom Moebert
\author Copyright © 2003-2020 Peter Hanappe, Conrad Berhörster, Antoine Schmitt, Pedro López-Cabanillas, Josh Green, David Henningsson, Tom Moebert
\version Revision 2.1.5
\date 2020-09-06
\author Copyright © 2003-2021 Peter Hanappe, Conrad Berhörster, Antoine Schmitt, Pedro López-Cabanillas, Josh Green, David Henningsson, Tom Moebert
\version Revision 2.1.6
\date 2021-01-02
All the source code examples in this document are in the public domain; you can use them as you please. This document is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ . The FluidSynth library is distributed under the GNU Lesser General Public License. A copy of the GNU Lesser General Public License is contained in the FluidSynth package; if not, visit http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt or write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

View File

@ -13,7 +13,7 @@
.\" along with this program; see the file LICENSE. If not, write to
.\" the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
.\"
.TH FluidSynth 1 "Feb 16, 2020"
.TH FluidSynth 1 "Jan 1, 2021"
.\" Please update the above date whenever this man page is modified.
.\"
.\" Some roff macros, for reference:

View File

@ -1115,7 +1115,7 @@ void
print_welcome()
{
printf("FluidSynth runtime version %s\n"
"Copyright (C) 2000-2020 Peter Hanappe and others.\n"
"Copyright (C) 2000-2021 Peter Hanappe and others.\n"
"Distributed under the LGPL license.\n"
"SoundFont(R) is a registered trademark of Creative Technology Ltd.\n\n",
fluid_version_str());

View File

@ -1718,6 +1718,9 @@ delete_fluid_player(fluid_player_t *player)
fluid_return_if_fail(player != NULL);
fluid_settings_callback_int(player->synth->settings, "player.reset-synth",
NULL, NULL);
fluid_player_stop(player);
fluid_player_reset(player);

View File

@ -606,10 +606,15 @@ static FLUID_INLINE unsigned int fluid_synth_get_min_note_length_LOCAL(fluid_syn
* @param settings Configuration parameters to use (used directly).
* @return New FluidSynth instance or NULL on error
*
* @note The @p settings parameter is used directly and should freed after
* the synth has been deleted. Further note that you may modify FluidSettings of the
* @note The @p settings parameter is used directly, but the synth does not take ownership of it.
* Hence, the caller is responsible for freeing it, when no longer needed.
* Further note that you may modify FluidSettings of the
* @p settings instance. However, only those FluidSettings marked as 'realtime' will
* affect the synth immediately. See the \ref fluidsettings for more details.
*
* @warning The @p settings object should only be used by a single synth at a time. I.e. creating
* multiple synth instances with a single @p settings object causes undefined behavior. Once the
* "single synth" has been deleted, you may use the @p settings object again for another synth.
*/
fluid_synth_t *
new_fluid_synth(fluid_settings_t *settings)
@ -1018,6 +1023,50 @@ delete_fluid_synth(fluid_synth_t *synth)
fluid_profiling_print();
/* unregister all real-time settings callback, to avoid a use-after-free when changing those settings after
* this synth has been deleted*/
fluid_settings_callback_num(synth->settings, "synth.gain",
NULL, NULL);
fluid_settings_callback_int(synth->settings, "synth.polyphony",
NULL, NULL);
fluid_settings_callback_int(synth->settings, "synth.device-id",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.overflow.percussion",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.overflow.sustained",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.overflow.released",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.overflow.age",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.overflow.volume",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.overflow.important",
NULL, NULL);
fluid_settings_callback_str(synth->settings, "synth.overflow.important-channels",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.reverb.room-size",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.reverb.damp",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.reverb.width",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.reverb.level",
NULL, NULL);
fluid_settings_callback_int(synth->settings, "synth.reverb.active",
NULL, NULL);
fluid_settings_callback_int(synth->settings, "synth.chorus.active",
NULL, NULL);
fluid_settings_callback_int(synth->settings, "synth.chorus.nr",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.chorus.level",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.chorus.depth",
NULL, NULL);
fluid_settings_callback_num(synth->settings, "synth.chorus.speed",
NULL, NULL);
/* turn off all voices, needed to unload SoundFont data */
if(synth->voice != NULL)
{
@ -1030,6 +1079,12 @@ delete_fluid_synth(fluid_synth_t *synth)
continue;
}
/* WARNING: A this point we must ensure that the reference counter
of any soundfont sample owned by any rvoice belonging to the voice
are correctly decremented. This is the contrary part to
to fluid_voice_init() where the sample's reference counter is
incremented.
*/
fluid_voice_unlock_rvoice(voice);
fluid_voice_overflow_rvoice_finished(voice);
@ -1082,6 +1137,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)
{
@ -4653,7 +4720,21 @@ fluid_synth_check_finished_voices(fluid_synth_t *synth)
}
else if(synth->voice[j]->overflow_rvoice == fv)
{
/* Unlock the overflow_rvoice of the voice.
Decrement the reference count of the sample owned by this
rvoice.
*/
fluid_voice_overflow_rvoice_finished(synth->voice[j]);
/* Decrement synth active voice count. Must not be incorporated
in fluid_voice_overflow_rvoice_finished() because
fluid_voice_overflow_rvoice_finished() is called also
at synth destruction and in this case the variable should be
accessed via voice->channel->synth->active_voice_count.
And for certain voices which are not playing, the field
voice->channel is NULL.
*/
synth->active_voice_count--;
break;
}
}
@ -5161,11 +5242,25 @@ fluid_synth_sfload(fluid_synth_t *synth, const char *filename, int reset_presets
}
/**
* Unload a SoundFont.
* Schedule a SoundFont for unloading.
*
* If the SoundFont isn't used anymore by any playing voices, it will be unloaded immediately.
*
* If any samples of the given SoundFont are still required by active voices,
* the SoundFont will be unloaded in a lazy manner, once those voices have finished synthesizing.
* If you call delete_fluid_synth(), all voices will be destroyed and the SoundFont
* will be unloaded in any case.
* Once this function returned, fluid_synth_sfcount() and similar functions will behave as if
* the SoundFont has already been unloaded, even though the lazy-unloading is still pending.
*
* @note This lazy-unloading mechanism was broken between FluidSynth 1.1.4 and 2.1.5 . As a
* consequence, SoundFonts scheduled for lazy-unloading may be never freed under certain
* conditions. Calling delete_fluid_synth() does not recover this situation either.
*
* @param synth FluidSynth instance
* @param id ID of SoundFont to unload
* @param reset_presets TRUE to re-assign presets for all MIDI channels
* @return #FLUID_OK on success, #FLUID_FAILED on error
* @return #FLUID_OK if the given @p id was found, #FLUID_FAILED otherwise.
*/
int
fluid_synth_sfunload(fluid_synth_t *synth, int id, int reset_presets)
@ -5226,7 +5321,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);
}
}
}

View File

@ -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 */

View File

@ -174,6 +174,7 @@ static void fluid_voice_swap_rvoice(fluid_voice_t *voice)
voice->can_access_rvoice = voice->can_access_overflow_rvoice;
voice->overflow_rvoice = rtemp;
voice->can_access_overflow_rvoice = ctemp;
voice->overflow_sample = voice->sample;
}
static void fluid_voice_initialize_rvoice(fluid_voice_t *voice, fluid_real_t output_rate)
@ -242,6 +243,7 @@ new_fluid_voice(fluid_rvoice_eventhandler_t *handler, fluid_real_t output_rate)
voice->eventhandler = handler;
voice->channel = NULL;
voice->sample = NULL;
voice->overflow_sample = NULL;
voice->output_rate = output_rate;
/* Initialize both the rvoice and overflow_rvoice */
@ -321,12 +323,13 @@ fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample,
voice->has_noteoff = 0;
UPDATE_RVOICE0(fluid_rvoice_reset);
/* Increment the reference count of the sample to prevent the
unloading of the soundfont while this voice is playing,
once for us and once for the rvoice. */
/*
We increment the reference count of the sample to indicate that this
sample is about to be owned by the rvoice. This will prevent the
unloading of the soundfont while this rvoice is playing.
*/
fluid_sample_incr_ref(sample);
fluid_rvoice_eventhandler_push_ptr(voice->eventhandler, fluid_rvoice_set_sample, voice->rvoice, sample);
fluid_sample_incr_ref(sample);
voice->sample = sample;
i = fluid_channel_get_interp_method(channel);
@ -1413,12 +1416,20 @@ fluid_voice_kill_excl(fluid_voice_t *voice)
}
/*
* Called by fluid_synth when the overflow rvoice can be reclaimed.
* Unlock the overflow rvoice of the voice.
* Decrement the reference count of the sample owned by this rvoice.
*
* Called by fluid_synth when the overflow rvoice has finished by itself.
* Must be called also explicitly at synth destruction to ensure that
* the soundfont be unloaded successfully.
*/
void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice)
{
voice->can_access_overflow_rvoice = 1;
fluid_voice_sample_unref(&voice->overflow_rvoice->dsp.sample);
/* Decrement the reference count of the sample to indicate
that this sample isn't owned by the rvoice anymore */
fluid_voice_sample_unref(&voice->overflow_sample);
}
/*
@ -1446,17 +1457,14 @@ fluid_voice_stop(fluid_voice_t *voice)
voice->chan = NO_CHANNEL;
if(voice->can_access_rvoice)
{
fluid_voice_sample_unref(&voice->rvoice->dsp.sample);
}
/* Decrement the reference count of the sample, to indicate
that this sample isn't owned by the rvoice anymore.
*/
fluid_voice_sample_unref(&voice->sample);
voice->status = FLUID_VOICE_OFF;
voice->has_noteoff = 1;
/* Decrement the reference count of the sample. */
fluid_voice_sample_unref(&voice->sample);
/* Decrement voice count */
voice->channel->synth->active_voice_count--;
}

View File

@ -71,7 +71,8 @@ struct _fluid_voice_t
fluid_channel_t *channel;
fluid_rvoice_eventhandler_t *eventhandler;
fluid_zone_range_t *zone_range; /* instrument zone range*/
fluid_sample_t *sample; /* Pointer to sample (dupe in rvoice) */
fluid_sample_t *sample; /* Pointer to sample (dupe in rvoice) */
fluid_sample_t *overflow_sample; /* Pointer to sample (dupe in overflow_rvoice) */
unsigned int start_time;
int mod_count;

View File

@ -909,6 +909,10 @@ fluid_settings_get_hints(fluid_settings_t *settings, const char *name, int *hint
* @param settings a settings object
* @param name a setting's name
* @return TRUE if the setting is changeable in real-time, FALSE otherwise
*
* @note Before using this function, make sure the @p settings object has already been used to create
* a synthesizer, a MIDI driver, an audio driver, a MIDI player, or a command handler (depending on
* which settings you want to query).
*/
int
fluid_settings_is_realtime(fluid_settings_t *settings, const char *name)

View File

@ -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;
@ -1160,6 +1164,7 @@ fluid_timer_run(void *data)
}
FLUID_LOG(FLUID_DBG, "Timer thread finished");
timer->callback = NULL;
if(timer->auto_destroy)
{
@ -1253,6 +1258,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;
}
/***************************************************************
*

View File

@ -235,6 +235,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)

View File

@ -13,6 +13,7 @@ ADD_FLUID_TEST(test_sample_rate_change)
ADD_FLUID_TEST(test_preset_sample_loading)
ADD_FLUID_TEST(test_preset_pinning)
ADD_FLUID_TEST(test_bug_635)
ADD_FLUID_TEST(test_settings_unregister_callback)
ADD_FLUID_TEST(test_pointer_alignment)
ADD_FLUID_TEST(test_seqbind_unregister)
ADD_FLUID_TEST(test_synth_chorus_reverb)
@ -20,6 +21,7 @@ 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_sfont_unloading)
ADD_FLUID_TEST(test_seq_event_queue_sort)
ADD_FLUID_TEST(test_seq_scale)
ADD_FLUID_TEST(test_seq_evt_order)

View File

@ -0,0 +1,95 @@
#include "test.h"
#include "fluidsynth.h"
#include "fluidsynth_priv.h"
#include "fluid_synth.h"
#include <string.h>
static fluid_list_t* realtime_int_settings = NULL;
static fluid_list_t* realtime_str_settings = NULL;
static fluid_list_t* realtime_num_settings = NULL;
void iter_func (void *data, const char *name, int type)
{
if(fluid_settings_is_realtime(data, name))
{
switch(type)
{
case FLUID_INT_TYPE:
realtime_int_settings = fluid_list_prepend(realtime_int_settings, FLUID_STRDUP(name));
break;
case FLUID_STR_TYPE:
realtime_str_settings = fluid_list_prepend(realtime_str_settings, FLUID_STRDUP(name));
break;
case FLUID_NUM_TYPE:
realtime_num_settings = fluid_list_prepend(realtime_num_settings, FLUID_STRDUP(name));
break;
case FLUID_SET_TYPE:
break;
default:
TEST_ASSERT(FALSE);
}
}
}
// this test should make sure that sample rate changed are handled correctly
int main(void)
{
fluid_list_t* list;
fluid_player_t* player;
fluid_synth_t *synth;
fluid_settings_t *settings = new_fluid_settings();
TEST_ASSERT(settings != NULL);
synth = new_fluid_synth(settings);
TEST_ASSERT(synth != NULL);
player = new_fluid_player(synth);
TEST_ASSERT(player != NULL);
// see which of the objects above has registered a realtime setting
fluid_settings_foreach(settings, settings, iter_func);
// delete the objects
delete_fluid_player(player);
delete_fluid_synth(synth);
// and now, start making changes to those realtime settings
// Anything below fluidsynth 2.1.5 will crash
for(list = realtime_int_settings; list; list = fluid_list_next(list))
{
int min, max;
char* name = fluid_list_get(list);
TEST_SUCCESS(fluid_settings_getint_range(settings, name, &min, &max));
TEST_SUCCESS(fluid_settings_setint(settings, name, min));
FLUID_FREE(name);
}
delete_fluid_list(realtime_int_settings);
for(list = realtime_num_settings; list; list = fluid_list_next(list))
{
double min, max;
char* name = fluid_list_get(list);
TEST_SUCCESS(fluid_settings_getnum_range(settings, name, &min, &max));
TEST_SUCCESS(fluid_settings_setnum(settings, name, min));
FLUID_FREE(name);
}
delete_fluid_list(realtime_num_settings);
for(list = realtime_str_settings; list; list = fluid_list_next(list))
{
char* name = fluid_list_get(list);
TEST_SUCCESS(fluid_settings_setstr(settings, name, "ABCDEFG"));
FLUID_FREE(name);
}
delete_fluid_list(realtime_str_settings);
delete_fluid_settings(settings);
return EXIT_SUCCESS;
}

229
test/test_sfont_unloading.c Normal file
View 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;
}