Building an FX Routing System purely in Gen

Sym's icon

Hello, I'm wondering if anyone has some pointers in changing the order of different fx processes inside one gen routine.

Optimally I would like to not duplicate any processes but rather change the order in which they are send to/received from. The only way I currently see it "might be" possible is by creating some form of function that uses multiple History Objects to function as send and receive. So for routing between four FX inside the gen routine I would need to introduce a four sample overall delay.

Any tips would be highly appreciated! Thanks

Graham Wakefield's icon

Crap -- I gave a pretty lengthy response, and then accidentally deleted it by hitting the wrong button! Ok, trying again, will keep this short though.

3 options, different pros & cons.

1) Simple switches, with at minimum N-1 history objects for N fx. Latency can be kept pretty minimal on a preferred path. Doesn't scale well if you want many possible fx topologies. E.g;

2) Store intermediate results in a [data]. Great if you want to support many possible topologies, but N fx means N samples latency. Can be scaled up to a full-on matrix mixer. Simple example:

3) Use a codebox to control order of operation via if/else statements. Advantage here is that you can ensure zero latency, but the disadvantage is that the CPU cost is likely higher, both because of the general overhead of if/else (branch prediction/cache misses on the CPU) as well as a greater likelihood of duplication in the generated machine code. I would only recommend this option if the fx routines are very simple, and minimizing latency is the highest priority. Definitely not for memory intensive routines such as reverbs! Example code:

y = in1;
if (which) {
    y = fx1(y);
} else {
    y = fx2(y);
}
if (which) {
    y = fx2(y);
} else {
    y = fx1(y);
}
out1 = y;

Finally, I suggest some kind of anti-clicking (e.g. duck all fx inputs to zero and back over a few ms, applying the topology change when the duck is at zero).

Graham Wakefield's icon

Additional thoughts:
- for the [history] paths, if the fx is a [delay] and you don't need sub-sample delay times, you won't need the [history] as [delay @feedback 1] can also handle feedback paths.
- you could use some of these techniques to choose between multi-operator FM topologies.
- in some cases, it might be useful to crossfade rather than switch between algorithms. You could use [mix] instead of [switch] for example, or add @interp linear to [peek], but watch out for feedback!
- for some topologies you could provide additional tap outs, e.g. for fx1->fx2 you could have tap outs at both fx2 as well as fx1. Again, blending between these might be fun.

Sym's icon

Hey Graham, thanks for the extensive reply!

I have all my code running in a single Codebox instance, using require to import all relevant FX routines.

My current solution is basically what you suggested here:

y = in1;
if (which) {
    y = fx1(y);
} else {
    y = fx2(y);
}
if (which) {
    y = fx2(y);
} else {
    y = fx1(y);
}
out1 = y;

While this overall works well I have issues with some duplicated FX function instances. I noticed that some objects like history have strange behavior when used this way. For example when having an abstraction called "fx1reverb" loaded in both if statements above, there will be feedback loops happening, although the function should be only active in one of the two if statements.

The only way to fix this error for me is, to "wrap" these fx functions into another set of functions called "fx1reverb1" "fx1reverb2" at the moment and have them defined individually in each if statement.

Also in generally using the if method above, makes changing the order of modules not smooth and can cause clicks. I would definitely like to go down the route of having some form of history / mix setup that is used to drive the input/output logic of each FX, while having them only defined once and running through.

I think the example 2) you showed will be my best bet - I will do some more digging today and try to reproduce this in a codebox setup. Thanks a lot!