From b675a392ea998554729438ac3c911642793cf7cc Mon Sep 17 00:00:00 2001 From: Tom M Date: Fri, 10 Jun 2022 09:47:08 +0200 Subject: [PATCH 01/10] Fix CI builds after update (#1114) --- .azure/azure-pipelines-android.yml | 6 ++++++ .github/workflows/linux.yml | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.azure/azure-pipelines-android.yml b/.azure/azure-pipelines-android.yml index c289b1fd..ff24fab2 100644 --- a/.azure/azure-pipelines-android.yml +++ b/.azure/azure-pipelines-android.yml @@ -52,6 +52,7 @@ variables: # This is a symlink pointing to the real Android NDK # Must be the same as $ANDROID_NDK_HOME see: # https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md + # We cannot use $ANDROID_NDK_HOME because this is an environment variable, but here, we need a compile-time constant. NDK: '/usr/local/lib/android/sdk/ndk-bundle' # All the built binaries, libs and their headers will be installed here @@ -197,6 +198,11 @@ jobs: displayName: 'apt-get install' condition: and(succeeded(), ne(variables.CACHE_RESTORED, 'true')) + - script: | + set -ex + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT + displayName: 'Use NDK r22' + - script: | set -e diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9d7fc5fa..400d1fee 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -62,7 +62,6 @@ jobs: echo Can execute `nproc` make jobs in parallel cmake --version cmake -E make_directory ${{github.workspace}}/build - sudo ln -s /usr/bin/clang-tidy-10 /usr/bin/clang-tidy which clang-tidy || true ls -la `which clang-tidy` || true echo $PATH From 1b4b7ddbfe4852a7eccaa25fdd7c8d2c4a1cd9d2 Mon Sep 17 00:00:00 2001 From: Bill Peterson Date: Fri, 10 Jun 2022 03:49:30 -0500 Subject: [PATCH 02/10] alsa_seq pass system realtime messages --- src/drivers/fluid_alsa.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/drivers/fluid_alsa.c b/src/drivers/fluid_alsa.c index 6f930edf..ec35d9f6 100644 --- a/src/drivers/fluid_alsa.c +++ b/src/drivers/fluid_alsa.c @@ -1339,13 +1339,33 @@ fluid_alsa_seq_run(void *d) break; case SND_SEQ_EVENT_PORT_START: - { - if(dev->autoconn_inputs) { - fluid_alsa_seq_autoconnect_port(dev, seq_ev->data.addr.client, seq_ev->data.addr.port); + if(dev->autoconn_inputs) + { + fluid_alsa_seq_autoconnect_port(dev, seq_ev->data.addr.client, seq_ev->data.addr.port); + } } - } - break; + break; + + case SND_SEQ_EVENT_START: + evt.type = MIDI_START; + break; + + case SND_SEQ_EVENT_CONTINUE: + evt.type = MIDI_CONTINUE; + break; + + case SND_SEQ_EVENT_STOP: + evt.type = MIDI_STOP; + break; + + case SND_SEQ_EVENT_CLOCK: + evt.type = MIDI_SYNC; + break; + + case SND_SEQ_EVENT_RESET: + evt.type = MIDI_SYSTEM_RESET; + break; default: continue; /* unhandled event, next loop iteration */ From fe7fa3e309a11fba40a748cac09a432a6114db8b Mon Sep 17 00:00:00 2001 From: Bill Peterson Date: Fri, 10 Jun 2022 03:49:36 -0500 Subject: [PATCH 03/10] winmidi pass system realtime messages --- src/drivers/fluid_winmidi.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/drivers/fluid_winmidi.c b/src/drivers/fluid_winmidi.c index ed8bd84a..0a34d981 100644 --- a/src/drivers/fluid_winmidi.c +++ b/src/drivers/fluid_winmidi.c @@ -145,22 +145,29 @@ fluid_winmidi_callback(HMIDIIN hmi, UINT wMsg, DWORD_PTR dwInstance, break; case MIM_DATA: - event.type = msg_type(msg_param); - event.channel = msg_chan(msg_param) + dev_infos->channel_map; + if(msg_param < 0xF0) /* Voice category message */ + { + event.type = msg_type(msg_param); + event.channel = msg_chan(msg_param) + dev_infos->channel_map; - FLUID_LOG(FLUID_DBG, "\ndevice at index %d sending MIDI message on channel %d, forwarded on channel: %d", - dev_infos->dev_idx, msg_chan(msg_param), event.channel); + FLUID_LOG(FLUID_DBG, "\ndevice at index %d sending MIDI message on channel %d, forwarded on channel: %d", + dev_infos->dev_idx, msg_chan(msg_param), event.channel); - if(event.type != PITCH_BEND) - { - event.param1 = msg_p1(msg_param); - event.param2 = msg_p2(msg_param); - } - else /* Pitch bend is a 14 bit value */ - { - event.param1 = (msg_p2(msg_param) << 7) | msg_p1(msg_param); - event.param2 = 0; - } + if(event.type != PITCH_BEND) + { + event.param1 = msg_p1(msg_param); + event.param2 = msg_p2(msg_param); + } + else /* Pitch bend is a 14 bit value */ + { + event.param1 = (msg_p2(msg_param) << 7) | msg_p1(msg_param); + event.param2 = 0; + } + } + else /* System message */ + { + event.type = msg_param; + } (*dev->driver.handler)(dev->driver.data, &event); break; From ff362b52cddc8077712f12ca36e48961d5e5928d Mon Sep 17 00:00:00 2001 From: Bill Peterson Date: Fri, 10 Jun 2022 03:50:08 -0500 Subject: [PATCH 04/10] fluid_midi_parser_parse pass all realtime messages --- src/midi/fluid_midi.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/midi/fluid_midi.c b/src/midi/fluid_midi.c index 778a705b..7a27db63 100644 --- a/src/midi/fluid_midi.c +++ b/src/midi/fluid_midi.c @@ -2642,14 +2642,9 @@ fluid_midi_parser_parse(fluid_midi_parser_t *parser, unsigned char c) * of another message. */ if(c >= 0xF8) { - if(c == MIDI_SYSTEM_RESET) - { - parser->event.type = c; - parser->status = 0; /* clear the status */ - return &parser->event; - } - - return NULL; + parser->event.type = c; + parser->status = 0; /* clear the status */ + return &parser->event; } /* Status byte? - If previous message not yet complete, it is discarded (re-sync). */ From 3591299ea0a8b02875c5d46b2be33397ea0cafea Mon Sep 17 00:00:00 2001 From: derselbst Date: Sat, 11 Jun 2022 17:26:09 +0200 Subject: [PATCH 05/10] Format recent code changes --- src/drivers/fluid_alsa.c | 12 +++++------ src/drivers/fluid_winmidi.c | 42 ++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/drivers/fluid_alsa.c b/src/drivers/fluid_alsa.c index ec35d9f6..f47b24cd 100644 --- a/src/drivers/fluid_alsa.c +++ b/src/drivers/fluid_alsa.c @@ -553,7 +553,7 @@ static fluid_thread_return_t fluid_alsa_audio_run_s16(void *d) { FLUID_MEMSET(left, 0, buffer_size * sizeof(*left)); FLUID_MEMSET(right, 0, buffer_size * sizeof(*right)); - + (*dev->callback)(dev->data, buffer_size, 0, NULL, 2, handle); /* convert floating point data to 16 bit (with dithering) */ @@ -1339,13 +1339,13 @@ fluid_alsa_seq_run(void *d) break; case SND_SEQ_EVENT_PORT_START: + { + if(dev->autoconn_inputs) { - if(dev->autoconn_inputs) - { - fluid_alsa_seq_autoconnect_port(dev, seq_ev->data.addr.client, seq_ev->data.addr.port); - } + fluid_alsa_seq_autoconnect_port(dev, seq_ev->data.addr.client, seq_ev->data.addr.port); } - break; + } + break; case SND_SEQ_EVENT_START: evt.type = MIDI_START; diff --git a/src/drivers/fluid_winmidi.c b/src/drivers/fluid_winmidi.c index 0a34d981..d99323b2 100644 --- a/src/drivers/fluid_winmidi.c +++ b/src/drivers/fluid_winmidi.c @@ -145,29 +145,29 @@ fluid_winmidi_callback(HMIDIIN hmi, UINT wMsg, DWORD_PTR dwInstance, break; case MIM_DATA: - if(msg_param < 0xF0) /* Voice category message */ - { - event.type = msg_type(msg_param); - event.channel = msg_chan(msg_param) + dev_infos->channel_map; + if(msg_param < 0xF0) /* Voice category message */ + { + event.type = msg_type(msg_param); + event.channel = msg_chan(msg_param) + dev_infos->channel_map; - FLUID_LOG(FLUID_DBG, "\ndevice at index %d sending MIDI message on channel %d, forwarded on channel: %d", - dev_infos->dev_idx, msg_chan(msg_param), event.channel); + FLUID_LOG(FLUID_DBG, "\ndevice at index %d sending MIDI message on channel %d, forwarded on channel: %d", + dev_infos->dev_idx, msg_chan(msg_param), event.channel); - if(event.type != PITCH_BEND) - { - event.param1 = msg_p1(msg_param); - event.param2 = msg_p2(msg_param); - } - else /* Pitch bend is a 14 bit value */ - { - event.param1 = (msg_p2(msg_param) << 7) | msg_p1(msg_param); - event.param2 = 0; - } - } - else /* System message */ - { - event.type = msg_param; - } + if(event.type != PITCH_BEND) + { + event.param1 = msg_p1(msg_param); + event.param2 = msg_p2(msg_param); + } + else /* Pitch bend is a 14 bit value */ + { + event.param1 = (msg_p2(msg_param) << 7) | msg_p1(msg_param); + event.param2 = 0; + } + } + else /* System message */ + { + event.type = msg_param; + } (*dev->driver.handler)(dev->driver.data, &event); break; From 58ec0d8eb7320d634dceea7d295ac89cc82f55dd Mon Sep 17 00:00:00 2001 From: derselbst Date: Sat, 11 Jun 2022 17:45:50 +0200 Subject: [PATCH 06/10] Fix div by zero in fluid_player_set_tempo() --- src/midi/fluid_midi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/midi/fluid_midi.c b/src/midi/fluid_midi.c index 7a27db63..fa72327c 100644 --- a/src/midi/fluid_midi.c +++ b/src/midi/fluid_midi.c @@ -2336,6 +2336,12 @@ static void fluid_player_update_tempo(fluid_player_t *player) int tempo; /* tempo in micro seconds by quarter note */ float deltatime; + /* do nothing if the division is still unknown to avoid a div by zero */ + if(player->division == 0) + { + return; + } + if(fluid_atomic_int_get(&player->sync_mode)) { /* take internal tempo from MIDI file */ @@ -2400,6 +2406,10 @@ static void fluid_player_update_tempo(fluid_player_t *player) * #FLUID_PLAYER_TEMPO_INTERNAL will set the player to follow this internal * tempo. * + * @warning If the function is called when no MIDI file is loaded or currently playing, it + * would have caused a division by zero in fluidsynth 2.2.7 and earlier. Starting with 2.2.8, the + * new tempo change will be stashed and applied later. + * * @return #FLUID_OK if success or #FLUID_FAILED otherwise (incorrect parameters). * @since 2.2.0 */ From 3a3ed4f783d3a8df6d36edee73a07684304185c6 Mon Sep 17 00:00:00 2001 From: Tom M Date: Thu, 23 Jun 2022 20:32:29 +0200 Subject: [PATCH 07/10] Fix system-wide config file not loaded (#1121) --- src/fluidsynth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fluidsynth.c b/src/fluidsynth.c index ea383d79..beb917fd 100644 --- a/src/fluidsynth.c +++ b/src/fluidsynth.c @@ -891,13 +891,13 @@ int main(int argc, char **argv) if(config_file == NULL) { config_file = fluid_get_userconf(buf, sizeof(buf)); - if(config_file == NULL) + if(config_file == NULL || !g_file_test(config_file, G_FILE_TEST_EXISTS)) { config_file = fluid_get_sysconf(buf, sizeof(buf)); } /* if the automatically selected command file does not exist, do not even attempt to open it */ - if(!g_file_test(config_file, G_FILE_TEST_EXISTS)) + if(config_file != NULL && !g_file_test(config_file, G_FILE_TEST_EXISTS)) { config_file = NULL; } From a50680aafa449cdd17c25a40441bf93ae6f3793c Mon Sep 17 00:00:00 2001 From: Tom M Date: Mon, 27 Jun 2022 17:26:15 +0200 Subject: [PATCH 08/10] Add warnings and extend documentation for issue #1120 (#1126) --- doc/fluidsettings.xml | 2 +- include/fluidsynth/synth.h | 12 ++++++++++++ src/drivers/fluid_adriver.c | 15 ++++++++++++++- src/synth/fluid_synth.c | 3 ++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index f2bf8c72..3f4c79e2 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -418,7 +418,7 @@ and commit the results. Refresh with the following command: 64 8192 - The size of the audio buffers (in frames). + This is the number of audio samples most audio drivers will request from the synth at one time. In other words, it's the amount of samples the synth is allowed to render in one go when no state changes (events) are about to happen. Because of that, specifying too big numbers here may cause MIDI events to be poorly quantized (=untimed) when a MIDI driver or the synth's API directly is used, as fluidsynth cannot determine when those events are to arrive. This issue does not matter, when using the MIDI player or the MIDI sequencer, because in this case, fluidsynth does know when events will be received. diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index b8d0b0ab..8944e6a4 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -331,6 +331,18 @@ FLUIDSYNTH_API int fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int p * render real-time audio, ensure that you call these functions from a high-priority * thread with little to no other duties other than calling the rendering functions. * + * @warning + * If a concurrently running thread calls any other sound affecting synth function + * (e.g. fluid_synth_noteon(), fluid_synth_cc(), etc.) it is unspecified whether the event triggered by such a call + * will be effective in the recently synthesized audio. While this is inaudible when only requesting small chunks from the + * synth with every call (cf. fluid_synth_get_internal_bufsize()), it will become evident when requesting larger sample chunks: + * With larger sample chunks it will get harder for the synth to react on those spontaneously occurring events in time + * (like events received from a MIDI driver, or directly made synth API calls). + * In those real-time scenarios, prefer requesting smaller + * sample chunks from the synth with each call, to avoid poor quantization of your events in the synthesized audio. + * This issue is not applicable when using the MIDI player or sequencer for event dispatching. Also + * refer to the documentation of \setting{audio_period-size}. + * * @{ */ FLUIDSYNTH_API int fluid_synth_write_s16(fluid_synth_t *synth, int len, diff --git a/src/drivers/fluid_adriver.c b/src/drivers/fluid_adriver.c index 1de0380a..edc05f52 100644 --- a/src/drivers/fluid_adriver.c +++ b/src/drivers/fluid_adriver.c @@ -323,7 +323,20 @@ new_fluid_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) if(def) { - fluid_audio_driver_t *driver = (*def->new)(settings, synth); + fluid_audio_driver_t *driver; + double srate, midi_event_latency; + int period_size; + + fluid_settings_getint(settings, "audio.period-size", &period_size); + fluid_settings_getnum(settings, "synth.sample-rate", &srate); + + midi_event_latency = period_size / srate; + if(midi_event_latency >= 0.05) + { + FLUID_LOG(FLUID_WARN, "You have chosen 'audio.period-size' to be %d samples. Given a sample rate of %.1f this results in a latency of %.1f ms, which will cause MIDI events to be poorly quantized (=untimed) in the synthesized audio (also known as the 'drunken-drummer' syndrome). To avoid that, you're strongly advised to increase 'audio.periods' instead, while keeping 'audio.period-size' small enough to make this warning disappear.", period_size, srate, midi_event_latency*1000.0); + } + + driver = (*def->new)(settings, synth); if(driver) { diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index 68ce0965..7d058258 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -3717,7 +3717,8 @@ fluid_synth_get_active_voice_count(fluid_synth_t *synth) * @param synth FluidSynth instance * @return Internal buffer size in audio frames. * - * Audio is synthesized this number of frames at a time. Defaults to 64 frames. + * Audio is synthesized at this number of frames at a time. Defaults to 64 frames. I.e. the synth can only react to notes, + * control changes, and other audio affecting events after having processed 64 audio frames. */ int fluid_synth_get_internal_bufsize(fluid_synth_t *synth) From 9910b0dc9b060ae216f69651ea30137330cb642e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20L=C3=B3pez-Cabanillas?= Date: Sun, 26 Jun 2022 15:26:26 +0200 Subject: [PATCH 09/10] Adding audio.periods setting to the fluid_pulse audio driver This common setting was ignored by the PulseAudio driver. It doesn't change the synthesis buffer size, which is still the number of frames specified by audio.period-size, but the number of periods may be raised to request a higher latency amount, unless audio.pulseaudio.adjust-latency is 1 (the default). In turn, PulseAudio will grant or not this latency request. See: [PulseAudio Latency Control](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/LatencyControl/). --- src/drivers/fluid_pulse.c | 51 +++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/drivers/fluid_pulse.c b/src/drivers/fluid_pulse.c index f7a74dea..6cb3f622 100644 --- a/src/drivers/fluid_pulse.c +++ b/src/drivers/fluid_pulse.c @@ -83,12 +83,12 @@ new_fluid_pulse_audio_driver2(fluid_settings_t *settings, pa_sample_spec samplespec; pa_buffer_attr bufattr; double sample_rate; - int period_size, period_bytes, adjust_latency; + int period_size, period_bytes, adjust_latency, periods; char *server = NULL; char *device = NULL; char *media_role = NULL; int realtime_prio = 0; - int err; + int err = 0; float *left = NULL, *right = NULL, *buf = NULL; @@ -103,6 +103,7 @@ new_fluid_pulse_audio_driver2(fluid_settings_t *settings, FLUID_MEMSET(dev, 0, sizeof(fluid_pulse_audio_driver_t)); + fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_dupstr(settings, "audio.pulseaudio.server", &server); /* ++ alloc server string */ @@ -143,7 +144,7 @@ new_fluid_pulse_audio_driver2(fluid_settings_t *settings, samplespec.rate = sample_rate; period_bytes = period_size * sizeof(float) * 2; - bufattr.maxlength = adjust_latency ? -1 : period_bytes; + bufattr.maxlength = adjust_latency ? -1 : period_bytes * periods; bufattr.tlength = period_bytes; bufattr.minreq = -1; bufattr.prebuf = -1; /* Just initialize to same value as tlength */ @@ -155,6 +156,11 @@ new_fluid_pulse_audio_driver2(fluid_settings_t *settings, &bufattr, &err); + if(err != PA_OK) + { + FLUID_LOG(FLUID_ERR, "pa_simple_new() failed with error: %s", pa_strerror(err)); + } + if(!dev->pa_handle) { FLUID_LOG(FLUID_ERR, "Failed to create PulseAudio connection"); @@ -176,6 +182,7 @@ new_fluid_pulse_audio_driver2(fluid_settings_t *settings, } buf = FLUID_ARRAY(float, period_size * 2); + if(buf == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory."); @@ -242,10 +249,8 @@ fluid_pulse_audio_run(void *d) { fluid_pulse_audio_driver_t *dev = (fluid_pulse_audio_driver_t *) d; float *buf = dev->buf; - int buffer_size; - int err; - - buffer_size = dev->buffer_size; + int buffer_size = dev->buffer_size; + int err = 0; while(dev->cont) { @@ -254,9 +259,20 @@ fluid_pulse_audio_run(void *d) if(pa_simple_write(dev->pa_handle, buf, buffer_size * sizeof(float) * 2, &err) < 0) { - FLUID_LOG(FLUID_ERR, "Error writing to PulseAudio connection."); + FLUID_LOG(FLUID_ERR, "Error writing to PulseAudio connection: %s", pa_strerror(err)); break; } + +#if 0 + { + pa_usec_t pa_latency = pa_simple_get_latency(dev->pa_handle, &err); + + if(err == PA_OK) + { + FLUID_LOG(FLUID_DBG, "PulseAudio latency: %d ms", (int) pa_latency / 1000); + } + } +#endif } /* while (dev->cont) */ return FLUID_THREAD_RETURN_VALUE; @@ -271,12 +287,10 @@ fluid_pulse_audio_run2(void *d) *right = dev->right, *buf = dev->buf; float *handle[2]; - int buffer_size; - int err; + int buffer_size = dev->buffer_size; + int err = 0; int i; - buffer_size = dev->buffer_size; - handle[0] = left; handle[1] = right; @@ -297,9 +311,20 @@ fluid_pulse_audio_run2(void *d) if(pa_simple_write(dev->pa_handle, buf, buffer_size * sizeof(float) * 2, &err) < 0) { - FLUID_LOG(FLUID_ERR, "Error writing to PulseAudio connection."); + FLUID_LOG(FLUID_ERR, "Error writing to PulseAudio connection: %s", pa_strerror(err)); break; } + +#if 0 + { + pa_usec_t pa_latency = pa_simple_get_latency(dev->pa_handle, &err); + + if(err == PA_OK) + { + FLUID_LOG(FLUID_DBG, "PulseAudio latency: %d ms", (int) pa_latency / 1000); + } + } +#endif } /* while (dev->cont) */ return FLUID_THREAD_RETURN_VALUE; From 1f8c6a4633c7b9a24df9e63df2f5eb13aa9cf579 Mon Sep 17 00:00:00 2001 From: derselbst Date: Mon, 27 Jun 2022 17:39:10 +0200 Subject: [PATCH 10/10] Minor cleanup --- src/drivers/fluid_pulse.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/drivers/fluid_pulse.c b/src/drivers/fluid_pulse.c index 6cb3f622..b94f9b73 100644 --- a/src/drivers/fluid_pulse.c +++ b/src/drivers/fluid_pulse.c @@ -156,14 +156,9 @@ new_fluid_pulse_audio_driver2(fluid_settings_t *settings, &bufattr, &err); - if(err != PA_OK) + if(!dev->pa_handle || err != PA_OK) { - FLUID_LOG(FLUID_ERR, "pa_simple_new() failed with error: %s", pa_strerror(err)); - } - - if(!dev->pa_handle) - { - FLUID_LOG(FLUID_ERR, "Failed to create PulseAudio connection"); + FLUID_LOG(FLUID_ERR, "Failed to create PulseAudio connection, because pa_simple_new() failed with error: %s", pa_strerror(err)); goto error_recovery; }