Norns SuperCollider Engine Question

Hi can anyone tell me what’s wrong with my SuperCollider engine that would cause Norns to throw a SUPERCOLLIDER ERROR at every boot?

// CroneEngine_GridKit
// Simple synth drum kit
// Drum algorithms from
// https://sccode.org/1-523
Engine_GridKit : CroneEngine {

	// Trigger kick voice. Value of trigger maps to voice amp
	var trig_kick = 0.3;
	// Trigger snare voice. Value of trigger maps to voice amp
	var trig_snare = 0.3;
	// Trigger hat voice. Value of trigger maps to voice amp
	var trig_hat = 0.3;

	*new { arg context, doneCallback;
		^super.new(context, doneCallback);
	}

	alloc {

		pg = ParGroup.tail(context.xg);

		SynthDef("kick", {
			|out = 0, pan = 0, amp = 0.3|
			var body, bodyFreq, bodyAmp;
			var pop, popFreq, popAmp;
			var click, clickAmp;
			var snd;
			// Body starts midrange, quickly drops down to low freqs, and trails off
			bodyFreq = EnvGen.ar(Env([261, 120, 51], [0.035, 0.08], curve: \exp));
			bodyAmp = EnvGen.ar(Env.linen(0.005, 0.1, 0.3), doneAction: 2);
			body = SinOsc.ar(bodyFreq) * bodyAmp;
			// Pop sweeps over the midrange
			popFreq = XLine.kr(750, 261, 0.02);
			popAmp = EnvGen.ar(Env.linen(0.001, 0.02, 0.001)) * 0.15;
			pop = SinOsc.ar(popFreq) * popAmp;
			// Click is spectrally rich, covering the high-freq range
			// You can use Formant, FM, noise, whatever
			clickAmp = EnvGen.ar(Env.perc(0.001, 0.01)) * 0.15;
			click = LPF.ar(Formant.ar(910, 4760, 2110), 3140) * clickAmp;
			snd = body + pop + click;
			snd = snd.tanh;
			Out.ar(out, Pan2.ar(snd, pan, amp));
		}).add;

		SynthDef("snare", {
			|out = 0, pan = 0, amp = 0.3|
			var pop, popAmp, popFreq;
			var noise, noiseAmp;
			var snd;
			// Pop makes a click coming from very high frequencies
			// Slowing down a little and stopping in mid-to-low
			popFreq = EnvGen.ar(Env([3261, 410, 160], [0.005, 0.01], curve: \exp));
			popAmp = EnvGen.ar(Env.perc(0.001, 0.11)) * 0.7;
			pop = SinOsc.ar(popFreq) * popAmp;
			// bandpass-filtered white noise
			noiseAmp = EnvGen.ar(Env.perc(0.001, 0.15), doneAction: 2);
			noise = BPF.ar(WhiteNoise.ar, 810, 1.6) * noiseAmp;
			snd = (pop + noise) * 1.3;
			Out.ar(out, Pan2.ar(snd, pan, amp));
		}).add;

		SynthDef("hat", {
			|out = 0, pan = 0, amp = 0.3|
			var click, clickAmp;
			var noise, noiseAmp;
			var snd;
			// Noise -> resonance -> expodec envelope
			noiseAmp = EnvGen.ar(Env.perc(0.001, 0.3, curve: -8), doneAction: 2);
			noise = Mix(BPF.ar(ClipNoise.ar, [4010, 4151], [0.15, 0.56], [1.0, 0.6])) * 0.7 * noiseAmp;
			snd = noise;
			Out.ar(out, Pan2.ar(snd, pan, amp));
		}).add;

		this.addCommand("trig_kick", "f", { arg msg;
			trig_kick = msg[1];
			Synth("kick", [\out, context.out_b, \amp, trig_kick], target:pg);
		});

		this.addCommand("trig_snare", "f", { arg msg;
			trig_snare = msg[1];
			Synth("snare", [\out, context.out_b, \amp, trig_snare], target:pg);
		});

		this.addCommand("trig_hat", "f", { arg msg;
			trig_hat = msg[1];
			Synth("hat", [\out, context.out_b, \amp, trig_hat], target:pg);
		});
	}
}

The engine is based on the simplest engine I could find in Dust, the PolyPerc engine used by the Awake script, and is my first attempt at writing an engine so I’m sure it’s something stupid.

Are you checking the SC REPL tab in maiden for errors?

I have been. Nothing has appeared there.

Two errors I see:

  1. This may have been a copy/paste error when moving your code into your post, but you’re missing the closing } for your class
  2. Variables in SuperCollider have to be declared with the var keyword. Your variable pg is never declared. Change line pg = ParGroup.tail(context.xg); to var pg = ParGroup.tail(context.xg);

I recommend following mimetaur’s guide for norns on mac (assuming you’re on mac) here: https://gist.github.com/mimetaur/18346a71f1444ec8bea98a0c3c6fa365 that way you can simply boot the SuperCollider mac app, and it’ll tell you if there are any errors (this is how I found the above two errors in your engine) :+1:

2 Likes

That was it! Don’t know why PolyPerc works, though, given that particular line is the same in that file.

Do you happen to know what this line is actually for?

I will have a look at that, too. I do have SuperCollider installed on my Macs, and did spend a while working on a project in SC a couple of months ago (though it doesn’t look like much “stuck”).

Developing on the Norns hardware itself, via Maiden seems quite smooth so, far, though.

PolyPerc declares var pg on the first line of the Engine definition: https://github.com/tehn/awake/blob/master/lib/Engine_PolyPerc.sc#L4. Inside a class, you can either have class-scoped variables, like pg is in PolyPerc, which you declare at the top of your class, or you can have function-scoped variables which you must declare at the start of the function.

“Groups” in SuperCollider are explained here: https://doc.sccode.org/Classes/Group.html TL;DR – it’s helpful to make collections of your synths and buses and etc, so that you can act on all of them at once (free them all at once, etc.). PolyPerc uses a “ParGroup” (parallel group), because the synths don’t route into each other (they’re each separate notes, for polyphony) so there’s no “order” within the group, they’re all in parallel.

1 Like

Ah, I see. Thanks for the explanation, @21echoes.

Given my drum synth has 3 voices, and there’s no routing between them, it makes sense for it to work in the same way, then, I guess.

How and where should I be freeing resourced from the group?

Are all 3 SynthDefs added to the group, or only the first one, in my example?

SynthDefs are never added to groups, Synths are. When you Synth("kick", [\out, context.out_b, \amp, trig_kick], target:pg);, you are adding that instance of the Kick synth to the group (note the target: pg).

Standard practice in Norns is to have an explicit free method in your Engine, like so:

  free {
    pg.free;
  }

However, your engine as written doesn’t need to do this, because you have doneAction: 2 in your EnvGen, meaning that your synth is auto-freed when the envelope finishes running.

Ah, I wondered about that. Thanks for the explanation!

Another quick question:

                this.addCommand("select_kit", "i", { arg msg;
			kit = msg[1];
		});
    
		this.addCommand("trig_kick", "f", { arg msg;
			trig_kick = msg[1];
			if(kit === 1, {
			  Synth("kick1", [\out, context.out_b, \amp, trig_kick], target:pg);
			}, {
			  Synth("kick2", [\out, context.out_b, \amp, trig_kick], target:pg);
			});
		});

Only the first SynthDef ever seems to get played, even when I set “kit” to something other than 1, ie here in the SC engine file itself

Engine_GridKit : CroneEngine {
  // Select kit
  var kit = 2;

Hmm, nothing sticks out to me as wrong there :thinking: You can debug by putting .postln in your code to be 100% sure which lines get executed:

this.addCommand("trig_kick", "f", { arg msg;
  trig_kick = msg[1];
  if(kit === 1, {
    "kit 1".postln;
    Synth("kick1", [\out, context.out_b, \amp, trig_kick], target:pg);
  }, {
    "kit 2".postln;
    Synth("kick2", [\out, context.out_b, \amp, trig_kick], target:pg);
  });
});

The output will show up in the sc tab in http://norns.local/

i don’t know either; maybe post the whole script in a gist or such, so we can check for scoping issues.

one thing is that you usually want the comparison equality == and not the identity equality ===

a = 1;
b = 1.0;
a == b;  // true; same value;
a === b; // false; different classes
[a.class, b.class].postln; // Integer, Float
a === b.asInteger; // true

in this case, msg is actually an array of strings. so you will want to coerce the OSC arguments to the numeric type you want if you are going to use strict comparisons

kit = msg[1].asInteger, etc

Turned out it was because I’d modified the engine file, but forgot I had to reset Norns before the changes took effect… doh…

Thanks very much for your assistance, anyway, guys. Much appreciated.

1 Like

That’s bitten me a few times as well :sweat_smile:. I really do recommend setting up engine development as much as possible on your laptop, and then sending to norns as a final QA sort of step. I resisted spending the overhead time getting set up on my laptop for a while, but once I did it was amazing how much faster I was able to develop once my edit -> test iteration time was closer to 1 second than 30 seconds :smile:

1 Like

A follow-up question: I tried to find a resource for this (how to set up a dev environment for norns on the desktop/laptop computer), but I’m still unsure about the best route to take. There’s the docker image repo, but it’s close to 2 years old: https://github.com/samdoshi/norns-dev.

that docker image is related to building the other components in norns. it’s also an old incomplete experiment that was never used or tested by the core development group.

you don’t need anything special except a supercollider installation to work on supercollider engines for norns. just use a class to encapsulate your basic synthesis / processing functionality.

2 Likes

I was just referring to the link I posted back earlier in the thread: https://gist.github.com/mimetaur/18346a71f1444ec8bea98a0c3c6fa365 All you have to do is a vanilla SC install, set up a few symlinks, and you’re good to go. The link also has examples for the syntax you can use to emulate norns’s engine messaging system

2 Likes

I did the setup last night too and it works great!

One thing that wasn’t super clear to me was that after setup you can simply treat the .scd file in the gist as if it were your .lua on norns (using SC language still) so you can add utility functions such as midi inputs and patterns etc.

Edit your engine from the SC IDE and hit recompile to refresh the script, then in the scd file parse the engine line again (plus any utility function you might need) and you’re good to go!

2 Likes

Yeah just got it working too. I made an engine out of a super-simple synthdef (“snare909.scd” from the Supercollider examples) with a couple of adjustable parameters/engine commands.

I wonder why I wasn’t able to see the class compile errors on Norns? That created some headaches first. But on the desktop debugging the engine was easy, and the method of sending OSC messages as described in the gist indeed works great. I could write a step-by-step guide for this (how to adapt a synthdef into an engine + testing & troubleshooting), especially if some more SC-savvy people could comment on it before publication?

7 Likes

That would be really useful, I think!

2 Likes