Outdated sclang features: mul/add and Mix

two sclang features that i don’t recommend using: mul/add arguments, and the Mix class.

SinOsc.ar(440, 0, 0.1) and SinOsc.ar(440) * 0.1 do the same thing, but the latter is far more readable. you don’t have to know the argument list of SinOsc.ar to be able to tell what the 0.1 is doing. * 0.1 is how you multiply by 0.1 anywhere else in sclang.

Mix(array) and array.sum do the same thing, but array.sum is more sclang idiomatic, and Mix confuses you into thinking it’s a UGen (which it is not — check the source code).

both of these are historical artifacts of a less smart synthdef optimizer. the mul and add arguments of ugen classes compile to MulAdd ugens (via the .madd method), which used to be faster than the BinaryOpUGens + and *. however, now the SynthDef compiler is much smarter and is capable of optimizing a * b + c into MulAdd(a, b, c). many newer ugens don’t even have mul/add arguments.

similarly, Mix is intended to optimize a sum into a tree of Sum3, Sum4, and + ugens. however, now writing a + b + c + d is optimized to Sum4(a, b, c, d), and the efficiency benefits of Mix are no longer relevant.

there are no current plans to deprecate either one, but i advise against using them. they are both remnants of older versions of SC, and all they do now is offer additional, unnecessary ways to do things.

i get that mul and add arguments in particular are a tough habit to kick, since they’re introduced in literally day 1 of SC, but i have faith that we all can collectively reach BinaryOpUGen nirvana.

EDIT: more reasons not to use mul/add

thinking about this more, i have just recently discovered another, more compelling reason to avoid mul and add. their behavior on multichannel expansion is inconsistent with other parameters when dealing with nondeterministic ugens. a demonstration:

{ LFNoise0.ar(freq: [100, 100], mul: 1) }.plot(0.2);
{ LFNoise0.ar(freq: 100, mul: [1, 1]) }.plot(0.2);

why does the first line of code generate two different random signals, but the second one the same signal duplicated? because internally, this is happening:

{ LFNoise0.multiNew(\audio, [100, 100]) * 1 }.plot(0.2);
{ LFNoise0.multiNew(\audio, 100) * [1, 1] }.plot(0.2);

in the first line, LFNoise0.multiNew is multichannel-expanding and generating two different copies of LFNoise0. but in the second line, the multichannel expansion happens in the madd stage, and the same copy is being multiplied by 1 twice. in this way, mul/add parameters are “special”. or in other words, inconsistent and confusing.

another problem that’s more of a developer concern than a user concern is that, since mul and add arguments conventionally must come last, it is not possible to add new arguments to a ugen.

7 Likes

As an alternative to mul and add, I would recommend getting used to using either range or linlin and it’s variants. For me, these represent the actual thing I want to do 95% of the time (except in cases where I’m just multiplying). For example:

var lfo = SinOsc.kr(1/10, 0, 25, 75);

…a SinOsc moving from from 50…100 becomes:

var lfo = SinOsc.kr(1/10).range(50, 100);

or:

var lfo = SinOsc.kr(1/10).linlin(-1, 1, 50, 100);

or the same with exponential scale:

var lfo = SinOsc.kr(1/10).linexp(-1, 1, 50, 100);

The only caveat with these is: linlin is more specific about your input values. range is hard-coded to a -1…1 or a 0…1 range (depending on what the input UGen is). I’ve occasionally had bugs because of this, but it’s still a good pattern for straightforward cases like a SinOsc LFO.

5 Likes

One counterpoint to Mix vs sum. Mix works regardless if the signal is mono or stereo. Sum will throw an error if the signal is not stereo. If you’re trying to create something reusable that doesn’t care if the input is mono or stereo but want to ensure it ends up mono, Mix is a bit more generic.

Yes! I was trying to remember why I use Mix and not .sum. This is exactly why. We should re-implement Mix so that it just does input.asArray.sum, which keeps the generic behavior but ditches the unnecessary logic in the implementation.

good catch. one solution would be to implement UGen:sum and SimpleNumber:sum.

I’d be slightly hesitant to do this? [0, 1, 2].do is equivalent to 3.do. If we made this change, [0, 1, 2].sum would NOT be equivalent to 3.sum. That feels like it conflicts a little with how looping and integers interact as well. The UGen case feels the same - UGens generally act the same-ish if they’re replaced with numbers.

that’s fair. i’m quite satisfied with signal.asArray.sum as the replacement for Mix.

1 Like

Granted that this doubling somehow looks absurd. But consider that there is a history and it’s potentially dangerous to change the implementation of Mix or even to deprecate it. A lot of code relies on it. I remember some years ago (10 or so ?) Mix was the target of much criticism. IIRC in former days you had to write Mix.kr or Mix.ar or at least it was the standard way. Then we got Mix() but the ar and kr methods remained or were re-established …

mul/add and Mix are not outdated in my opinion. One of the inspiring things with sclang is that you can write the same piece in so many ways. Also take into consideration that a lot of sc-tutorials use these features to explain basic concepts.

mul/add and Mix are not outdated in my opinion.

I do not know why they are outdated. However, I do not often use “mul:” and “add:” nowadays. It is funny that the ways I do not use more often are mentioned as outdated by Nathan…

One of the inspiring things with sclang is that you can write the same piece in so many ways.

Writing the same piece in so many ways might be inspiring for computer-science-based users and learners as well as advanced users while music-based users or beginners are confusing with these various styles.

When I was learning SC for the first time, These many writing styles were confusing.

As a lecturer, I have seen the students who already learned C, C++ or Python have less difficulty with the writing styles, but the students whose first programming language is SuperCollider have problems with them.

Thus, I think it is essential that there must be tutorials for beginners which introduces the easiest and modern ways of writing style and concentrates on them, and slowly refers to various styles. I think what Nathan has been summarising is necessary here. I thank him for his efforts.

Of course, the various writing styles should remain because of the backward compatibility and historical reason since there have been too many great codes written in various styles.

2 Likes

Supporting multiple ways of writing the same thing is rarely a good thing in programming languages. On this, as on so many things, Larry Wall was completely wrong.

2 Likes

I agree this can be confusing for beginners, and I was at first confused too by all theses writing styles.

But now if you ask me what style should be the standard and the others deleted, I can’t decide. Every style has his benefits and drawbacks and I use them (almost) all.

On this topic, I don’t see how some additional arguments to UGens are confusing. it’s common to have multiples entries point to scale a thing (the synth amp then the mixer track volume for example)

But speaking of confusion, you have another topic about SynthDef arguments as function arguments or .kr style. I agree this is confusing. But if you read carefully the thread, you realize that the function argument style is not outdated because it has also its uses

  • function style: only one initialization at the top (no risk to init multiple times causing error), more readable, less typing than symbol style
  • symbol style: can add a new argument everywhere without having to go to the top, can generate argument names dynamically, can specify rate and lag (more readable than in the array after the SynthDef)

My point is: you should not fear the diversity of writings styles. SC is not any programming language, SC is a music software. When you use a GUI, you can dynamically re-arrange the layout to put more used features on the same screen to allow things which would not be possible otherwise. When you use a code interface, the only thing moving is by you writing and erasing. Using an inflexible language would close the door to creative ideas. The interface of the instrument is not neutral, we can ear it in the music.

You can even write your own DSL in SC ! I would not want to write music in Java :stuck_out_tongue:

1 Like

One observation - SC’s left to right operator precedence can make SinOsc.ar(440) * 0.1 and SinOsc.ar(440, 0, 0.1) not always equivalent.

For example, this:

VarSaw.ar(440) + SinOsc.ar(440) * 0.1;

has a different result than

VarSaw.ar(440) + SinOsc.ar(440, mul:0.1);

To achieve the second result without using mul you would need to write it as

VarSaw.ar(440) + (SinOsc.ar(440) * 0.1);

just a caveat that I know can trip people up

1 Like

sorry, i gotta disagree with you here. i kinda doubt that i’ve closed creative doors to myself by keeping a consistent code style.

being locked into 1 way of doing things may feel restrictive at first, but to me it’s ultimately liberating, because you stop thinking about style and focus instead on what really matters: what your code does.

okay, maybe Mix and mul/add aren’t terrible (although i’ve added a drawback in the original post — many newer ugens do not support mul/add at all). but in the other post, i laid out some significant red flags regarding argument-style SynthDefs. i really don’t want to try to figure out why |freq = [100, 200, 300]| isn’t working when i’m trying to design some sounds. by propagating a less error-prone style, we save more people from frustrating debugging experiences, and that’s way more important than nebulous creative benefits.

i agree that compositional interface is not neutral, but if it’s something that’s on your mind, why not go all in? a DSL specifically for writing SynthDefs would be a super interesting project.

2 Likes

Ooh that’s a nasty one.

For a somewhat math-centric language like SCLang, the operator precedence rules are pretty evil. I don’t even want to begin counting how often I’ve been bitten by those.

Nowadays I’m pretty paranoid about putting parentheses around everything (which isn’t doing wonders for readability either) - and still it happens that I mess up.

Pure Evil I say. NHHall.ar(“Pure Evil”) !

I tend to use parentheses in most languages. It’s not that I don’t know the precedence rules - more that I don’t want to think about them when reading code.

1 Like

If you reorder the formula, you can save parentheses.

e.g.

1 + (2 * 3)
2 * 3 + 1

440 * (2 ** 2)
2 ** 2 * 440

etc.

I suppose Nathan’s recommendation for array.sum actually cleans up this situation…

var sig = [
    VarSaw.ar(440),
    SinOsc.ar(440) * 0.1
].sum;

i have updated the original post with another reason to avoid mul/add — multichannel expansion behaves differently for those than for other arguments.