Fb1 provides an interface for single sample feedback and feedforward at audio and control rate, the defining relation with (formal) access to previous samples is passed as a Function, which might involve additional UGens. Fb1.ar works with arbitrary blockSizes and also allows to refer to samples earlier than one blockSize before. This includes linear filter definitions of arbitrary length with dynamic coefficients as well as all kinds of nonlinear calculations of feedback and feedforward data (FOS and SOS UGens cover the linear case with lengths 1 and 2, LTI the general linear case).
Fb1 at control rate exists since miSCellaneous v0.22, for compatibility reasons I left the convention that Fb1.new generates an ar UGen, so Fb1.new is now equivalent to Fb1.ar and Fb1.kr is possible in addition, see Ex.5. Fb1 is the base of an ordinary differential equation integrator framework for initial value problems, that also came with v0.22, see Fb1_ODE for an introduction.
HISTORY AND CREDITS: There have been long discussions on single-sample feedback in SC. The most simple, but CPU-intense strategy is setting the server's blockSize to 1. Julian Rohrhuber gave a number of examples with Dbufrd / Dbufwr. SC's folder 'Examples' contains the files single_sample_feedback.scd and single_sample_feedback_02.scd. Special solutions are also possible with Delay1, Delay2 and other UGens. This particular implementation is based on Nathaniel Virgo's suggestion of iteratively writing to and reading from Buffers of blockSize – big credit for this! See also Nathaniel Virgo's Feedback quark for his feedback classes Fb and FbNode. Thanks also to James Harkins for his remarks on graph order. See Ex.1a for the basic feedback implementation principle. I implemented the ar feedforward option by temporary buffers for ar writing and kr reading, the feedback / feedforward relation can now be passed via a Function with 'in' and 'out' args. That way the syntax looks very similar to the common notations used for filter descriptions and also applies directly to the multichannel case. Most other options of Fb1 are for special multichannel handling and differentiated lookback definitions, which can help to save a lot of UGens.
Creates a new Fb1 ar object.
func |
The Function to define the feedback / feedforward relation. The Function should take the two arguments 'in' and 'out', both understood as nested multichannel signals, additionally a block index is passed (Ex.3e). Each 'in' / 'out' item of the arrays represents current or previous samples, for all points in time the samples are passed in specific array shapes, which are determined by the shapes of in and outSize. Allowed are pure signals (size = 0) and nested SequenceableCollections at maximum: e.g. outSize can be 0, 3, or [0, 2, 5], accordingly in signals can be of sizes 0, i or [i1, ... , in] with i, ij >= 0. Note that 'in' and 'out' only formally represent ar feedback and feedforward signals, technically kr UGens (BufRd.kr) are passed, the ar signals are reconstructed at the end by reading (arrays of) Buffers. The Function should return the multichannel UGen to be referred to with 'out', the shapes of the returned UGens and outSize must be the same. Furthermore the meaning of 'in' and 'out' depends on the inDepth and outDepth arguments. If an Integer is passed to them (default), the indices of 'in' resp. 'out' correspond to the lookback indices: E.g. out[1] refers to the last output sample(s) (of shape outSize), out[2] to the output sample(s) before the last output sample(s) etc. This is compliant with the convention of writing out[i-1], out[i-2] etc., out[0] refers to out[i-blockSize]. For a multichannel 'in' / 'out' signal, depth can be differentiated, which saves UGens in the case of "gaps" in the recursion: E.g. for a three-channel out signal outDepth can look like [3, [7, 18], [2, 5, 6]]. Then out[1] is a three-channel signal, whereby out[1][0] corresponds to out[i-1] of the first, out[1][1] to out[i-18] of the second and out[1][2] to out[i-5] of the third component. If the size of inDepth / outDepth is smaller than outSize, wrapping is applied. As a result double-bracketing can be used to define specific lookback indices for all components of the multichannel signal: E.g. if outDepth equals [[7, 18]] for a three-channel signal then out[0] means the three-channel signal out[i-7] and out[1] means out[i-18]. See Ex.3a for multichannel feedback / feedforward. |
in |
A single ar input signal or a SequenceableCollection of ar input signals to be referred to with func (feedforward data). See Ex.3a for multichannel feedback / feedforward. |
outSize |
Integer or SequenceableCollection thereof, the size(s) defined by the UGen(s) returned by func. It's the user's responsibilty to pass the correct size(s)! Defaults to 0. |
inDepth |
Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedforward data. If a SequenceableCollection is passed, lookback indices for feedforward data can be differentiated, its items can again be Integers or SequenceableCollections (see func). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 1 (no lookback). See Ex.3c. |
outDepth |
Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedback data. If a SequenceableCollection is passed, lookback indices for feedback data can be differentiated, its items can again be Integers or SequenceableCollections (see func). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 2 (look back to last sample at maximum). See Ex.3c. |
inInit |
Number or SequenceableCollection, feedforward init data. If a Number is passed, it means the previous init value for the calculation of the first sample(s), if the size of in is larger than 1, this init value is taken for all components of the multichannel signal. If a SequenceableCollection is passed, this differentiates the init values for a multichannel signal 'in' used by func. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of inInit is smaller than the size of in, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of a multichannel in. See Ex.3b. |
outInit |
Number or SequenceableCollection, feedback init data. If a Number is passed, it means the previous init value for the calculation of the first sample(s), if outSize is larger than 1, this init value is taken for all components of the multichannel signal 'out' used by func. If a SequenceableCollection is passed, this differentiates the init values for this multichannel signal. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of outInit is smaller than outSize, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of the multichannel signal 'out' used by func. See Ex.3b. |
blockSize |
Integer, this should be the server blockSize. It's the user's responsibility to pass the correct number. However it might be interesting to experiment with other values. Defaults to 64. See Ex.3d. |
blockFactor |
Integer. For a value > 1 this allows for lookback indices larger than blockSize, up to blockSize * blockFactor - 1. It's the user's responsibility to pass correct Integers in this case. Defaults to 1. See Ex.3d. |
graphOrderType |
0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens.
Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. This saves a lot of UGens and in all my examples I didn't encounter cases with different results. Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens, which can be lengthy with type 1. However, CPU usage doesn't directly correspond to the number of UGens. |
leakDC |
Boolean. Determines if a LeakDC is applied to the output. Defaults to true. |
leakCoef |
Number, the leakDC coefficient. Defaults to 0.995. |
Equivalent to *new.
Creates a new Fb1 kr object.
func |
The Function to define the feedback / feedforward relation. The Function should take the two arguments 'in' and 'out', both understood as nested multichannel signals, additionally a block index is passed (Ex.3e). Each 'in' / 'out' item of the arrays represents current or previous control samples, for all points in time the control samples are passed in specific array shapes, which are determined by the shapes of in and outSize. Allowed are pure signals (size = 0) and nested SequenceableCollections at maximum: e.g. outSize can be 0, 3, or [0, 2, 5], accordingly in signals can be of sizes 0, i or [i1, ... , in] with i, ij >= 0. Note that 'in' and 'out' only formally represent feedback and feedforward signals, technically kr UGens (BufRd.kr) are passed. The Function should return the multichannel UGen to be referred to with 'out', the shapes of the returned UGens and outSize must be the same. Furthermore the meaning of 'in' and 'out' depends on the inDepth and outDepth arguments. If an Integer is passed to them (default), the indices of 'in' resp. 'out' correspond to the lookback indices: E.g. out[1] refers to the last control output sample(s) (of shape outSize), out[2] to the control output sample(s) before the last control sample(s) etc. This is compliant with the convention of writing out[i-1], out[i-2] etc. For a multichannel 'in' / 'out' signal, depth can be differentiated, which saves UGens in the case of "gaps" in the recursion: E.g. for a three-channel out signal outDepth can look like [3, [7, 18], [2, 5, 6]]. Then out[1] is a three-channel signal, whereby out[1][0] corresponds to out[i-1] of the first, out[1][1] to out[i-18] of the second and out[1][2] to out[i-5] of the third component. If the size of inDepth / outDepth is smaller than outSize, wrapping is applied. As a result double-bracketing can be used to define specific lookback indices for all components of the multichannel signal: E.g. if outDepth equals [[7, 18]] for a three-channel signal then out[0] means the three-channel signal out[i-7] and out[1] means out[i-18]. See Ex.3a for multichannel feedback / feedforward. |
in |
A single kr input signal or a SequenceableCollection of kr input signals to be referred to with func (feedforward data). See Ex.3a for multichannel feedback / feedforward. |
outSize |
Integer or SequenceableCollection thereof, the size(s) defined by the UGen(s) returned by func. It's the user's responsibilty to pass the correct size(s)! Defaults to 0. |
inDepth |
Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedforward data. If a SequenceableCollection is passed, lookback indices for feedforward data can be differentiated, its items can again be Integers or SequenceableCollections (see func). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 1 (no lookback). See Ex.3c. |
outDepth |
Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedback data. If a SequenceableCollection is passed, lookback indices for feedback data can be differentiated, its items can again be Integers or SequenceableCollections (see func). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 2 (look back to last control sample at maximum). See Ex.3c. |
inInit |
Number or SequenceableCollection, feedforward init data. If a Number is passed, it means the previous init value for the calculation of the first control sample(s), if the size of in is larger than 1, this init value is taken for all components of the multichannel signal. If a SequenceableCollection is passed, this differentiates the init values for a multichannel signal 'in' used by func. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of inInit is smaller than the size of in, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of a multichannel in. See Ex.3b. |
outInit |
Number or SequenceableCollection, feedback init data. If a Number is passed, it means the previous init value for the calculation of the first control sample(s), if outSize is larger than 1, this init value is taken for all components of the multichannel signal 'out' used by func. If a SequenceableCollection is passed, this differentiates the init values for this multichannel signal. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of outInit is smaller than outSize, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of the multichannel signal 'out' used by func. See Ex.3b. |
graphOrderType |
0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens.
Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. This saves a lot of UGens and in all my examples I didn't encounter cases with different results. Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens, which can be lengthy with type 1. However, CPU usage doesn't directly correspond to the number of UGens. |
leakDC |
Boolean. Determines if a LeakDC is applied to the output. Defaults to true. |
leakCoef |
Number, the leakDC coefficient. Defaults to 0.995. |
The feedback / feedforward relation is defined within func, it's important to note that this Function is applied in a very special way to build the feedback relation into the synthdef graph.
Let n be the given blockSize, then
Writing ar UGens in func that produce a time-varying signal itself (e.g. SinOsc.ar, in contrast to SinOsc.kr and operator UGens like '+', '*' etc.) - instead, if such ar UGens aren't applied to data from inside func, they can be passed via Fb1's and func's 'in' arg. It remains the case of such ar UGens that should process data that is provided by func (e.g. letting the fb out modulate a parameter of a VarSaw.ar). This is currently not possible and I don't have a clear picture if and how it would be possible at all or if it would make much sense.
Examples 1b-1d are just a comparison of standard filters vs. explicit definition with Fb1 to show its functioning. Mostly there is no benefit in doing so in practice as standard filters UGens need less ressources. The real power of Fb1 lies in the potential to define nonlinear feedback and feedforward relations (Ex.2 ff.). Other than that you can use it to define higher order linear filters for which no classes exist.
The original form of the following example is by Nathaniel Virgo and shows the underlying principle for feedback alone. Succesively Buffers are set with new values at kr, at the end buffers are read with an ar Phasor, thereby the order of UGens is crucial. As James Harkins remarked in the below thread, plugging the writers into the final reader forces it, it can be done with summing, but other operations than '+' are also possible.
The example also works without this precautionary measure, at least on OSX, SC 3.9.3. The option graphOrderType allows to turn forced ordering off or to choose the alternative order-forcing operation '<!', see Ex.4.
https://www.listarc.bham.ac.uk/lists/sc-users-2011/msg01337.html
https://www.listarc.bham.ac.uk/lists/sc-users-2011/msg01363.html
This is an interesting field of exploration as it cannot easily be done with SC otherwise. Here the time-varying potential of unary and binary operators comes into play, normally they are applied to time-varying signals, but here their iteration on a single-sample base is itself an essential part of producing the variation in time. Good candidates are already simple operators and their combinations, also with + and -, e.g. *, /, **, %, trigonometric operators etc.