diff --git a/.azure/azure-pipelines-android.yml b/.azure/azure-pipelines-android.yml index 0ff0d528..c289b1fd 100644 --- a/.azure/azure-pipelines-android.yml +++ b/.azure/azure-pipelines-android.yml @@ -44,8 +44,7 @@ variables: VORBIS_VERSION: '1.3.7' OGG_VERSION: '1.3.5' OPUS_VERSION: '1.3.1' - # flac 1.3.3 is completely broken: pkgconfig is incorrectly installed, compilation failure, etc.; use recent master instead - FLAC_VERSION: '27c615706cedd252a206dd77e3910dfa395dcc49' + FLAC_VERSION: '1.3.4' # Android NDK sources and standalone toolchain is put here DEV: '$(System.DefaultWorkingDirectory)/android-build-root' @@ -160,7 +159,7 @@ jobs: wget https://github.com/xiph/ogg/releases/download/v${OGG_VERSION}/libogg-${OGG_VERSION}.tar.gz tar zxf libogg-${OGG_VERSION}.tar.gz - wget -O flac-${FLAC_VERSION}.tar.gz https://github.com/xiph/flac/archive/${FLAC_VERSION}.tar.gz + wget -O flac-${FLAC_VERSION}.tar.gz https://github.com/xiph/flac/archive/refs/tags/${FLAC_VERSION}.tar.gz tar xf flac-${FLAC_VERSION}.tar.gz wget -O opus-${OPUS_VERSION}.tar.gz https://github.com/xiph/opus/archive/refs/tags/v${OPUS_VERSION}.tar.gz diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index 7a481195..b0ce4d4e 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -118,8 +118,7 @@ Developers: bool 0 (FALSE) - When set to 1 (TRUE), samples are loaded to and unloaded from memory - on demand. + When set to 1 (TRUE), samples are loaded to and unloaded from memory whenever presets are being selected or unselected for a MIDI channel. PROGRAM_CHANGE and PROGRAM_SELECT events are typically responsible for this. diff --git a/src/drivers/fluid_adriver.h b/src/drivers/fluid_adriver.h index 8720fbfc..8517f337 100644 --- a/src/drivers/fluid_adriver.h +++ b/src/drivers/fluid_adriver.h @@ -23,6 +23,10 @@ #include "fluidsynth_priv.h" +#ifdef __cplusplus +extern "C" { +#endif + /* * fluid_audio_driver_t */ @@ -175,6 +179,8 @@ fluid_audio_driver_t *new_fluid_file_audio_driver(fluid_settings_t *settings, void delete_fluid_file_audio_driver(fluid_audio_driver_t *p); #endif - +#ifdef __cplusplus +} +#endif #endif /* _FLUID_AUDRIVER_H */ diff --git a/src/drivers/fluid_coreaudio.c b/src/drivers/fluid_coreaudio.c index 7b3358d6..3be562c4 100644 --- a/src/drivers/fluid_coreaudio.c +++ b/src/drivers/fluid_coreaudio.c @@ -73,7 +73,7 @@ OSStatus fluid_core_audio_callback(void *data, #define OK(x) (x == noErr) -#if MAC_OS_X_VERSION < 1200 +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 120000 #define kAudioObjectPropertyElementMain (kAudioObjectPropertyElementMaster) #endif diff --git a/src/drivers/fluid_oboe.cpp b/src/drivers/fluid_oboe.cpp index f95af01f..37ce916b 100644 --- a/src/drivers/fluid_oboe.cpp +++ b/src/drivers/fluid_oboe.cpp @@ -25,13 +25,9 @@ * This file may make use of C++14, because it's required by oboe anyway. */ -extern "C" { - #include "fluid_adriver.h" #include "fluid_settings.h" -} // extern "C" - #if OBOE_SUPPORT #include diff --git a/src/synth/fluid_chan.c b/src/synth/fluid_chan.c index b685a766..0f2eecb4 100644 --- a/src/synth/fluid_chan.c +++ b/src/synth/fluid_chan.c @@ -162,9 +162,10 @@ fluid_channel_init_ctrl(fluid_channel_t *chan, int is_all_ctrl_off) fluid_channel_set_cc(chan, i, 0); } - fluid_channel_clear_portamento(chan); /* Clear PTC receive */ chan->previous_cc_breath = 0;/* Reset previous breath */ } + /* Unconditionally clear PTC receive (issue #1050) */ + fluid_channel_clear_portamento(chan); /* Reset polyphonic key pressure on all voices */ for(i = 0; i < 128; i++) diff --git a/src/synth/fluid_mod.c b/src/synth/fluid_mod.c index effa2027..bebfb9df 100644 --- a/src/synth/fluid_mod.c +++ b/src/synth/fluid_mod.c @@ -184,6 +184,8 @@ fluid_mod_get_source_value(const unsigned char mod_src, if(mod_flags & FLUID_MOD_CC) { + val = fluid_channel_get_cc(chan, mod_src); + /* From MIDI Recommended Practice (RP-036) Default Pan Formula: * "Since MIDI controller values range from 0 to 127, the exact center * of the range, 63.5, cannot be represented. Therefore, the effective @@ -197,16 +199,20 @@ fluid_mod_get_source_value(const unsigned char mod_src, if(mod_src == PAN_MSB || mod_src == BALANCE_MSB) { *range = 126; - val = fluid_channel_get_cc(chan, mod_src) - 1; + val -= 1; if(val < 0) { val = 0; } } - else + else if(mod_src == PORTAMENTO_CTRL) { - val = fluid_channel_get_cc(chan, mod_src); + // an invalid portamento fromkey should be treated as 0 when it's actually used for moulating + if(!fluid_channel_is_valid_note(val)) + { + val = 0; + } } } else diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index b6a8c3ee..636dc01e 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -1794,6 +1794,9 @@ fluid_synth_cc_LOCAL(fluid_synth_t *synth, int channum, int num) case ALL_CTRL_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_channel_init_ctrl(chan, 1); + // the hold pedals have been reset, we maybe need to release voices + fluid_synth_damp_voices_by_sustain_LOCAL(synth, channum); + fluid_synth_damp_voices_by_sostenuto_LOCAL(synth, channum); fluid_synth_modulate_voices_all_LOCAL(synth, channum); break; diff --git a/src/utils/fluid_settings.h b/src/utils/fluid_settings.h index c05e0ed0..73a63e86 100644 --- a/src/utils/fluid_settings.h +++ b/src/utils/fluid_settings.h @@ -22,6 +22,10 @@ #ifndef _FLUID_SETTINGS_H #define _FLUID_SETTINGS_H +#ifdef __cplusplus +extern "C" { +#endif + int fluid_settings_add_option(fluid_settings_t *settings, const char *name, const char *s); int fluid_settings_remove_option(fluid_settings_t *settings, const char *name, const char *s); @@ -54,4 +58,8 @@ int fluid_settings_split_csv(const char *str, int *buf, int buf_len); void* fluid_settings_get_user_data(fluid_settings_t * settings, const char *name); +#ifdef __cplusplus +} +#endif + #endif /* _FLUID_SETTINGS_H */ diff --git a/src/utils/fluidsynth_priv.h b/src/utils/fluidsynth_priv.h index 999b3245..ce5fb7fd 100644 --- a/src/utils/fluidsynth_priv.h +++ b/src/utils/fluidsynth_priv.h @@ -48,6 +48,9 @@ #include "fluidsynth.h" +#ifdef __cplusplus +extern "C" { +#endif /*************************************************************** * @@ -318,5 +321,8 @@ else \ #define fluid_return_val_if_fail(cond, val) \ fluid_return_if_fail(cond) (val) +#ifdef __cplusplus +} +#endif #endif /* _FLUIDSYNTH_PRIV_H */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 72bca3f9..f1ab8748 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on ## add unit tests here ## +ADD_FLUID_TEST(test_synth_reset_cc) ADD_FLUID_TEST(test_sample_cache) ADD_FLUID_TEST(test_sfont_loading) ADD_FLUID_TEST(test_sample_rate_change) diff --git a/test/test_synth_reset_cc.c b/test/test_synth_reset_cc.c new file mode 100644 index 00000000..25e81471 --- /dev/null +++ b/test/test_synth_reset_cc.c @@ -0,0 +1,146 @@ + +#include "test.h" +#include "fluidsynth.h" +#include "fluidsynth_priv.h" +#include "fluid_synth.h" +#include "fluid_midi.h" +#include "fluid_chan.h" +#include + +// render enough samples to go past the release phase of the voice +enum { SAMPLES=100*1024 }; + +static void test_sustain(fluid_synth_t* synth, int chan) +{ + // depress sustain pedal + TEST_SUCCESS(fluid_synth_cc(synth, chan, SUSTAIN_SWITCH, 127)); + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + + // hit a note, after some (render-)time has passed + TEST_SUCCESS(fluid_synth_noteon(synth, chan, 60, 127)); + + // trigger the dsp loop to force rvoice creation + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + + // one voice must be playing by now + TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 2); + + // send noteoff + TEST_SUCCESS(fluid_synth_noteoff(synth, chan, 60)); + + // voice stays on + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 2); + + // reset controllers + TEST_SUCCESS(fluid_synth_cc(synth, chan, ALL_CTRL_OFF, 0)); + + // voice should be off now + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 0); +} + +static void test_sostenuto(fluid_synth_t *synth, int chan) +{ + // play a note + TEST_SUCCESS(fluid_synth_noteon(synth, chan, 60, 127)); + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + + // depress sostenuto pedal + TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 2); + TEST_SUCCESS(fluid_synth_cc(synth, chan, SOSTENUTO_SWITCH, 127)); + + // send noteoff right afterwards + TEST_SUCCESS(fluid_synth_noteoff(synth, chan, 60)); + + // voice stays on after rendering + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 2); + + // reset controllers + TEST_SUCCESS(fluid_synth_cc(synth, chan, ALL_CTRL_OFF, 0)); + + // voice should be off now + TEST_SUCCESS(fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL)); + TEST_ASSERT(fluid_synth_get_active_voice_count(synth) == 0); +} + +static void test_portamento_fromkey(fluid_synth_t* synth, int chan) +{ + int ptc; + + // Portamento is disabled + TEST_SUCCESS(fluid_synth_cc(synth, chan, PORTAMENTO_CTRL, 127)); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_CTRL, &ptc)); + TEST_ASSERT(ptc == 127); + + TEST_SUCCESS(fluid_synth_cc(synth, chan, ALL_CTRL_OFF, 0)); + + // Because PTC is used for modulating, it should be reset to zero + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_CTRL, &ptc)); + TEST_ASSERT(!fluid_channel_is_valid_note(ptc)); + + // Enable Portamento + TEST_SUCCESS(fluid_synth_cc(synth, chan, PORTAMENTO_SWITCH, 64)); + TEST_SUCCESS(fluid_synth_cc(synth, chan, PORTAMENTO_TIME_MSB, 10)); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_SWITCH, &ptc)); + TEST_ASSERT(ptc == 64); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_TIME_MSB, &ptc)); + TEST_ASSERT(ptc == 10); + + TEST_SUCCESS(fluid_synth_cc(synth, chan, ALL_CTRL_OFF, 0)); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_CTRL, &ptc)); + TEST_ASSERT(!fluid_channel_is_valid_note(ptc)); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_SWITCH, &ptc)); + TEST_ASSERT(ptc == 0); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_TIME_MSB, &ptc)); + TEST_ASSERT(ptc == 0); + + // Portamento is disabled + TEST_SUCCESS(fluid_synth_cc(synth, chan, PORTAMENTO_CTRL, 127)); + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_CTRL, &ptc)); + TEST_ASSERT(ptc == 127); + + TEST_SUCCESS(fluid_synth_cc(synth, chan, ALL_CTRL_OFF, 0)); + + // Because PTC is used for modulating, it should be reset to zero + TEST_SUCCESS(fluid_synth_get_cc(synth, chan, PORTAMENTO_CTRL, &ptc)); + TEST_ASSERT(!fluid_channel_is_valid_note(ptc)); +} + +// this test should make sure that sample rate changed are handled correctly +int main(void) +{ + int chan; + fluid_synth_t *synth; + fluid_settings_t *settings = new_fluid_settings(); + TEST_ASSERT(settings != NULL); + + synth = new_fluid_synth(settings); + TEST_ASSERT(synth != NULL); + TEST_SUCCESS(fluid_synth_sfload(synth, TEST_SOUNDFONT, 1)); + + for (chan = 0; chan < fluid_synth_count_midi_channels(synth); chan++) + { + const fluid_channel_t* channel = synth->channel[chan]; + if(channel->channel_type == CHANNEL_TYPE_DRUM) + { + // drum channels won't spawn voices + continue; + } + + test_portamento_fromkey(synth, chan); + fluid_synth_system_reset(synth); + + test_sustain(synth, chan); + fluid_synth_system_reset(synth); + + test_sostenuto(synth, chan); + fluid_synth_system_reset(synth); + } + + delete_fluid_synth(synth); + delete_fluid_settings(settings); + + return EXIT_SUCCESS; +}