Middle Layer Signal Switching

This partial DSP graph is a construct I find myself using over and over. It is used extensively in Accents. For example in the Voltage Vault unit for the bypass switch, in Logics & Maths for switching between the different algorithms, and probably many other places.

I thought I’d document it for myself so I don’t have to re-understand it each time, and also for anyone who it might help in their middle or UI layer endeavors.

In the example below, we are switching between sending the unit input, and a signal called mySignal to the unit output by engaging or disengaging a toggle control (comparator) called switch. It should be relatively easy to adapt this concept to switch between any two signals, and also to use other mechanisms to switch between them - for example an item in the menu which performs a hardSet operation.

Switch (Comparator) is off (zero) - mySignal sent to unit output
The invertingVCA multiplies the output of switch (0) by negOne (-1) for a result of 0. This is sent to switchSum, which adds one (1) to it for a result of 1. This is sent to mySignalToOutVCA and multiplied by mySignal. mySignal x 1 = mySignal. Finally this is sent to outputMixer, and then to self,“Out”. The result is that mySignal comes out of the unit output.

Following the other path for self,“In”, it is fed to inputToOutVCA where it is multiplied by switch, who’s value is zero, resulting in silence sent to the outputMixer and to self,“Out”. The unit input signal is silenced (0) in the outputMixer.

Switch (Comparator) is on (one) - unit input sent to unit output
The invertingVCA multiplies the output of switch (1) by negOne (-1) for a result of -1. This is sent to switchSum, which adds one (1) to it for a result of 0. This is sent to mySignalToOutVCA and multiplied by mySignal which results in zero (silence mySignal). Finally this silent signal is sent to outputMixer, and then to self,“Out”. The result is that mySignal is not heard on the unit output.

Following the other path for self,“In”, it is fed to inputToOutVCA where it is multiplied by switch, who’s value is 1, resulting in mySignal appearing on the outputMixer and to self,“Out”. The unit input signal is sent to the unit output.

local inputToOutVCA = self:addObject("inputToOutVCA",app.Multiply())
local mySignalToOutVCA = self:addObject("mySignalToOutVCA",app.Multiply())
local outputMixer = self:addObject("outputMixer",app.Sum())
local switch = self:addObject("switch",app.Comparator())
local invertingVCA = self:addObject("invertingVCA",app.Multiply())
local negOne = self:addObject("negOne",app.ConstantOffset())
local one = self:addObject("one",app.ConstantOffset())
local switchSum = self:addObject("switchSum",app.Sum())
switch:setToggleMode()
negOne:hardSet("Offset",-1.0)
one:hardSet("Offset",1.0)

self:addMonoBranch("switch", switch, "In", switch, "Out")

connect(mySignal,"Out",mySignalToOutVCA,"Left")
connect(negOne,"Out",invertingVCA,"Left")
connect(switch,"Out",invertingVCA,"Right")
connect(invertingVCA,"Out",switchSum,"Left")
connect(one,"Out",switchSum,"Right")
connect(switchSum,"Out",mySignalToOutVCA,"Right")
connect(mySignalToOutVCA,"Out",outputMixer,"Left")
connect(self,"In1",inputToOutVCA,"Left") --
connect(switch,"Out",inputToOutVCA,"Right")
connect(inputToOutVCA,"Out",outputMixer,"Right")
connect(outputMixer,"Out",self,"Out1")

-- ...

controls.switch = Gate {
    button = "switch",
    description = "switch",
    branch = branches.switch,
    comparator = objects.switch,
  }

@odevices what do you think about a tag called “development”?

4 Likes

This is exactly what the Crossfade object does and it will be very efficient :nerd_face:

Out = w*In1 + (1-w)*In2
w is in [0, 1]

Where w is the switching signal (or fade).

2 Likes

Looks like I’ve re-invented the wheel then. :thinking:

Well, here’s to an efficiency gain and cleaner code, in that case! It was a good mental exercise.

3 Likes

Reinventing the wheel is one of the most solid learning tools that I’ve ever encountered. Whenever I’ve ended up reinventing the wheel, I come out of the experience with confidence that I can get to the right answer with little or no guidance.

4 Likes

Oh! I just remembered that the Crossfade object is currently setup to approximate a constant power cosine fade by mapping the switching signal through an inverted parabola.

Out = u * In1 + (1 - u) * In2
u = w * (2 - w)
w is in [0, 1]

So it can be made even more efficient by simplifying to the linear crossfade posted before.

1 Like

So you are saying a straightforward linear crossfade exists in core? Or to create a custom c++ object for that?

Btw @Joe I’m working on some DSP layer logic gates to make these kind of things easier in the Middle Layer (and above). If you can imagine Sloop has this kind of boolean logic all over the place to try and count things correctly, here’s the WIP sources.

So far I want to include: Logical and, or, not, trig (basically !!)
And some memory tools as well latch, dlatch (basically sample and hold), register (simulate a series of dlatches)

Edit: I noticed because of the logical not in your diagram btw, I’ve written that a million times using a gain bias ha (x * -1 + 1)

1 Like

Oh, wow, fantastic idea, @tomf ! I thought this topic was going to be just a bit of info sharing. Turned out quite differently, but I’m glad I posted it. I like this idea of making simpler/cleaner solutions before we all do the same (harder/less efficient) things over and over again.

PS - from your WIP code, seems like you have a pretty good handle on the neon stuff. I’m still looking at it like a deer in the headlights. :slight_smile:

I’m getting better at it, my approach is to write something normally in C++ and then think about how I can group repeated operations.

It’s a lot of going back and forth to the intrinsics reference online, it doesn’t always have the exact operation you might think of. For example, the not implementation is basically 0 || !(x > 0) since the intrinsics has an “or not” function vornq_u32 .

Of course feel free to completely copy any of these.

Edit2: This pattern comes up a lot as well. Aka do a bunch of operations (compares) then quickly iterate over the vector to account for each intermediate sample value.

2 Likes

Probably the wrong thread but I really wanna start learning and writing my own code - I just don’t know where to start!

Brian I’d be happy to roll this into a community project for whoever else wants to learn and we all have the same thing to practise on :slight_smile:

@nocables Step one: download the tutorial files, build them and run them on the emulator. Step two: start writing code :wink:

1 Like

Thank you, how the hell did I miss that!

I might be back with questions :slight_smile:

Doesn’t exist in the core.libcore or app namespace at the moment because the only place I use the cross-fade operation is to mix dry and wet signals on effect units which needs constant-power. Also, I think I tend to put more complexity into the DSP Objects because the lower level is ultimately more flexible and optimizations are easier.

1 Like

Makes a lot of sense. Of course I’m quite used to doing things the middle layer way at this point! Entirely new skill set to develop, and a bit of a paradigm shift! :wink: