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;
+}