This is great! One minor snag: the example doesnāt work for me because the mul method is not defined, so I assume you have defined it in some other extension. I have added this to both the + UGen and + Array extensions:
mul {
arg mul;
^MulAdd(this, mul);
}
And now it works great! I am always looking for ways to reduce keystrokes, and I love the method-chaining style as well, so I am really digging this. By the way, your terse guide is awesome too. You are making some excellent contributions!
Ps. Below is a tiny demonstration video of sending the same notation to 1. SuperCollider, 2. Smalltalk and 3. Scheme.
Itās a bit obscure, but the Emacs āmode-lineā shows which mode itās in, and for the ātranslationsā the translated text is displayed in the āecho areaā.
just a note that I would favor the implicit rate constructor being part of SC - the mental overhead of selecting a rate when instantiating every Ugen is annoying (and 3 extra characters!)
just a note that I would favor the implicit rate constructor being part of SC - the mental overhead of selecting a rate when instantiating every Ugen is annoying (and 3 extra characters!)
It could easily be done by reserving the new constructor for UGens and properly implementing ir, kr, ar, dr constructors but It will break a lot of code. I also noted, personally, that writing the rate makes it easy to understand the graph.
ā¦ so implementing a *new constructor wouldnāt break anything that exists in the vanilla main library.
I suppose here we run up against the typical SC-land trade-off: between expressiveness and consistency.
I wonāt deny that this is a nice way to write unidirectional signal chains.
At the same time, itās been raised before that SC is challenging to learn because there are ātoo many ways to do the same thing.ā One of the ways that we end up with ātoo many waysā is by adding conveniences without first considering potential drawbacks (and once itās in, deprecation becomes a big deal).
To be honest, for myself, neither of these two is especially readable:
var sig = Dust.ar(1) * 0.25;
sig = Decay.ar(sig, 0.2) * WhiteNoise.ar;
sig = AllpassN.ar(sig, 0.2, 0.2, 3);
Maximally compact? No. But (this is just a personal opinion) ā 1/ Capitalizing UGen names distinguishes them from inputs and draws attention to them in a way that I donāt get from the left-to-right filter methods. (Also, class names are highlighted while method names arenāt.) 2/ The visual separation between stages is (somehow?) useful or comforting to me.
At least this way, you have a consistent place to look for the operation being performed, instead of having to scan a long line without any visual clues about what is adding UGens and what is just (say) a variable reference.
to my eye the variable name āsigā (five occurances!) is noise + finding it after the equals take a moment to boot. If I need it for further calculation I can assign it at the top of the chain.
re the .new method I think it should default to ar. To my mind the control rate is becoming a relic anyway, most useful as a āspecial caseā for low resource platforms and in a more modern setup the more an opportunity for errors than anything else. I still use it out of old habits but Iād love to forget about it!
Suppose the decay time is variable (signal input rather than a constant). The only way to pass that will be as a variable, because _ becomes thorny as soon as thereās more than one method call ā Iām not sure what would happen with e.g. => Decay.ar(_, SinOsc.kr(0.1, 0, 0.2, 0.25)).
Also, expressive variable names are a form of self-documentation, which you might think is unimportant until you try to update a four-year-old SynthDef and you find that you have no idea āwhat the @@@@ was I thinking???ā
This is a bit off topic, but I have been wondering about that as well. Even James McCartney said in his lecture āSuperCollider and Timeā that he would probably get rid of control rate if he could.
I am currently in the process of designing an audio programming language/library (so far only on pen and paper ) and I have been thinking about control rate a lot. I would say that with a block size of 64 samples the CPU savings are certainly noticable but not spectacular. However, my audio engine would support subgraphs that can run at lower/higher block sizes. My main goals are local single sample feedback and FFT via reblocking (similar to Pd), but it would also allow you to run your control rate ugens in a dedicated subgraph at a large blocksize. With a local block size of 1024 samples and many ugens, the performance difference could be quite large.
On the language side, I would like the rates to be automatically deduced as often as possible. Also, I would prefer a more stream-like syntax for connecting ugens - a bit like yours! Therefore I find this thread quite interesting.
About control rate, I think itās interesting that it can also fall out as a special case of ādemand rateā, simply by rewriting references to the sample rate in the demand subgraph.
Ie. if sr = 48000, control rate with a block size of 48, perhaps written kr(ā¦), can be implemented as demand(impulse(1000), multiplySampleRateBy(1/1000, ā¦)) &etc.
Writing Sc graphs without rate qualifiers can be helpful if you want to send the same notation to both scsynth and to a simple home made synthsesiser that doesnāt have an ar/kr distinction.
It could easily be done by reserving the new constructor for UGens and properly implementing ir, kr, ar, dr constructors but It will break a lot of code.
I guess the idea would be to add the implicit rate constructors as an option, but not a requirement.
ā¦ so implementing a *new constructor wouldnāt break anything that exists in the vanilla main library.
I was thinking about demand rate ugens and special cases that already use new as a lot of code in risk if it was going to be implemented from the top of the class hierarchy, but maybe there is a way to keep compatibility. I do agree with your later observations.
I am currently in the process of designing an audio programming language/library (so far only on pen and paper ) and I have been thinking about control rate a lot. I would say that with a block size of 64 samples the CPU savings are certainly noticable but not spectacular. However, my audio engine would support subgraphs that can run at lower/higher block sizes. My main goals are local single sample feedback and FFT via reblocking (similar to Pd), but it would also allow you to run your control rate ugens in a dedicated subgraph at a large blocksize. With a local block size of 1024 samples and many ugens, the performance difference could be quite large.
There is a nice language for low level faust like code generation that is multidimensional and multirate: https://github.com/jleben/arrp Iām not sure if it manages data between different rate running apps or just inside the generators.