Untouched earlier custom classes/Extensions now broken

Hello everyone,

first some quick context: I have been using SuperCollider for well over 10 years, and am now trying to continue development in it while using the latest version (now using: 3.12.0, on macOS 11.5.1).

However, classes (defined in Extensions) which I have written, thoroughly tested and long used without problems no longer function.

I seem to have isolated the problem, or at least part of it, in the following example code:

START OF SEPARATE FILE “MyPrimitives.sc” (EXTENSION):

MyPrimitives
{
  pulse_bipolar_lim
  {
    arg a_freq = 100.0,
        a_pWidth = 0.5;

    var a_pWidth_clipped = Clip.ar (in: a_pWidth, lo: 0.0, hi: 1.0);

    var uGenGraph = Pulse.ar (freq: a_freq, width: a_pWidth_clipped, mul: -1.0);

    ^uGenGraph;
  }
}

END OF SEPARATE FILE

START OF MAIN CODE:

Server.default = Server.local.boot;

~myPrimitives = MyPrimitives.new;

(
  ~testSynth = SynthDef
  ( "testSynth",
    {
      // Yields an output tone:
      var out_sig = ~myPrimitives.pulse_bipolar_lim ( );

      // Adding this line yields a silent crash ("Server 'localhost' exited with exit code 0."):
      out_sig = out_sig + ~myPrimitives.pulse_bipolar_lim ( );

      Out.ar (bus: 0, channelsArray: 0.04 * out_sig);
    }
  ).play;
)
~testSynth.free;

END OF MAIN CODE.

I of course hope somebody can shed light on this. It is no overstatement to say that all my existing code is broken and that I cannot write new code in SuperCollider.

With kind regards,
Staas.

I suspect a rate mismatch between Clip.ar and the scalar default inputs. Some UGens, at audio rate, expect audio rate input.

I’m not at my computer now, but you can look in the class library for usages of methodSelectorForRate, to match the UGen’s output rate to the input(s).

That’s only a guess – I haven’t tested.

hjh

OK, here’s what I get:

(
f = {
	arg a_freq = 100.0,
	a_pWidth = 0.5;
	
	var a_pWidth_clipped = Clip.ar (in: a_pWidth, lo: 0.0, hi: 1.0);
	
	var uGenGraph = Pulse.ar (freq: a_freq, width: a_pWidth_clipped, mul: -1.0);
	
	uGenGraph;
};
)

a = { (f.value + f.value) * 0.01 }.play;

^^ crash, as you indicated (so the function reproduces the crash).

(
f = {
	arg a_freq = 100.0,
	a_pWidth = 0.5;
	
	// ohhhhh wait... the easier way is to let the interpreter
	// choose the right 'clip' implementation
	var a_pWidth_clipped = a_pWidth.clip(lo: 0.0, hi: 1.0);
	
	var uGenGraph = Pulse.ar (freq: a_freq, width: a_pWidth_clipped, mul: -1.0);
	
	uGenGraph;
};
)

a = { (f.value + f.value) * 0.01 }.play;

^^ no crash

IMO it was always fragile to force Clip.ar, when you are not guaranteeing an audio-rate input for it. Better practice to use the clip operator and let SC promote it to a UGen only when needed.

hjh

1 Like

Thanks for your attention! This certainly clarifies what is going wrong. I’m still a bit surprised that the same code after some specific past update of SuperCollider must have stopped working.

But this hardly matters: I can now continue again, and am happy to be doing so following your detailed and educational advice.

Thanks again, also for the rapid response,
Staas.

If something didn’t crash before, and it crashes now, it does matter.

The catch is – I can’t figure out how your code didn’t crash before.

The most immediately relevant part of the code to check is the UGen C++ code for Clip, because this is where it crashed. That’s been stable for about 10 years (most of it for 12).

Clip.ar in the language has been stable for 19 years.

So we can rule out code changes.

Here’s the most likely scenario I can think of:

11 years ago, there is a change in the log “Clip - re-enable simd optimization.” This means that for audio rate Clip, it will use CPU vector opcodes instead of running a normal C loop.

This means that the signal input must be audio rate. Clip.ar(Something.kr()) produces a nasty buzzing noise because the 64-sample control block contains garbage (except for the first sample, supplied by the kr unit). Clip.ar(number) crashes.

It’s possible that you were using a build in the past where SIMD was disabled, without realizing it. Then, when you upgraded, the new build enables SIMD.

I think the bug is that the class library doesn’t check the input rates. What you were trying to do should have been disallowed from the beginning – using input.clip(lo, hi) is a better practice, since it chooses the output rate for you.

If I add this to InRange (the class definition):

	checkInputs {
		if(rate == \audio) { this.checkSameRateAsFirstInput }
	}

Then the crashing example posts “ERROR: Clip first input is not audio rate: 0.5 scalar” and 1/ no crash (because it doesn’t play) and 2/ it tells you why it didn’t play.

EDIT: I just logged InRange/Clip/Wrap/Fold need to check audio rate · Issue #5547 · supercollider/supercollider · GitHub

hjh

1 Like

Thanks a lot for initiating a general and graceful fix for this in SuperCollider itself, and of course also for this forensic examination leading to what must be the root cause. It then probably must have been this SIMD optimization being re-enabled “under the hood”. But this happened already so many years ago, of course…

…I can possibly clarify that from my side. First of all: “If something didn’t crash before, and it crashes now, it does matter.”

I really appreciate this attitude a lot (its application is quite evident from the above :slight_smile: ) and in a certain way, it may explain the time gap: My main focus with SuperCollider is and has been a system for fingertip haptics controlling musical sound, with both computed in real time. This system has many hard- and software components, closely integrated. Just to finish building it while also keeping it running meant the “crashes matter!” attitude in my case translated in, among other things, freezing versions of component programming languages used.

The more complex and layered the code for audio synthesis and haptic DSP necessarily became, the more I feared the moment of updating. I had no idea it must have been so long :slight_smile:

(Just in case you’re interested, here is a demonstration video of the system - it’s quietly paced and does take between 7 and 8 minutes to watch: Ghostfinger demo video - YouTube )

Cheers and thanks,
Staas.

1 Like