Opinionated Advice for SuperCollider Beginners

I’ve put up a new post on my weblog, on my Internet website, which I think may be of interest.

Also I just updated this older post of mine which is more specific and technical:

Would be interested to hear any thoughts on these. Any high-level advice you’d give to SC beginners?

19 Likes

Very nice! I’d love to hear more. Perhaps an even strong opinion?

I’d probably suggest a few things, but they’d also be opinions. I coming from a realtime interaction (with an instrument) perspective, where I write the code, hit play, and run away from the computer, so a lot of the flexibility SC offers isn’t needed.

  • When learning supercollider, it might be better to learn Bus before synth controls, that way you can introduce NamedControl as something similar to a Bus, but accepting of a symbol. Then circle back to the shorthand.

  • There isn’t anything about coding style. I’d really suggest avoiding global variables where ever possible, except from at the highest level of code. This is because they are neither global nor variables (and they are mutable). Likewise, inline vars should be the default as supercollider’s support for functional style operations (collect and whatnot) is pretty extensive.

  • Being familiar with structures such as Event and Array and how you can apply functions like collect, reduce, inject to change them makes all aspects of SC easier, particularly complex synthesis where you can use all the those structures to hold UGen data.

  • When using multiple files, absolutely do not use global variables across files. This always causes headaches and makes the smaller files a nightmare to read later. I’ve abandoned projects because of this.

  • When using multiple files, instead of calling load on a string, prefer, thisProcess.interpreter.compileFile(path).(). That way the final line of the file is returned and if you wrap the file in curlies with arguments, you get back a function that you can pass arguments too. This turns files into a sort of module with inputs. Even make a quick class that wraps this, call it Import or something.

  • I’d also add to stay away from using Event’s as an object prototyping framework, every time I’ve tried, I find
    some new gotcha that I hadn’t noticed before.

  • Oh, and be careful of operator ordering. Whilst most things go strictly from left to right, there are a few exceptions, like the colon in (\a: 1 + 2), which might appear like it would do something similar to \a -> 1 + 2, but does not.

Your section on ambitious software projects is definitely good advice. I’ve been slowly developing my own supercollider coding style for writing classes that helps a great deal with this, but it is hard to escape the huge inheritance hierarchy, and all the extra methods your classes get despite them making no sense.

2 Likes

Here’s a spicy one… for the love of God, try to make something in SC that’s not slow-moving atmospheric dronescapes. I would appreciate drones more if they didn’t completely dominate academic electronic music concerts and the modular synth enthusiast community. Good ambient is quite difficult to make, but bad ambient is all too easy, especially in SC.

11 Likes

I share your annoyance with the default Event type

and though I do use patterns a lot I actually think that the pattern system should ideally be broken off into a separate library (and take the default Event type with it!) - have been discussing this with Charles Célese Hutchins over on Mastadon… (likewise JitLib)

Then we need a real include system…

There are some weird things about the default Event prototypes for sure, but it’s trivially easy to not use it:

Event.parentEvents.default.clear.putAll(());
// or....
Event.addEventType(\custom, {
   "do my own thing on .play".postln;
}, ());
Pbind(\type, \custom);

And relatively easy to borrow only parts of the default Event behavior, since they’re broken out in Event.partialEvents. The fundamental concept - streams of composable, prototype-based musical events - is probably the best and most powerful part of SuperCollider overall apart from the SynthDef graph system.

There’s a bit of a need for a middle ground here: the default Event system packs in a lot of “automatic” behavior that is not very discoverable and can be confusing for new users. On the flip side, it handles a huge amount of the music-related boilerplate and bookkeeping parts of SuperCollider that can easily be dealbreaker to implement for anyone who wants to make music without doing heavy programming.

In general, you’re trading a possibility of hard-to-debug behavior against the possibility of difficult programming tasks to reach some useful musical concepts - sadly, there really only two choices (yes or no) and each of them exist at pretty far-flung poles in terms of user experience (hence the need for a middle ground).

The best libraries make things easy to debug EVEN WHEN you get a lot of automatic things for free, but the Event system has I think grown much more organically and is a mish-mash of different approaches and strategies. I’ve done some prototyping work on a more well-factored and deterministic version of the Event system, but it’s a huge task - and of course very easy to just reproduce the same problems as the current system has: it’s not like the current system didn’t have a number of very smart people trying to make it clean over many years :).

couldnt agree more with that.

I agree that the default Event, and its use in Pbind, is one of the most powerful parts of SuperCollider. It’s a sort of “batteries included” approach to making music in SuperCollider, which makes it a good entry point for new users without much programming experience. In fact, some tutorials even teach the pattern system before teaching SynthDef.

The problem is that, for something that is supposed to be beginner-friendly, it is surprisingly difficult to find useful information in the help files about the gotchas involved with using SynthDefs in combination with the default Event. They are alluded to in later parts of the Pattern Guide, Pbind, and Event help files, but I really think there should be a dedicated help file about how one should code SynthDefs for use with the default Event, with prominent links to it in the SynthDef, Event, and Pbind help files.

At bare minimum, this should include a list of control names that you should or should not use in SynthDefs because of their use in the default Event. E.g.: You should use \amp, \freq, \gate, \out, etc. (where appropriate); you should not use \lag, \detune, \dur, \harmonic, etc. (unless you really know what you’re doing). Likewise, this should include a list of keys you should avoid in Pbinds because of their role in the default Event. All of these could be demonstrated with very simple examples and counter-examples.

2 Likes

Awesome man, i really appreciate this. Also great website, i didnt know about it!

If I have a bored-but-hyperactive afternoon some time soon, I’ll try to rewrite some of the help files here because you’re entirely right: the docs here are a disaster, and there’s no way to make sense of some of this without reading the Event source code.

4 Likes

Glad to know I’m not the only one who had to read the Event source code to actually understand how they work…

I’ll pop out of hibernation for one longish post…

“Don’t install tons of quarks or third-party plugins” – for the most part, yes – the difference between having 1000 objects that you haven’t learned how to use yet, and having 10,000 objects that you haven’t learned how to use yet, is not especially productive.

One exception, though, is signal routing. I realized early on when I started with SC that I wanted to handle signal routing in DAW terms. So I wrote MixerChannel (ddwMixerChannel quark). As a result, signal routing in my production work is just not a problem. At all.

There are hundreds of ways to use buses and groups for routing, and maybe half a dozen of those are good (I’m tempted to say, even less than that, could be just 1 or 2). The odds of a novice audio programmer improving on DAW routing are slim – there’s a lot of benefit to be gained from standardizing a signal routing methodology, and then continuing to do it that way. (Here, SC documentation would benefit from a signal routing guide.)

That plus a simple parametric EQ GUI (ddwEQ quark) – which doesn’t even show the frequency response graphically, just gives you the controls – got me 95% of the way with mixing in SC.

“Find non-SC production tutorials and translate them to SC” – yes yes yes. Yes yes yes yes yes yes yes yes yes yes yes yes yes yes yes yes.

Here, it might be nice if someone added a Guide doc about standard synthesis formulas.

“Don’t build ambitious software projects in SC” – as advice for beginners, sure. There are bigger questions here about the future of SC, but perhaps not in this thread.

“a rage-filled diatribe about the default Event type” – well… ok… you did say it was opinionated. I’ll be equally opinionated and say that it is very, very nice to be able to think in terms of scale degrees without having to write degreeToKey everywhere, and to play chords transparently, and to have notes released automatically. For example, rookie mistake with Routines and something that will not happen with the \note event type:

(
r = Routine {
	var synth;
	loop {
		s.bind { synth = Synth(\default, [freq: exprand(200, 800)]) };
		0.3.wait;
		s.bind { synth.release };
		0.1.wait;
	}
}.play
)

r.stop;  // 3 in 4 chance of a stuck note now

In my … opinion… then, there’s a bit of throwing out the baby with the bathwater going on here. (For instance, you’re annoyed with SC drone-a-ramas but also discouraging users from looking into a framework that makes it easier, in the long run, to handle rhythm and harmony. Harmony and rhythm are major factors in my SC music and they might not be, if I had been told early on to stay away from patterns and events. They also might not be, if I were using Pd or Max where there’s no built-in support for what SC events do.)

It is fair to say that the documentation doesn’t warn you sufficiently about gotchas and I’m glad to see some ideas here about rectifying that. However, I think we as a community might want to be careful about bandying terms like “disaster” around (not Nathan’s term, in fairness) – it may unwittingly become a counterincentive to future contributions.

hjh

2 Likes

yes please!!! Happy to be a verbose student on anyone’s drafts for those.

I don’t have any issue with the pattern system at all! I think you should expect supercollider to try and fill in the blanks for you when you pass something to a Pbind or call play. It is an excellent system.
My issue is specifically about object prototyping for purposes beyond calling play. Basically thinking about them like a sort of class.

Consider this…

a = (\numChannels: 4)
a.numChannels == 1

I know you can use squares to get around this, but the docs and the general ergonomics of the language seem to suggest that using the dot notation is, not only, expected but a good idiomatic way to write in supercollider - the previous experience and expectations one might have of writing supercollider leads to this incredibly hard to detect bug. That isn’t good for some one who is new to programming, who has just learnt about instance methods and is now punished for correctly (from their perspective) using them. In a way, its not really about Event, but the large number of methods that Object implements and how they creep into unexpected places, particularly when writing classes. Having some method add functionality to your event when you pass it in is expected, getting a different result from what you explicitly state is surprising and requires you to learn every method from Object all the way down to Event. That is why I’d consider writing object prototyping unusable for beginners (which is what this thread is about).

1 Like

Yeah, that’s a big issue.

Generally, the Class library follows an OOP style where every class, including the toplevel Object class, implements hundreds of methods by default. This is fundamentally incompatible with the concept of duck typing used in the Event prototyping system.

Now, every OOP system (in a dynamically typed language) needs to define some special methods/properties, but typically it either hides them from the user or uses special naming conventions (e.g. double underscores). Of course, it is nice that you can call .size or .value on any object and get a meaningful result, but as we have seen, it also has its downsides. (Actually, you can emulate SC’s style with duck typing, but less ergonomically.)

I don’t think there is a real solution to this, other than not mixing two fundamentally incompatible OOP styles in the same language :slight_smile:

(I could go into a long rant about SuperColliders OOP system in general, but I won’t :slight_smile:

…two fundamentally incompatible OOP styles in the same language…

I wonder, there’s “self includes: Smalltalk”

and “implementations of prototyping systems”

&etc.

Also, a tiny bit of syntax can make working with ‘foreign’ objects nicer, i.e.

  • p⋄q(r…) → p.performMessageSendWithDelegationSemantics(‘q’, [r…])

Ps. With regards (size: -1).size == 1 and intercepting doesNotUnderstand, a ProtoObject that understands very little (as many Smalltalks have) is quite useful, see:

Squeaks ProtoObject understands 25 messages, and most have rather specific selectors!

  • cannotInterpret: doesNotUnderstand: ifNil:ifNotNil: pointsOnlyWeaklyTo: tryPrimitive:withArgs: &etc.
1 Like

There’s the part where if conditionals aren’t allowed in Synth. One workaround is to use select. But another method is to lay out all the allowed outputs and use a binary flag to select between outputs. If there are 3 outputs, then the valid binary representation is 000,001,010,011 … etc. And to get the appropriate flag is a (flags & flagposition)>> position, and so(101 & 010 )>>1` will be 0, which shows the flag at the second position is off. It’s faster than having an array of flags.

Another thing I was trying to figure out, was if it was possible to get a Synth to remember state, since a synth only takes in input, processes and outputs. To do that, feed it into a bus that is reread by the Synth. The bus becomes a memory buffer for the synth.

On the other hand, another way would be to use sendTrig to send data to the client from the server and have some kind of logic on the client instead.

Another opinionated advice is the tutorials seem to imply the valid method of creating voices, is midi -> create synth -> synth is destroyed. But, the traditional physical method is to create a directed graph of oscillators, filters, adsr, gates, lfo. An example of how to do this would be helpful.

i for one welcome our slow-moving atmospheric dronescape overlords :slight_smile: my advice for beginners is - if bad ambient music in SC is easy to make then that might be a good place to start because it is easy - everyone has to start somewhere

3 Likes

Regardless of my personal aesthetic preferences (and trauma from sitting in concert halls and being subjected to pure sine tones, and not in a cool Radigue or Lucier way), my advice is to at least experiment with making something that isn’t drone, even if your calling is to make that kind of music. Working in styles that are, on average, more laborious to make will help flex SC muscles.

1 Like

Please allow me to +1 this and thank James again for MixerChannel. I periodically get the feeling there’s a world of pain i never have to think about because I, early on and more or less accidentally, decided to rely on MixerChannel for … all that stuff.

Cheers James!
eddi

@jamshark70 Thank you very much!

@jordan

Could you give an example? It is hard for me to find an example of the benefits of using thisProcess.interpreter.compileFile(path).().