Tutorials

Swappable Max Logic Using bpatchers

A question that comes up occasionally is the viability of switchable Max logic: Is it possible to ‘swap out’ portions of a patch, similar to the way a plug-in can be swapped into a DAW channel? There are a few ways to do this, but one useful way is to take advantage of bpatchers and their ability to change the patcher embedded within the object.

Switching Event Logic

Let’s begin with an example:

Logic Switcher.zip
application/zip 7.30 KB
Download the logic switcher patch used in this tutorial

Start the metro at the top of the patch to see the output of the bpatcher in the multislider. It creates a counter-based ramp from 0 to 127, just as you’d expect. Now, select "lsbp-random" from the umenu object and you’ll see that the contents of the bpatcher changes, with the output now tracking the random object output.

How it works

This trick is based on the magic of the thispatcher object. thispatcher is used to control the function of any patcher that contains it; it is the basis for scripting, which allows us to manipulate objects and connections within the patch.

Why do we need to use thispatcher and scripting? It’s because of how inlets to bpatchers work: a message sent into the inlet of a bpatcher is routed to the embedded patch, and is not used by the bpatcher itself. So we cannot simply send messages to the bpatcher; we have to have the Max scripting engine communicate for us.

Scripting is a complicated branch of Max programming, but we only have to scratch the surface of scripting to get our example to work properly. In this case, we use the script sendbox message to thispatcher, with the scripting name of the bpatcher (in this case, “bpat”), and a command to send to the bpatcher (“replace”). Finally, we provide the new patcher file name and trigger the message. This allows you to have different logic in each patcher file, giving us the ability to create swap-in content for any performance patch.

Making bpatcher files that work properly

There are a couple of rules that you will have to follow in order for this to work correctly. First of all, all of the bpatchers should have the same number of inlets and outlets. If you load a bpatcher file that has fewer inlets or outlets than your patch expects, the patch generates an error and disconnects patch cords from unavailable connections. You can see that all of the patches in our example have the same number of inlets and outlets.

Also, you will want to make sure that all of the bpatcher files are in the Max search path, or in the folder with the patcher that is using it. The file names also have to be unique within the search path. If you name one of your files “sinewave”, then create another patch called “sinewave” in the future, you cannot be sure that the proper file will always be loaded. So it is a good idea to create very unique patch names for your replacement patches – in our example, I used “lsbp-“ as a prefix to the filename to keep things unique.

Switching Audio Logic

An obvious question follows: Can this technique be used for audio, too? Let’s give it a try:

Audio Switcher.zip
application/zip 8.00 KB
Download the audio switching patches used in this tutorial

Start the system by turning on the ezdac~ and turning up the gain~ setting. You’ll hear a cosine wave ramping from 220 to 660 Hz. Now select a different patch from the menu, and you’ll hear the audio change along with the contents of the bpatcher.

In fact, there is really no difference in working with events or with audio. You need to maintain the inlet and outlet count, and the bpatcher files need to have unique names.

The asbp-pinknoise patch file does show something we didn’t see earlier: in this case, in order to maintain connections, we had to have an inlet that isn’t connected to anything!

This is a ‘dummy’ inlet that is only there to maintain the inlet on the bpatcher object, and to prevent any errors when replacing files.

Discontinuity?

Let's take a look at another example, where we embed more sophisticated logic into replaceable bpatcher files:

Audio Discontinuity.zip
application/zip 5.17 KB
Download the Audio Disconuity patch used in this tutorial

In this case, each of the bpatcher files has to load a file into a buffer~, and the asbp-dis2 file contains additional DSP for filtering. Since this logic can take some time to initialize, you might be concerned that there would be audio ‘glitches’ when you are switching files. However, even in this case (with file loads occurring), no glitch. Why not?

The crossfade preferences

The answer is found in Max’s preferences, under the ‘Mixer’ category.

The option to “Enable Mixer Crossfade” determines what happens when you make a change to the DSP chain of your patch. If this is set to “Off” (the default), your patch will quickly fade out, rebuild the DSP chain, then fade back in again. The speed of the fade is controlled by the “Mixer Crossfade Ramp Time” setting.

However, if you set “Enable Mixer Crossfade” to “On”, a little bit of magic happens. When you make a change to the DSP chain of the patch, audio is buffered for the duration of the “Mixer Crossfade Latency” setting. Then the new version of the patch is built, the DSP chain is built, then the system crossfades from the buffered audio into the patch's audio output. The crossfade time is controlled by the “Mixer Crossfade Ramp Time”, which (as you might guess) must be set lower than the “Mixer Crossfade Latency”.

If you plan on doing crossfade in a performance situation, tweaking these settings is critical to get snappy-feeling user interface while preventing audible glitches. But note: since you are working with buffered audio, high-CPU patches (for example, >50% CPU usage) may still glitch, since your system will not be able to rebuild the DSP chain before the buffer runs dry.

Using the bpatcher swap trick is a great way to build "plug-in" logic modules for your patches. Just follow the simple patch creation rules, and make your performance patches more easily extensible without having to edit the base patch. And remember, with bpatchers, you can also take advantage of presentation mode for unique UI’s for each embedded patch as well. This is a powerful tool for Max patch development, all thanks to the magical thispatcher object!

by Darwin Grosse on 2021年9月30日

Creative Commons License
Namakemon's icon

I read people saying Max MSP can't be used for livecoding due to pauses when you add objects (which is true, of course), seems that mixer crossfade renders that statement false (at least as blank statement )?

tmhglnd's icon

Nice examples! I personally prefer [poly~] for swapping out patchers since it does not show a user-interface and also gives the option to up/down sample and change vectorsize. @NAMAKEMON, true you can use the mixer crossfade to live code with max and hear less clicks, but it does take some tweaking, because in my experience if the dsp-chain compilation takes longer than the crossfade-latency you still hear clicks, and if the crossfade time is quite long but you quickly add object after object the crossfader can't keep up and you also hear clicks. Also using the mixer introduces latency, which I found not so useful when using live audio input.

Darwin Grosse's icon

@TMHGLND makes good points, but having individual UI's is actually one of the keys for this being useful. So that's why I tend to use this approach.

Your points about the crossfade time is spot-on, and should be considered part of the 'tweaking' to figure out if this could work for anyone's performance stuff. It really does depend on how complicated the replaced code is (which determines the amount of time it take to recompile the dsp chain).

Thanks for the thoughtful response!

redhexagonal's icon

Are there any problems swapping bpatchers within Max for Live devices?

Roman Thilenius's icon


Q: why is switching patches with bpatcher or poly~ better than with normal patchers?

A: because you do not need to rescript all the connections!

Q: but is it thread safe?

A: no it isnt. if you try to load 200 copies of the same patcher into a bpatcher or poly~ at the same moment, it can break.

Roman Thilenius's icon


@timo: how can the compilation take long than one vector?

tmhglnd's icon

I don't know how this works under the hood but in my past experience with big patches it can take a little time before the DSP is recalculated when adding/removing objects. I then had to make the mixer latency bigger otherwise there would still be a click because it seemed the crossfade started before the new dsp chain was ready to fade-in. I do however not use the parallel processing anymore, so maybe this experience is not representative for current max versions.

Roman Thilenius's icon


i think that is only the time it takes to load the patch and initialize it? if not, i´d like to learn about it.

in either case, one could delay the fade-in from within the subpatcher i think. (using dspstate?)

tmhglnd's icon

For me this experience also came from when patching and listening in real-time (for example trying to live-patch/live-code with Max), so it was not just loading a subpatcher but also inserting/adding objects.

Shakeeb Alireza's icon

Having built a decent sized Max project with 'hot-swappable' modularity builtin from the outset (see: https://github.com/shakfu/groovin ), one of the issues that has bit me and that isn't mentioned here is how to deal with MIDI mapping of ui elements in such 'modules'. The problem currently is that you can manually map the parameters to Midi events but if you 'swap' these are lost.

I would be interested to hear how others have overcome this problem.

S

david vilayleck's icon

hello i have been trying to redo the first logic switcher patch from scratch and i learned about bpatchers, added items in the inspector of the umenu but it doesn't work .. what am i doing wrong there ? thanks!

my LS.zip
application/zip 6.32 KB

Sapo's icon

Hi !

I'm making a MFL patch using swappable bpatchers but I noticed something not working as expected. The dynamically loaded bpatchers will not allow to automate UI elements inside it...
Only the ones contained in the bpatcher loaded when the device is saved will be automatable.
Is there something i'm missing or this is a known limitation ?
Is there a solution to make it work properly ?

Thanks !