Noob InFeedback order of execution basics

Hello,
This is likely very obvious, but I just realised I don’t quite understand something which I thought I understood about InFeedback. In the audio feedback modulation example from the help file:

(
SynthDef("help-InFeedback", { arg out=0, in=0;
    var input, sound;
        input = InFeedback.ar(in, 1);
        sound = SinOsc.ar(input * 1300 + 300, 0, 0.4);
        Out.ar(out, sound);

}).play;
)

What does it mean exactly to multiply the frequency of the SinOsc by input? In other words, what exactly is input at that very moment/cycle? I polled it and I get positive and negative values which just look like amplitude values of a signal. And yet the result of this is different from just modulating the frequency of the SinOsc by inserting another SinOsc there (an LFO). Is it because it’s the same signal but ever so slightly delayed? Again, apologies if this seems trivial, but I can’t quite grasp the timeline/order of execution here.

Cheers,

Yes.

input here is the sound signal written to the out bus using Out.ar, one cycle earlier. Let’s say we replace SinOsc.ar(input * 1300 + 300, ...) with SinOsc.ar(SinOsc.ar(someFreq) * 1300 + 300), ...) like you suggested and call the enclosing SinOsc.ar the “carrier” and input/the inner SinOsc the “modulator”, respectively. You’re really comparing two different cases here:

  • a sine wave carrier being frequency modulated by another simple sine wave, vs.
  • a sine wave carrier being frequency modulated by a past version of itself (a control block duration older), which is a more complex sound (more specifically, a feedback FM’d sound…)

Edit: try inserting input.scope; in the SynthDef function instead of just polling input’s value, that should make it obvious that input is not just a simple sine wave.

1 Like

Delayed by one sample and mixed with whatever was output in the current cycle on the bus already. The latter is zero in that example, so InFeedback only gives the previous sample. But you could also do e.g.

(
SynthDef("help-InFeedback", { arg out=0, in=0;
	var input, sound;
	Out.ar(out, DC.ar(0.1));
	input = InFeedback.ar(in, 1);
	sound = SinOsc.ar(input * 1300 + 300, 0, 0.4);
	Out.ar(out, sound);
}).play;
)

Which changes the sound compared to the help example. Of course that’s a rather artificial example since we could have added 0.1 to input directly, but the mixing is more interesting if there are other Synths writing to the bus before “help-InFeedback”.

1 Like

Just to add for clarity to the OP. The input of InFeedback is not delayed by one sample, but by one audio cycle (depending on your server’s blockSize).

1 Like

blockSize only affects .kr but not .ar if I’m not mistaken.

Ah, never mind, it does affect InFeedback.ar because audio is computed in those chunks as well. So I’m guessing the semantics is to mix one full blockSize with the previous blockSize, position by position.

SeverOptions.blockSize directly sets the scsynth’s -z argument, which sets the number of samples per audio block. The consequence is that any audio processing on the server will be calculated in blocks of blockSize size.

Yes, that is correct.

Thank you all for the great explanations. Allow me to ask yet another thing which may be blatantly obvious, yet…

Back to the original example from the helpfile, i get that * 1300 + 300 ends up determining the frequency of the SinOsc, but can you help me understand why we need both operations? +300 is an offset of sorts, it adds 300 Hz on top of whatever you have before, but what about * 1300? why do we need that to produce a useful frequency value, i.e. how come that +300 is not enough in this situation, given that InFeedback already would provide a varying modulation source? my first hunch before running it was that input + some value going through InFeedback would suffice, but that essentially just ends up resulting in the frequency being almost exactly the added value (plus some negligible extra hz).

// i know i’m obviously missing a point, that’s why I’m asking :slight_smile:

THANK YOU

That multiplication is needed because the output of the SinOsc, and thus the input of the InFeedback, ranges between -1 and 1. That’s why if you don’t multiply the values you only get a small flickering: it’s actually going between 299 and 301. The multiplication allows the range of the oscillation to reach higher frequencies.

1 Like

^^ The general point there is: Always be aware of the range of values in a signal.

When I teach interactive multimedia, data range is lecture no. 3 – because it’s fundamental, and understanding this unlocks a ton of problems.

Math operations on the range apply to the endpoints of the range:

  • SinOsc range: -1 to +1
  • Times 1300 = -1300 to +1300
  • Plus 300 = -1000 to +1600 (negative frequencies are expected in FM synthesis, btw)

Basically all “how do I modulate x y or z input” problems can be approached this way.

hjh

1 Like

still struggle to see why only multiplication by 1300 is not enough to produce the right frequency.
if -1000 to +1600 works, why does -1300 to +1300 not work as a frequency for the SinOsc?
thanks!

In general, that would be a matter of taste/experimentation, but in this particular case, you actually get complete silence when removing the addition (+ 300). This is because the value of input starts out at 0, and if there is no offset, it won’t budge. Why? Because 0 * 1300 is still zero, and a SinOsc at frequency zero produces silence:

{ SinOsc.ar(0) }.play; // silence!
{ SinOsc.ar(0) }.poll; // confirmed in post window

So you ran into a special case here (the range being centered at 0 causing silence for all future feedback cycles because of how SinOsc works).

One more tip on ranges: search for the linlin and range methods in the help browser. linlin can be used to (linearly) scale any number from a given input range to a given output range, and range can be used on UGens to directly specify an output range, no mental math needed:

// scale a number you expect to lie in between -1 and 1 to a range from 0 to 100
0.5.linlin(-1, 1, 0, 100).postln; // 75.0

// scale the output of a UGen directly to a given range
// this is smart enough to automatically recognize that SinOsc outputs values between -1 and 1 and do the math for you
SinOsc.ar(440).range(-1000, 1600) // replacement for the multiplication/addition above
1 Like