Outdated sclang features: mul/add and Mix

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.

droptableuser

22d

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.

This is a very common complaint. But if you look into higher mathematics, all those elementary mathematics rules are not taken for granted anymore, but had to be made explicit. This is why in languages like APL or Smalltalk, the solution is simply to keep all precedence left to right – you can rely on it. If you want to be clear, use parentheses.

The alternative is something like Haskell, then you can really change how the language looks. But you need to know the definition of an operator in order to be able to know how what argument is called on what function. This is a lot of work, if you are confronted with new libraries all the time…

2 Likes

I’m well aware of the strangeness that exists out there (octonions anyone? who needs associativity anyway), but the overwhelming majority of all supercollider code ever written doesn’t use anything but bread and butter maths. (Except it doesn’t because in sclang universe 1 + 2 * 3 == 9 whereas 2 * 3 + 1 == 7).

There’s really no need to implement haskell to have sane operator precedence rules, as pretty much every “conventional language” out there proves on a daily basis.

That’s not to say that the rules should be changed now, because a lot of code already relies on them (violating the principle of least astonishment if you ask me, but no one ever does :smiley: ), but it’s certainly not a design decision I would have applauded had I had something to say in it…

Or you could write the whole thing in FORTH…

LISP doesn’t have, or need, operator rules. I’d prefer a DSL in LISP for building synth defs, but that might just be my own personal damage.

The reason that SuperCollider has it’s precedence rules is because that’s how UGens work (and pretty much ave to work) - and so the language enforces them universally so that you don’t get inconsistencies.

Years of debugging code in multiple languages has resulted in me using parentheses pretty religiously for anything mathematical to minimize surprises, so it’s never really caused me any issues in SuperCollider. Oddly that goes double for Haskell, even though I’m happy with point free style generally.

For example if I was doing a code review and saw that someone had replaced the top bit of code with the bottom line - I would flag it. The top line is far more readable and is less likely to result in bugs further down the road.

@cian are you aware of overtone written in clojure?

Yeah overtone seems kind of neat.

This!
I want to say that these methods make coding so much easier.

Maybe it would be more correct to say, that .range will take the default output range of the UGen as its expected input? Which is exactly what one usually and most of the time works with. In those other cases, yes, .linlin and its variants are another convenience method everybody should know!

exprange is also useful. Takes a linear range and makes it exponential. Incredibly useful.

also .unipolar and .bipolar are neat when you just want to limit your synth to 0, 1 or make a unipolar ugen to -1, 1