FbNode an even more painless way to create a feedback loop within a SynthDef


Inherits from: UGen


There are many ways to create feedback loops in SuperCollider.  Similarly to Fb, FbNode aims to help you create them in as painless a way as possible.  The easiest way to understand it is probably to skip to the examples section.  FbNode is part of the Feedback quark.


Ways of creating feedback loops in SuperCollider can be divided into two types: those that use busses to route signals between SynthDefs, and those that create a feedback loop within a SynthDef. FbNode is of the latter type.


The traditional way to create feedback within a SynthDef is to use LocalIn/LocalOut.  FbNode is designed to be close to this model, but has a very major advantage over it, which is that you can have as many FbNodes as you like in a single SynthDef instead of just one, and have them all feed back into each other as much as you want. This makes it much easier to make complex networks of feedback lines, and leads to much more readable code.


Additionally, FbNode has an optional built-in multi-tap delay line, so you can easily control the time period of your feedback loops.  As with (almost) all methods of achieving feedback in SuperCollider, the minimum delay is given by the server's block size, which is usually 64 samples.


FbNode vs. Fb


Fb is the original way of doing feedback from the Feedback quark. After several years of using it, it became clear that although it worked very well in a lot of cases, for some things it just wasn't the right tool for the job. FbNode is designed to be both more flexible and more intuitive to use than Fb. Fb has a model in which the output of a function is fed back into its input, whereas FbNode is more about writing to and reading from delay lines. FbNode is probably the best one to learn first, but they're both useful and it's worth learning both and seeing which one best fits a given situation.  You can use both of them in the same SynthDef if you want.


One plus-point of Fb over FbNode is that it does multichannel expansion, at least in most cases. FbNode tries to make it easy to do multichannel feedback, but you have to ask for it explicitly - it doesn't try to do it for you. 


Another tiny potential disadvantage of FbNode over Fb is that it allocates an additional blockSize of samples for every feedback loop, in order to do some necessary double buffering. The chances of this tiny bit of extra memory use being a significant problem in most applications are very small, however.


On the other hand, FbNode makes it much easier to have multiple interacting feedback delay lines, especially if you want them all to have different delay times.


A caveat regarding multiple servers


The following will not be an issue for most users, but because of the way FbNode works, it needs to know the server's sample rate and block size at the time the SynthDef is compiled. To do this, it uses Server.default.options.blockSize (because there's no way to get the block size of a running server) and Server.default.sampleRate (because there's no way to reliably get the sample rate of a server that isn't running). This means that the server has to be booted in order to compile SynthDefs containing FbNodes, and there's no way to compile such SynthDefs for a remote server that has a different sample rate from the default one. I'd like to fix this, but I don't know the best way to approach it.



See also: Fb, FbL, FbC, FbK, LocalIn, InFeedback, NodeProxy, LocalBuf, and also the single sample feedback file in the Examples folder that comes with SC. These are all alternative ways to do feedback in SuperCollider.


Creation / Class Methods


*new (numChannels, maxdelaytime, interpolation)

This creates a new feedback node, which you can then read from and write to.

numChannels - the number of channels that the feedback loop will have. This defaults to 1. Note that you can either use this argument, or you can just create more than one FbNode. Multi-channel FbNodes are useful for things like stereo delay lines, but if you're doing something complicated with lots of channels, it will probably be better to use multiple FbNodes, as in the last example below.

maxdelaytime - the maximum delay time in seconds. Used to initialize the delay buffer size. You do not need to subtract ControlDur.ir from this, as this is handled automatically.  If you don't set it, or you set it to less than one control period (i.e. less than 64/44100 ≈ 0.014s) then no extra delay line will be added.

interpolation - what kind of interpolation to use when reading back from the delay line. (This is only relevant if maxdelaytime is set.) This argument uses the same numbering scheme as BufRd's interpolation argument, which for some reason goes like this:

1 - no interpolation

2 - linear interpolation

4 - cubic interpolation

the default is 2 (linear interpolation).


Reading from an FbNode

You don't have to do anything special to read from an FbNode if you don't want to add a delay to its output - just pass the node itself as an argument to a UGen. (See the third example below.)

delay (delaytime)

Output the FbNode's feedback signal with an added delay. The delay time can be modulated. The maximum delay time and the interplolation method must be given when you create the FbNode. You can use this method as many times as you want, with different delay times, so this effectively gives you a multi-tap delay line on every FbNode.

delaytime - the amount by which to delay the signal. This will be clipped to be between one block size (about 14 milliseconds by default) and 

the FbNode's maxdelaytime. If you didn't set a maxdelaytime when creating the FbNode then this argument will be ignored and the signal will be delayed by one block size. This defaults to the FbNode's maxdelaytime.

Writing to an FbNode


write (signal)

Write a signal to the FbNode's internal buffer. You can do this before or after you read from it, or both. In either case, the signal you get when you read from the FbNode will be the signal that was written to it in the previous control period.  This method returns its input.

If you write to a FbNode more than once, the two signals will be added. (But note that a BufWr UGen will be created every time you write to the node, even though only the last of these is necessary. If you're doing it a lot it's better to sum the signals manually and only write once.)


signal - the signal to write to the FbNode's buffer. This signal must have the same number of channels as the FbNode, otherwise you will get a 

buffer mismatch error.


It isn't possible to write to only one channel of an FbNode at a time. If you want to do that, it's better to create an array of FbNodes.



Examples


The first two examples are the same as for Fb, so you can see how FbNode compares for basic tasks.



// simple dub delay effect


b = Buffer.read(s,"sounds/a11wlk01.wav")


(

{

var in = PlayBuf.ar(1,b);

var fbNode = FbNode(1,0.6);

var signal = fbNode.delay; // read the feedback bus and delay the result. 

// The delay time defaults to the max delay time, 0.6 s in this case.


// Add the input to the feedback signal, then filter and distort it.

signal = BPF.ar(signal*0.8 + in, 2000, 3.8).distort;

// for fun effects, try changing the 0.8 to something greater than one


// write the signal to the feedback buffer

fbNode.write(signal);

DetectSilence.ar(signal ,doneAction:2);

signal!2;

}.play;

)


b.free


// Karplus-Strong style plucked string algorithm (see also Pluck)

(

{

var in = Impulse.ar(0)!2; // stereo input (although in this case both channels are the same)

var freq = 200;

var fbNode = FbNode(2, 0.1); // two channel feedback with a maximum delay time of 0.1 s


var signal = fbNode.delay(1/freq);


signal = LPF.ar(LeakDC.ar(signal),8000)*0.99 + in;


fbNode.write(signal); // two channel signal being written into two-channel FbNode

DetectSilence.ar(signal,doneAction:2);

signal;

}.play

)




// basic usage without adding a delay line: self-modulating sine wave.

(

{

var fbNode = FbNode(1);

var signal = SinOsc.ar(100, fbNode * Line.kr(0,2,10) ); 

// the FbNode is used to modulate the SinOsc's phase

fbNode.write(signal);

signal ! 2;

}.play;

)



// Two delay lines with cross talk. This would be quite awkward to do with Fb.

(

{

var in = WhiteNoise.ar*Line.kr(1,0,0.1);


// create two FbNodes with different delay times

var fbNode1 = FbNode(1,9/8);

var fbNode2 = FbNode(1,1);


var sig1 = in + (fbNode1.delay * 0.8) + (fbNode2.delay * 0.1);

var sig2 = in + (fbNode1.delay * 0.1) + (fbNode2.delay * 0.8);


fbNode1.write(sig1);

fbNode2.write(sig2);


Pan2.ar(sig1, -0.8) + Pan2.ar(sig2, 0.8);

}.play;

)



// using the multi-tap delay feature

(

{

var in = Saw.ar([100,102])*Line.kr(1,0,0.1); // stereo input signal

var fbNode = FbNode(2, 1.0);


var signal = Mix.fill(10,{fbNode.delay(1.0.rand)});

fbNode.write(in + (signal*0.1));

// if you want, you can use FbNode as a normal multi-tap delay, just by not adding in the

// feedback signal here.

signal;


}.play;

)



// How to create an array of many feedback delay lines.

// (This doesn't sound great, but it illustrates the technique.)

(

{

var in = WhiteNoise.ar*Line.kr(1,0,0.05); 

var n = 10;

var fbNodes = {FbNode( 1, rrand(0.1,1.0) )}!n; 

// create n mono FbNodes, each with a different max delay time.

var signals = n.collect {

arg i;

// the nodes are arranged in a circle, with each one getting some feedthough from

// the nodes on either side.

var signal = in + (fbNodes[i].delay*0.4)

+ (fbNodes[(i+1)%n].delay*0.3)

+ (fbNodes[(i-1)%n].delay*0.3);

fbNodes[i].write(signal);

};


Splay.ar(signals);

}.play;

)