The goal here is to produce a bell sound that plays equally in the right and left speakers, but nothing actually comes out. Below is the SynthDef and a Synth that calls it.
All I can tell is that maybe Iâm not using the FreeVerb correctly, but Iâm not sure what Iâm supposed to be doing with it. Is this one of those things where you have to pass things through buses?
1.) for all the UGens you use, you should specify their rate (audio rate, control etc.). for example SinOsc should be declared as audio rate like this: SinOsc.ar. You also have done that for your envelope with .kr which is control rate.
2.) you should have a look at the arguments of your UGens carefully. The first argument of SinOsc is its frequency. In your example you are passing the envelope as the first argument and the frequency as the second argument.
3.) thats probably debatable, but a good practice imo is to not use the mul or add arguments of UGens, but instead multiply your SinOsc with the envelope like this: SinOsc.ar(freq) * env;
4.) you dont have to specify snd and sig. You could specify your variables at the top and then first declare sig = SinOsc.ar(freq) * env; in the line afterwards you can put that into Pan2 with sig = Pan2.ar(sig, pan); and in that line afterwards into FreeVerb with sig = FreeVerb.ar(sig).
5.) you have declared an amp argument, but you are not using it in your SynthDef. Its always good to have control over the amplitude at least once, at the very end of your SynthDef.
This should make sound (i have also specified an out argument, maybe you want to play it on another audio bus).
(
SynthDef(\bell, { |out = 0, amp = 0.8, freq = 440, decay = 3, pan = 0|
var env, sig;
env = Env.perc(releaseTime: 2.0, curve: -3.5).kr(doneAction: 2);
sig = SinOsc.ar(freq) * env;
sig = sig * amp;
sig = Pan2.ar(sig, pan);
sig = FreeVerb.ar(sig);
Out.ar(out, sig);
}).add;
)
(
Pbind(
\instrument, \bell,
\degree, 0,
\amp, 0.7,
\decay, 3,
\amp, 0.8,
\pan, 0,
\out, 0,
).play;
)
I guess what youâre trying to do here is keyword arguments, but the syntax for keywords is SinOsc.ar(mul: envl, freq: freq) (and dietcv is right, the rate method call is not optional).
In Pbind, the arg names are not arguments to Pbind itself â thatâs why the syntax isnât the same. Ron Kuivila used to recommend writing Pbind(*[name: pattern, name: pattern...]) and some folks still do that.
3.) thats probably debatable, but a good practice imo is to not use the mul or add arguments of UGens, but instead multiply your SinOsc with the envelope like this: SinOsc.ar(freq) * env;
I am curious why, would you mind to comment?
Thanks! P
Because it will be converted into a MulAdd UGen, this operation on floating-point numbers is commonly implemented in hardware as an optimized CPU instruction. C++ compilers like GCC or Clang often optimize (a * b) + c into a single FMA instruction. I think, some years ago, an effort was made to optimize SynthDefs using this UGen substitution.
just to be clear: SO the MulAdd UGen uses more CPU than the signal
addition and multiplication with * and + ?
Out of more curiosity, do we know the factor by which mu:&add: are more
expensive?
I think there was a bit of confusion. The âmulâ and the âaddâ arguments in some UGens are one thing.
MulAdd UGen is something else you donât write explicitly in the SynthDef. Still, the language optimizes operations with multiplication and addition into a MullAdd UGen when possible, providing better performance because the compiler uses a unique optimized instruction on the CPU, named fused-multiply-add (FMA), instead of two regular operations.
From some of my benchmarks, the cost of an extra UGen is orders of magnitude more costly than simple arithmetic. While it is true that the CPU can perform âmulAddâ faster the âmulâ followed by âaddâ, the cost of the extra UGen call in the latter is far greater than any benefit, so the real optimisation comes from having less UGen calls.
(This could change if you have really large block sizes, but in most cases, more ugens == bad)
However, the synthdef compiler will just rewrite your code in an attempt to figure out what is best⌠so you donât even need to think about this when making music â just trust it to do the correct thing.
My personal reason for this is quite pragmatic. I think its more easy to read and more easy to understand, for example when doing simple FM. It also makes things consistent, consider Ugens which dont have mul and add and you mix them together with Ugens which have mul and add in your SynthDef.
This might be a bit extreme but in my personal practice im just using basic ugens like Phasor and BufRd and try avoid the more exotic ones. Im just using their basic inputs like freq or reset and would for example never use the start, end and resetPos arguments of Phasor.
I donât quite follow, but basically for each ugen (which on the C++ side is a runtime known pointer) you need to load the memory there (this might induce a cache miss), then build that stack with the arguments in the correct place, re-fetch all the input and output buffers (hopefully still in the cache), load the ugenâs owned state (potentially a cache miss), then finally to do the calculation, before unwinding the stack and moving on to the next ugen.
In relative terms, this all happens very fast, and for more complex UGens the cost of all this is negligible, but for really quick ugens (like arithmetic which is hopefully just a handful of assembly instructions) it starts to become a bottleneck (it is block size dependent too!).
Edit⌠oh I see what you mean, yea the C++ compiler will (hopefully) turn for(auto& s : samples) s += 2 into a vectorised version, potentially even a muladd(1, 2), meaning you are now doing two muladds, one of the addition, and one for the multiplication.
Thanks,
so the MulAdd Ugen will be created by the language/optimizations out of
the mul: add: arguments? And as MulAdd is more costly it being a UGen,
it is preferable to write arithmetic operations?
How does the .range() method compare to these two?
@jordan recently did benchmarks (the only thing that matters) and reported that SinOsc.ar(mul: 0.5, add: 1) is more efficient.
Conversations about it will not help you know the best optimization; just trust well-designed benchmarks based on your use cases. Thatâs the lessons we learned here.
No this isnât quite true. When you write SinOsc.ar * a + b the synthdef compiler will go ahead and turn it into a MulAdd ugen for you (hopefully, and if it doesnât, Iâm trying to fix this in a pr).
@Peter To simplify⌠Write code that behaves as you want it to and donât worry about this sort of thing.
How? In sc 3.13, AFAICS they both compile to a SinOsc and a MulAdd. How can an identical structure run faster or slower depending on the way it was written initially (which the server doesnât know)?
Sorry but this thread is driving me a bit bonkers. Sooo many unsupported assertions being made, which are causing anxiety for at least one user.