Hi,
I created a plugin that loads Pure Data patches so I had to use several instances of Pure Data in a multi thread context. I managed to solve the problems using t_pdinstance and locks (so I needed to create my own wrapper like libpd). I think it would be great to integrate some of these improvements to libpd. Before sending a pull request or opening another issue on the repository about it, I thought that it would be better to present how I did this here.
I'm assuming that several instances can't run in parallel for 3 main reasons:
1 - The clocks can be created on the fly, so if the current instance is not the right one, the clocks can be integrated in a wrong list. This problem implies that all the methods that can create a clock (so potentially all the messages sent to Pure Data) and the sched_tick method must be called sequentially. I tried to find a solution ( https://sourceforge.net/p/pure-data/patches/559/) but there are always some specific cases that can’t be thread safe. The only real solution is (by a way or another) to set up the instance of the clock at its creation but this implies to change the interface of Pure Data and potentially (surely) a lot of externals would be deprecated.
2 - The vectors of audio input and output are global and the dsp_add method is global. So the DSP method must be called with the right instance. Here again, the only real solution would be to change the interface of Pure Data, to receive the instance in the dsp method and to add the perform methods to this instance. The dac and adc objects would also be able to retrieve the the vector of audio input and output specific to the instance.
3 - The MIDI methods are global. That implies that most the methods must be called sequentially if you don’t want to receive or send MIDI events to the wrong instance.
So the best solution is simply to use t_pdinstance. If the interface is opaque and before calling any method you set the instance and lock it, you should not have any problem. Basically, my interface is like this: First, initialize Pure Data and after you can call
- Create an instance (lock – create the instance – unlock). Each instance has its own vectors of audio input and output, MIDI methods, print method.
- Create a patch (lock, set the instance, create the patch, unlock).
- Prepare the dsp (lock, set the instance, reallocate the vectors of audio if the sample rate or the number of inputs or outputs changed, send dsp start message, unlock). In the DAWs like Reaper you must be able to have two instances running with two different sample rates.
- All the other methods are called like this:
- Lock and set the instance
- Send the MIDI events, send the messages, process the DSP
- Unlock
When you set the instance it also sets the current vectors of audio, the print method and the MIDI methods so the instance receives all the MIDI output event, output messages, posts, etc. It seems better to gather the methods for the messages, MIDI and DSP together. You can avoid this but in practise, I don’t see any reason.
The instance management is mostly in the c wrapper and the lock feature is in the C++ wrapper. Here the code: ( https://github.com/pierreguillot/Camomile/tree/master/Source/Pd). Most of the important stuff are in z_pd.c/.h (and PdInstance.cpp/.hpp). Of course, I have my own programming style, if you are interested I'll submit merge requests trying to respect the libPD programming style.
Cheers