FluidSynth / fluidsynth

Software synthesizer based on the SoundFont 2 specifications
https://www.fluidsynth.org
GNU Lesser General Public License v2.1
1.85k stars 257 forks source link

Jack thread terminates if synth.effects-channels and/or synth.audio-channels are used within a config file #724

Closed mchehab closed 3 years ago

mchehab commented 3 years ago

FluidSynth version

$ ./src/fluidsynth --version
FluidSynth runtime version 2.1.5
Copyright (C) 2000-2020 Peter Hanappe and others.
Distributed under the LGPL license.
SoundFont(R) is a registered trademark of Creative Technology Ltd.

FluidSynth executable version 2.1.5
Sample type=double

Describe the bug

When either one of the options bellow:

set synth.audio-channels 6
set synth.effects-channels 6

Are used within a config file, the Jack Kthread terminates, causing fluidsynth to silently crash. When this bug happens, it is not possible anymore to manually adjust the connection graph with qjackctl, or to use fluidsynth.

This bug also affects qsynth, making impossible to use it with more than 2 stereo channel outputs.

Expected behavior

Fluidsynth should be able to accept changing the number of audio and effect channels via configuration settings.

Steps to reproduce

Create a config file with:

set audio.jack.multi true
set audio.jack.autoconnect true
set synth.audio-channels 6
set synth.effects-channels 6

Call fluidsynth -v -f config. The result is:

FluidSynth runtime version 2.1.5
Copyright (C) 2000-2020 Peter Hanappe and others.
Distributed under the LGPL license.
SoundFont(R) is a registered trademark of E-mu Systems, Inc.

fluidsynth: debug: Using 'alsa_seq' midi driver
Warning: 'audio.jack.multi' is not a realtime setting, changes won't take effect.
Warning: 'audio.jack.autoconnect' is not a realtime setting, changes won't take effect.
Warning: 'synth.audio-channels' is not a realtime setting, changes won't take effect.
fluidsynth: error: requested set value for setting 'synth.effects-channels' out of range
set: Value out of range. Try 'info synth.effects-channels' for valid ranges
Warning: 'synth.effects-channels' is not a realtime setting, changes won't take effect.
fluidsynth: Received EOF while reading commands, exiting the shell.
Failed to execute user provided command configuration file 'config'
fluidsynth: debug: Using 'jack' audio driver
Jack: JackClient::SetupDriverSync driver sem in flush mode
Jack: JackLinuxFutex::Connect name = jack_sem.1000_default_fluidsynth
Jack: Clock source : system clock via clock_gettime
Jack: JackLibClient::Open name = fluidsynth refnum = 4
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_00 type = 32 bit float mono audio port_index = 53
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_00 type = 32 bit float mono audio port_index = 54
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_01 type = 32 bit float mono audio port_index = 55
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_01 type = 32 bit float mono audio port_index = 56
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_02 type = 32 bit float mono audio port_index = 57
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_02 type = 32 bit float mono audio port_index = 58
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_03 type = 32 bit float mono audio port_index = 59
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_03 type = 32 bit float mono audio port_index = 60
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_04 type = 32 bit float mono audio port_index = 61
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_04 type = 32 bit float mono audio port_index = 62
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_05 type = 32 bit float mono audio port_index = 63
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_05 type = 32 bit float mono audio port_index = 64
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_l_00 type = 32 bit float mono audio port_index = 65
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_r_00 type = 32 bit float mono audio port_index = 66
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_l_01 type = 32 bit float mono audio port_index = 67
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_r_01 type = 32 bit float mono audio port_index = 68
fluidsynth: debug: Jack engine sample rate: 48000
fluidsynth: Jack sample rate mismatch, adjusting. (synth.sample-rate=44100, jackd=48000)
Jack: JackClient::Activate
Jack: JackPosixThread::StartImp : create non RT thread
Jack: JackPosixThread::ThreadHandler : start
Jack: JackClient::kBufferSizeCallback buffer_size = 512
Jack: JackClient::Init : period = 10666 computation = 100 constraint = 10666
Jack: JackPosixThread::AcquireRealTimeImp priority = 15
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 2
Jack: JackClient::kActivateClient name = fluidsynth ref = 4 
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 53
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 54
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 55
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 56
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 57
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 58
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 59
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 60
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 61
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 62
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 63
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 64
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 65
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 66
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 67
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 68
Jack: JackClient::Connect src = fluidsynth:l_00 dst = system:playback_1
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::Connect src = fluidsynth:r_00 dst = system:playback_2
Jack: JackClient::Connect src = fluidsynth:l_01 dst = system:playback_3
Jack: JackClient::Connect src = fluidsynth:r_01 dst = system:playback_4
Jack: JackClient::Connect src = fluidsynth:l_02 dst = system:playback_5
Jack: JackClient::Connect src = fluidsynth:r_02 dst = system:playback_6
Jack: JackClient::Connect src = fluidsynth:l_03 dst = system:playback_7
Jack: JackClient::Connect src = fluidsynth:r_03 dst = system:playback_8
Jack: JackClient::Connect src = fluidsynth:l_04 dst = system:playback_9
Jack: JackClient::Connect src = fluidsynth:r_04 dst = system:playback_10
Jack: JackClient::Connect src = fluidsynth:l_05 dst = system:playback_11
Jack: JackClient::Connect src = fluidsynth:r_05 dst = system:playback_12
Jack: JackClient::Connect src = fluidsynth:fx_l_00 dst = system:playback_1
Jack: JackClient::Connect src = fluidsynth:fx_r_00 dst = system:playback_2
Jack: JackClient::Connect src = fluidsynth:fx_l_01 dst = system:playback_3
Jack: JackClient::Connect src = fluidsynth:fx_r_01 dst = system:playback_4
Type 'help' for help topics.

> Jack: JackClient::Execute end name = fluidsynth
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 10
Jack: JackPosixThread::Terminate

Additional context

The problem is related to the callback call at: fluid_jack_driver_process() function, as this quick hack prevents Jack thread to stop:

diff --git a/src/drivers/fluid_jack.c b/src/drivers/fluid_jack.c
index 18495dfe010b..bcc1e32165ed 100644
--- a/src/drivers/fluid_jack.c
+++ b/src/drivers/fluid_jack.c
@@ -689,7 +689,7 @@ fluid_jack_driver_process(jack_nframes_t nframes, void *arg)
     fluid_jack_audio_driver_t *audio_driver;
     fluid_jack_midi_driver_t *midi_driver;
     float *left, *right;
-    int i;
+    int i, ret;

     jack_midi_event_t midi_event;
     fluid_midi_event_t *evt;
@@ -776,12 +776,15 @@ fluid_jack_driver_process(jack_nframes_t nframes, void *arg)
             FLUID_MEMSET(audio_driver->fx_bufs[k], 0, nframes * sizeof(float));
         }

-        return callback(audio_driver->data,
+        ret = callback(audio_driver->data,
                         nframes,
                         audio_driver->num_fx_ports * 2,
                         audio_driver->fx_bufs,
                         audio_driver->num_output_ports * 2,
                         audio_driver->output_bufs);
+        if (ret)
+            FLUID_LOG(FLUID_DBG, "%s: callback returning %d", __FUNCTION__, ret);
+   return 0;
     }
 }

With this quick hack, fluidsynth now returns:

$ ./src/fluidsynth -v -f config
FluidSynth runtime version 2.1.5
Copyright (C) 2000-2020 Peter Hanappe and others.
Distributed under the LGPL license.
SoundFont(R) is a registered trademark of Creative Technology Ltd.

fluidsynth: debug: Using 'alsa_seq' midi driver
Warning: 'audio.jack.multi' is not a realtime setting, changes won't take effect.
Warning: 'audio.jack.autoconnect' is not a realtime setting, changes won't take effect.
Warning: 'synth.audio-channels' is not a realtime setting, changes won't take effect.
fluidsynth: error: requested set value for setting 'synth.effects-channels' out of range
set: Value out of range. Try 'info synth.effects-channels' for valid ranges
Warning: 'synth.effects-channels' is not a realtime setting, changes won't take effect.
fluidsynth: Received EOF while reading commands, exiting the shell.
Failed to execute user provided command configuration file 'config'
fluidsynth: debug: Using 'jack' audio driver
Jack: JackClient::SetupDriverSync driver sem in flush mode
Jack: JackLinuxFutex::Connect name = jack_sem.1000_default_fluidsynth
Jack: Clock source : system clock via clock_gettime
Jack: JackLibClient::Open name = fluidsynth refnum = 4
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_00 type = 32 bit float mono audio port_index = 53
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_00 type = 32 bit float mono audio port_index = 54
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_01 type = 32 bit float mono audio port_index = 55
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_01 type = 32 bit float mono audio port_index = 56
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_02 type = 32 bit float mono audio port_index = 57
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_02 type = 32 bit float mono audio port_index = 58
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_03 type = 32 bit float mono audio port_index = 59
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_03 type = 32 bit float mono audio port_index = 60
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_04 type = 32 bit float mono audio port_index = 61
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_04 type = 32 bit float mono audio port_index = 62
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:l_05 type = 32 bit float mono audio port_index = 63
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:r_05 type = 32 bit float mono audio port_index = 64
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_l_00 type = 32 bit float mono audio port_index = 65
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_r_00 type = 32 bit float mono audio port_index = 66
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_l_01 type = 32 bit float mono audio port_index = 67
Jack: JackClient::PortRegister ref = 4 name = fluidsynth:fx_r_01 type = 32 bit float mono audio port_index = 68
fluidsynth: debug: Jack engine sample rate: 48000
fluidsynth: Jack sample rate mismatch, adjusting. (synth.sample-rate=44100, jackd=48000)
Jack: JackClient::Activate
Jack: JackPosixThread::StartImp : create non RT thread
Jack: JackPosixThread::ThreadHandler : start
Jack: JackClient::kBufferSizeCallback buffer_size = 512
Jack: JackClient::Init : period = 10666 computation = 100 constraint = 10666
Jack: JackPosixThread::AcquireRealTimeImp priority = 15
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 2
Jack: JackClient::kActivateClient name = fluidsynth ref = 4 
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 53
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 54
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 55
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 56
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 57
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 58
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 59
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 60
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 61
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 62
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 63
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 64
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 65
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 66
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 67
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 9
Jack: JackClient::kPortRegistrationOn port_index = 68
Jack: JackClient::Connect src = fluidsynth:l_00 dst = system:playback_1
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::Connect src = fluidsynth:r_00 dst = system:playback_2
Jack: JackClient::Connect src = fluidsynth:l_01 dst = system:playback_3
Jack: JackClient::Connect src = fluidsynth:r_01 dst = system:playback_4
Jack: JackClient::Connect src = fluidsynth:l_02 dst = system:playback_5
Jack: JackClient::Connect src = fluidsynth:r_02 dst = system:playback_6
Jack: JackClient::Connect src = fluidsynth:l_03 dst = system:playback_7
Jack: JackClient::Connect src = fluidsynth:r_03 dst = system:playback_8
Jack: JackClient::Connect src = fluidsynth:l_04 dst = system:playback_9
Jack: JackClient::Connect src = fluidsynth:r_04 dst = system:playback_10
Jack: JackClient::Connect src = fluidsynth:l_05 dst = system:playback_11
Jack: JackClient::Connect src = fluidsynth:r_05 dst = system:playback_12
Jack: JackClient::Connect src = fluidsynth:fx_l_00 dst = system:playback_1
Jack: JackClient::Connect src = fluidsynth:fx_r_00 dst = system:playback_2
Jack: JackClient::Connect src = fluidsynth:fx_l_01 dst = system:playback_3
Jack: JackClient::Connect src = fluidsynth:fx_r_01 dst = system:playback_4
Type 'help' for help topics.

> fluidsynth: debug: fluid_jack_driver_process: callback returning -1
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
Jack: JackClient::ClientNotify ref = 4 name = fluidsynth notify = 18
fluidsynth: debug: fluid_jack_driver_process: callback returning -1
fluidsynth: debug: fluid_jack_driver_process: callback returning -1
fluidsynth: debug: fluid_jack_driver_process: callback returning -1
...
fluidsynth: debug: fluid_jack_driver_process: callback returning -1
derselbst commented 3 years ago

You may only place set commands in a config file for those settings that are real-time settings. All the settings you've used are non-realtime. Hence the synth gets created with the default values for those settings. Then, your config file modifies them. Then, it creates the audio-driver with those modified values. fluid_synth_process() detects a mismatch in the number of audio-channels and returns -1, which we in turn return to jack, and jack kicks us out of the process loop.

Expected behavior Fluidsynth should be able to accept changing the number of audio and effect channels via configuration settings.

No. It's not supported and fluidsynth is telling you:

Warning: 'audio.jack.multi' is not a realtime setting, changes won't take effect.
Warning: 'audio.jack.autoconnect' is not a realtime setting, changes won't take effect.
Warning: 'synth.audio-channels' is not a realtime setting, changes won't take effect.
fluidsynth: error: requested set value for setting 'synth.effects-channels' out of range
set: Value out of range. Try 'info synth.effects-channels' for valid ranges
Warning: 'synth.effects-channels' is not a realtime setting, changes won't take effect.

This bug also affects qsynth, making impossible to use it with more than 2 stereo channel outputs.

It's not impossible. Just use the dedicated commandline flags, either -L 6 or -o synth.audio-channels=6.

causing fluidsynth to silently crash

Where is that "crash" here? Jack only kicks us out of its processing loop and fluidsynth remains usable - at least for me. I agree, we should provide better user feedback in this case. But I don't see anything else we can do here.

mchehab commented 3 years ago

You may only place set commands in a config file for those settings that are real-time settings. All the settings you've used are non-realtime. Hence the synth gets created with the default values for those settings. Then, your config file modifies them. Then, it creates the audio-driver with those modified values. fluid_synth_process() detects a mismatch in the number of audio-channels and returns -1, which we in turn return to jack, and jack kicks us out of the process loop.

Yes, that's the behaviour I detected.

No. It's not supported and fluidsynth is telling you:

Warning: 'audio.jack.multi' is not a realtime setting, changes won't take effect.
Warning: 'audio.jack.autoconnect' is not a realtime setting, changes won't take effect.
Warning: 'synth.audio-channels' is not a realtime setting, changes won't take effect.
fluidsynth: error: requested set value for setting 'synth.effects-channels' out of range
set: Value out of range. Try 'info synth.effects-channels' for valid ranges
Warning: 'synth.effects-channels' is not a realtime setting, changes won't take effect.

This bug also affects qsynth, making impossible to use it with more than 2 stereo channel outputs.

It's not impossible. Just use the dedicated commandline flags, either -L 6 or -o synth.audio-channels=6.

Yeah, using fluidsynth command line works, but I didn't find any way to do something like that using qsynth frontend. On a quick look on its source, it sounded to me that it is using fluidsynth API calls in order to setup the number of channels, but it is probably passing the number of audio/effect channels too late.

Probably qsynth is doing something similar to what fluidsynth.c does. At fluidsynth.c it first initializes the synthetizer, then it parses the config file, which is too late for "synth." settings.

causing fluidsynth to silently crash

Where is that "crash" here? Jack only kicks us out of its processing loop and fluidsynth remains usable - at least for me. I agree, we should provide better user feedback in this case. But I don't see anything else we can do here.

It is a "crash" in the sense that synth GUIs like qsynth think that fluidsynth is properly running, while the Jack output thread has long gone. So, if one sets the number of audio channels to, let's say, 6, it pretends that everything is working.

Anyway, I just added a PR that should be detecting this problem in advance and printing some warnings: https://github.com/FluidSynth/fluidsynth/pull/725

Yet, IMHO, the best would be to let the config file to be able of setting those parameters, either parsing the set commands from it before calling:

synth = new_fluid_synth(settings);

Or by adding an update method that would allow to dynamically change the number of audio channels.

derselbst commented 3 years ago

Yeah, using fluidsynth command line works, but I didn't find any way to do something like that using qsynth frontend.

In qsynth you just click on Setup > Audio. In this dialog, you can set up the number of channels accordingly. After closing the dialog, qsynth destroys the synth and audio-driver and recreates them.

Or by adding an update method that would allow to dynamically change the number of audio channels.

Setting up audio channels requires memory allocation. There is no way we can do this on-the-fly when the synth is already running. It would interfere with concurrent rendering calls. That's impossible.

either parsing the set commands from it before calling

Possible, theoretically. One would need to parse the file twice (which is ugly), break the existing API by uncluttering the command handler from the objects it requires for its creation while taking huge care to not break any existing functionality. Incredibly time-consuming, and not very beneficial in the end.

mchehab commented 3 years ago

Yeah, using fluidsynth command line works, but I didn't find any way to do something like that using qsynth frontend.

In qsynth you just click on Setup > Audio. In this dialog, you can set up the number of channels accordingly. After closing the dialog, qsynth destroys the synth and audio-driver and recreates them.

I'm currently debugging the issue there. I was assuming that this would be due to the same issue as reported on this issue, , but it is a different one. It seems that there are two problems at qsynth:

  1. It always try to create just 2 effect groups, even when audio channels is bigger than 2;
  2. it doesn't work when the number of audio channels is > 1 and it is an odd number.

So, The net effect is that, if the number of audio channels is bigger than 2, it gets a Jack terminate message (only displayed on its message log window):

Jack: JackPosixThread::Terminate

Such problems initially seemed to be related to fluid_synth_process(), but calling fluidsynth command line interface works. So, it could be due to some unrelated issue - or perhaps due to the lack of some validation either at qsynth or at the API function calls.

Or by adding an update method that would allow to dynamically change the number of audio channels.

Setting up audio channels requires memory allocation. There is no way we can do this on-the-fly when the synth is already running. It would interfere with concurrent rendering calls. That's impossible.

True, but, it would be possible to re-size the buffer when the synth is idle.

Btw, in the specific case of set commands from config files, the problem is that the parsing order is racy. What it does is:

  1. the synth is created (and a few other modules like the router are initialized);
  2. the config file is parsed;
  3. the output driver instance is created, which will use the newer settings.

If, at step (2), any command change the non-realtime settings from fluid_synth, things break.

No idea how easy would be to implement it, but a possible approach would be to split new_fluid_synth() into 2 separate functions: the first one that would be just doing a minimal set of configurations that would make fluid_cmd.c happy, and a new function would be doing the settings-dependent setup.

either parsing the set commands from it before calling

Possible, theoretically.

One would need to parse the file twice (which is ugly),

IMHO, passing such settings via command line args is a lot uglier :-) :

fluidsynth -jsv -L6 -o "audio.jack.multi=true" -o "audio.driver=jack" \
 -o "audio.file.format=s24" -o "audio.periods=2" -o "audio.period-size=256" \
 -o "audio.realtime-prio=99" -o "audio.sample-format=24bits" -o "audio.jack.multi=true" \
 -o "midi.alsa_seq.id=Synth" -o "audio.jack.id=Synth" -o "midi.jack.id=Synth-midi" \
 -o "midi.driver=alsa_seq" -o "midi.realtime-prio=99" -o "shell.port=9800" \
 -o "synth.effects-groups=3" -o "synth.cpu-cores=4" -o "synth.midi-bank-select='gs'" \
 -o "synth.sample-rate=48000" \
 '/usr/share/soundfonts/Live HQ Natural SoundFont GM.sf2' \
 '/usr/share/soundfonts/Chateau Grand-v1.8.sf2' \
 /usr/share/soundfonts/SGM-v2.01-YamahaGrand-Guit-Bass-v2.7.sf2 \
 /usr/share/sounds/sf2/FluidR3_GM.sf2 /usr/share/sounds/sf2/FluidR3_GS.sf2

break the existing API by uncluttering the command handler from the objects it requires for its creation while taking huge care to not break any existing functionality. Incredibly time-consuming, and not very beneficial in the end.

I see the point.

derselbst commented 3 years ago

Or by adding an update method that would allow to dynamically change the number of audio channels.

Setting up audio channels requires memory allocation. There is no way we can do this on-the-fly when the synth is already running. It would interfere with concurrent rendering calls. That's impossible.

True, but, it would be possible to re-size the buffer when the synth is idle.

The synth is stateless. It is always "ready-to-render". If you run the fluidsynth executable via cmdline, the synth always renders silence, so it's always busy (as soon as the audio-driver is created). Even if there were an "idle" state and you decide to tear down the channels and re-create them, the synth may start rendering again at any time, even if you are still busy doing this kind of "construction works". One would need synchronization here, and this means the synth would not be real-time safe anymore. So no, it's not possible.

Btw, in the specific case of set commands from config files, the problem is that the parsing order is racy.

Yes, that's clear to me. But it's the best order we can get without breaking other things. We've been through this pain several times already.

the first one that would be just doing a minimal set of configurations that would make fluid_cmd.c happy

That "minimal set" is a fully configured synth. You can put note and CC events in a configuration file as well, configure MIDI router rules, etc. So a half-complete-throw-away-synth is not sufficient.

IMHO, passing such settings via command line args is a lot uglier :-)

Well... that's the idea of UIs like qsynth. Alternatively, write that commandline into a shell file or use an alias. The soundfonts can be safely placed into the config file btw.

derselbst commented 3 years ago

but a possible approach would be to split new_fluid_synth() into 2 separate functions: the first one that would be just doing a minimal set of configurations that would make fluid_cmd.c happy

Your idea of splitting new_fluid_synth() (our core-function) has distracted me from your actual idea. So, coming back to your thought, here is an idea:

In fluidsynth.c right after creating the fluid_settings_t object and before creating any other object, we create a fluid_cmd_handler, which only has settings-related commands registered:

extern fluid_cmd_handler_t *new_fluid_cmd_handler2(fluid_synth_t *synth, fluid_midi_router_t *router, fluid_settings_t* settings);

preliminary_handler = new_fluid_cmd_handler2(NULL, NULL, settings);
// parse all set commands:
fluid_source(preliminary_handler, cfg_file);
// continue initializing...
the_final_real_handler = new_fluid_cmd_handler(synth, router);
// parse the entire file again, even the set commands (they won't have changed since the parsing from above)
fluid_source(the_final_real_handler, cfg_file);
// do rest of fluidsynth initialization...

One would only need to add this new function new_fluid_cmd_handler2, make it a bit smarter than the existing new_fluid_cmd_handler in terms of "which commands to register if objects were NULL" and replace the handler->synth->settings things with direct access to the settings object passed in e.g. handler->settings.

This should be super simple to do and quick as well. You're welcome to draft a PR for that. Currently, I have a few other points on my agenda.

mchehab commented 3 years ago

One would only need to add this new function new_fluid_cmd_handler2, make it a bit smarter than the existing new_fluid_cmd_handler in terms of "which commands to register if objects were NULL" and replace the handler->synth->settings things with direct access to the settings object passed in e.g. handler->settings.

This should be super simple to do and quick as well. You're welcome to draft a PR for that. Currently, I have a few other points on my agenda.

Yeah, I can work on something like that. In a matter of fact, I did an experiment like that earlier today:

https://github.com/mchehab/fluidsynth/commit/a43e2e4d08942580d7a573bd08608c9dc840d624

There is one issue on this hack, that you mentioned: it needs to be a little bit smarter, in order to avoid creating some symbols:

fluidsynth: error: Failed to register numeric setting 'synth.reverb.room-size' as it already exists with a different type
fluidsynth: error: Failed to register numeric setting 'synth.reverb.damp' as it already exists with a different type
fluidsynth: error: Failed to register numeric setting 'synth.reverb.width' as it already exists with a different type
fluidsynth: error: Failed to register numeric setting 'synth.reverb.level' as it already exists with a different type
fluidsynth: error: Failed to register numeric setting 'synth.chorus.level' as it already exists with a different type
fluidsynth: error: Failed to register numeric setting 'synth.chorus.speed' as it already exists with a different type
fluidsynth: error: Failed to register numeric setting 'synth.chorus.depth' as it already exists with a different type
fluidsynth: error: Failed to register string setting 'midi.portname' as it already exists with a different type
fluidsynth: error: Failed to register string setting 'synth.default-soundfont' as it already exists with a different type

I suspect it shouldn't be hard to change this hack to the approach you suggested.


On an OOT comment, this was what I cooked in order to fix multi-channel support with Jack on Qsynth:

https://github.com/mchehab/qsynth/commit/8f7dabc4931c41174990dd4bec67d20716b8b3e0

mchehab commented 3 years ago

One would only need to add this new function new_fluid_cmd_handler2, make it a bit smarter than the existing new_fluid_cmd_handler in terms of "which commands to register if objects were NULL" and replace the handler->synth->settings things with direct access to the settings object passed in e.g. handler->settings. This should be super simple to do and quick as well. You're welcome to draft a PR for that. Currently, I have a few other points on my agenda.

Yeah, I can work on something like that. In a matter of fact, I did an experiment like that earlier today:

mchehab@a43e2e4

Added a patch series solving it at PR #737.