Newbie: .range question

Question about a example:

(
SynthDef("wow", 
	{arg freq = 60, amp = 0.1, gate = 1, wowrelease = 3;
		var chorus, source, filtermod, env, snd;
		chorus = freq.lag(2) * LFNoise2.kr([0.4, 0.5, 0.7, 1, 2, 5, 10]).range(1, 1.02);
		source = LFSaw.ar(chorus) * 0.5;
		filtermod = SinOsc.kr(1/16).range(1, 10);
		env = Env.asr(1, amp, wowrelease).kr(2, gate);
		snd = LPF.ar(in: source, freq: freq * filtermod, mul: env);
		Out.ar(0, Splay.ar(snd))
}).add;
)

"The source sound LFSaw.ar takes the variable chorus as its frequency. In a concrete
example: for a freq value of 60 Hz, the variable chorus would result in an expression like:

60 ∗ [1.01, 1.009, 1.0, 1.02, 1.015, 1.004, 1.019]

Question: How do you get those values in the string? [1.01… ]?

I think I don’t see how that range command is working in relation to the string:

LFNoise2.kr([0.4, 0.5, 0.7, 1, 2, 5, 10]

60 * 0.4 = 24?! How can the answer be 1.01?

1 Like

60 * a list of random values generated by LFNoise2 between 1 and 1.02. Makes the list of frequencies. The list given as example, could have been different values from 1 to 1.02.

The list [0.4, 0.5, 0.7, 1, 2, 5, 10], takes care of the rate of random values generated by LFNoise2. That list is given and fixed.

Is that correct?

Hello and welcome,

you are refering to the example on page 95 / 96 in Bruno Ruviaro’s tutorial:

It"s meant that the frequency values at any time could look like this, they must be in the range 1 - 1.02.

Yes, it’s the (LFO) frequencies of (audio) frequency changes.

Greetings

Daniel

2 Likes

Thanks.

A similar question


(
{
    SinOsc.ar(freq:
        EnvGen.kr(Env.new(
            [ 0, 0.5, 0, 0.3, 0.3, 0, 0.1, 0, 0.2, 0.3, 0.15, 0.5, 0.25 ],
            [1, 0.5, 0.5, 2, 0.7, 1, 0.3, 0.6, 0.5, 0.8, 0, 0.4])
        ) * 1000 + 200
    )
}.play
)

Question: Why does the following not work?


(
{
    SinOsc.ar(freq:
        EnvGen.kr(Env.new(
            [ 0, 0.5, 0, 0.3, 0.3, 0, 0.1, 0, 0.2, 0.3, 0.15, 0.5, 0.25 ],
            [1, 0.5, 0.5, 2, 0.7, 1, 0.3, 0.6, 0.5, 0.8, 0, 0.4])
    ), mul: 1000, add: 200    )
}.play
)

SinOsc outputs samples between -1 and +1 at the sample rate of your system (typically 44100 Hz). The freq argument specifies how many times per second you will get a sine shape (full cycle). Mul and add are applied to the range of output samples (btwn -1 +1), not to the frequency.

In your second example, you are outputting samples in the range -800 +1200, and the frequency of your sine follows the (unchanged) values coming out of the envelope, so your freq is constrained between 0 and 1.

1 Like

At the moment you think you grab the idea, you loose it again…

I thought using multiplication (*) and addition (+), was just a other way of writing down the mul and add value.

So, my assumption that 1000 is the mul and 200 is the add of the SinOsc in this code is wrong?

The values in the array are used for frequency in this patch right? Those values are multiplied by 1000 and 200 is added. So the lowest or base frequency is 200 (0 * 1000 + 200). That is my understanding of it.

The example does EnvGen.kr(...) * 1000 + 200, not SinOsc.ar(…) * 1000 + 200.

Those math ops are inside the SinOsc’s parentheses, so they apply to an input to the UGen. With the given syntax, they cannot apply to the result.

hjh

1 Like

No worries! You are right that mul and add are literally just doing a (*) and a (+). The source of confusion here is what exactly is being multiplied and added. As James explained above, it has to do with what you are multiplying and adding to.

Watch in the Post window what numbers SinOsc is generating. They are between -1 and +1. We know that SinOsc.ar is generating 44100 samples per second (because of the .ar, audio rate; and assuming that’s the sample rate of your system), but poll prints only 10 of those per second to display:

{ SinOsc.ar(freq: 1).poll }.play;

Notice that changing the freq only alters how many full cycles of up and down take place; the range of the output (-1 +1) remains the same.

Here, one full cycle every 10 seconds:

{ SinOsc.ar(freq: 1/10).poll }.play;

When you use mul and add, you are multiplying and adding that output range, it has nothing to do with the freq of the oscillator itself:

{ SinOsc.kr(freq: 1/10, mul: 1000, add: 200).poll }.play;

I changed from .ar to .kr above because didn’t want this to make a loud pop going to the speakers. A huge mul in a sound-producing oscillator effectively means a huge amplitude).

The above is the same as:

{ (SinOsc.kr(freq: 1/10) * 1000 + 200).poll }.play;

…which is also the same as:

{ SinOsc.kr(freq: 1/10).range(-800, 1200).poll }.play;

Again, the point of all the above is, the mul and add have nothing to do with freq. Back to your other example with EnvGen, the reason the first one does what you expect and the second does not is that in the first you do the scaling (* and +) applied to the EnvGen, so that it is the EnvGen’s original output range 0-1 that is converted to a new range (desired audible frequencies). In the second example, by using mul and add of the SinOsc, you are effectively altering the amplitude of the SinOsc (its output), not its frequency.

Hope this helps.

Bruno

1 Like

Thanks a lot for the clear answers. Helps me a lot!

edit:

{SinOsc.ar(400 + SinOsc.ar(7, mul: 5))}.play

{SinOsc.ar(SinOsc.ar(7, mul: 5) + 400)}.play

{SinOsc.ar(SinOsc.ar(7, mul: 5, add: 400))}.play

These seems to be the same. Hmm
So mul and add has nothing to do with frequency. It’s scales the output range. Hmm.

I just realized what the confusion might be:

// mul/add *inside* parens
SinOsc.ar(freq, 0, mul: mul, add: add)

=

// math ops *outside* parens
SinOsc.ar(freq, 0) * mul + add

‘mul’ and ‘add’ apply to the result of the associated UGen – so the corresponding math operators must be outside the unit.

So mul and add has nothing to do with frequency. It scales the output range.

Yes, the output range of the UGen to which the specific mul/add belong.

“Has nothing to do with frequency” – consider, though, that SinOsc has two inputs (frequency and phase): SinOsc.ar(freq, phase, mul, add) – so, if you had expected mul and add perhaps to apply to the frequency, why would it necessarily be frequency and not phase? OK, “I wasn’t using phase” but the SynthDef compiler can’t change the meaning of mul/add based on what you used/didn’t use (SinOsc.ar(mul: 2, add: 1) – then what?). Phase is actually closer in the argument list, so that’s just as good a guess as any.

That relationship at best would be syntactically ambiguous – and so, unacceptable. It just can’t work that way.

A mul/add applying to frequency would have to be 100% contained within the “freq” slot (between open-paren and comma): SinOsc.ar(SomeUGen.kr(blah, blah, mul: freqmul, add: freqadd), ...).

It’s been proposed, btw, to discourage the use of ‘mul’ and ‘add’ in favor of the math operators. This thread provides some evidence in favor of that proposal: it can be confusing to have two ways to write the same thing. (TL;DR: You could save some of these headaches by forgetting about mul/add altogether and just using ‘*’, ‘+’, and range mapping methods like range, exprange, linlin, linexp.)

hjh

Mul and add do seem like relics… * and + make for more legible code…

Thanks again!

Is Amplitude Modulation done with multiplication and Frequency Modulation with addition?

I did also start with puredata, I hope it helps me understand sound manipulation better. The fact that it is more visual might help me.

Oversimplifying a bit, but yes – amplitude scaling (volume control) is always multiplication, and the standard definition of frequency modulation is:

carrier freq = freq + (freq * modulator * index)

But I usually reduce that to:

carrier freq = freq * (1 + (modulator * index))

(In SC, it doesn’t make much difference – I believe both of these expressions compile to one ‘*’ and one ‘MulAdd’ – but in Pd/Max, the 1+ formulation means one fewer connection to make.)

hjh

Still breaking my head about supercollider and soundsynthesis. :slight_smile:

{SinOsc.ar(SinOsc.kr(5, 400, 800), 0, 0.3)}.play

This is more a ‘software trick’ then a sound modulation right?
There are not two oscillators here modulating each other. It’s the OOSE software which makes use of it’s objects to generate frequencies from 400 to 800 hz, 5 times per second. That’s why this is a common sound in SuperCollider and less so in Pure Data. It’s a result of how the software is designed and works… (?)
Put it differently, the second SinOsc has more a sequencing role, then a sound modulation role.

:thinking:

no Idea what you mean by software trick! the whole enterprise is a software trick I suppose…

FWIW you have indeed specified two oscillators and the inner one is modulating the outer one (around a center frequency of zero though!

Try it like this:

{SinOsc.ar( freq: 400+ SinOsc.kr( freq:37, mul:100), mul:0.1))}.play

a quick correction: when using SinOsc with positional parameters the second is phase (in radians) which you had set to 400 in your example which doesn’t make a ton of sense!

a nice way to set the range of a modulator is using the ‘.range’ method like so (note that we’re multiplying here)

(SinOsc.ar(freq: 400 * SinOsc.kr(25).range(0.9,0.9.reciprocal), mul:0.1)

for full on FM sound

(SinOsc.ar(freq: 400* SinOsc.ar(137).range(0.9,1.1),mul:0.1)}

The reference to Pure Data suggests perhaps some confusion about the term “control rate.”

In Pure Data, “control” objects are driven by messages coming into an input (except objects like [metro] that generate control messages). No input message, no result – control objects can sit idle when not triggered. These are analogous to “language-side” operations in SC.

In SC, a “control-rate” UGen is a unit generator, just running at a slower rate. With default settings, a kr unit produces one output sample per 64 audio samples. But it isn’t different in kind, only in speed.

SuperCollider’s kr UGens have no analogue in Pure Data:

  • SC language side ops → Pd control messages
  • SC .ar units → Pd audio signals
  • SC .kr units → don’t exist in Pd

So you can’t use Pd concepts to read them.

Anyway this is just confirming what semiquaver said: your expression does contain two oscillators, running at different rates. It does not consist of an audio oscillator and a message-driven control object masquerading as an oscillator.

Put it differently, the second SinOsc has more a sequencing role, then a sound modulation role.

If you say that, then you would have to say it’s “sequencing” 689 values every second (which you won’t hear as discrete values) – and then you would also have to say that SinOsc.ar in some input is “sequencing” 44,100 values every second. But you wouldn’t say that. Both are modulation.

Btw SC applies some smoothing when you put a kr unit into the input of an ar unit, so that you don’t hear step noise.

hjh

I suppose in Pd you could use a subpatch with a [block~] object to run at slower than audio rate, and this would be an analogue to kr units. But I’ve never seen anyone actually do that ([block~] is often used for oversampling, where kr is “under-sampling”).

Except for the 400-radian phase offset (which semiquaver noted), this should be a literal translation into Pd of SinOsc.ar(SinOsc.kr(5, /*40*/0, 800), 0, 0.1).

pd-kr

hjh

I’m glad I’ve asked! :wink: Thanks.

It will sink in… slowly…:slight_smile:

I’ve dived into Pure Data a tiny bit. But when it comes to workflow, SuperCollider seems more easy and a lot faster to me. I hoped it would help me to understand sound synthesis better, and I’m sure it will help at the end.
For me I see more potential in Supercollider now, but I’m sure it is valuable if you can also work with PD a bit.