Norns: SuperCollider

Hi all.

Not sure if this is the best place to post this, but I’m struggling with getting SC code to run on the norns. I write in the SC IDE and then manually migrate over using the example engines. The following code throws an error after a reset, but I can’t see why. Any pointers would be very welcome.

// Engine_nornsBasicEngine

Engine_nornsBasicEngine : CroneEngine {
	var <synth;

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

alloc {
	SynthDef("nornsBasicEngine", {
		arg out, hz=220, amp=0.5, amplag=0.02, hzlag=0.01;
		
		var amp_, hz_;

		amp_ = Lag.ar(K2A.ar(amp), amplag);
		hz_ = Lag.ar(K2A.ar(hz), hzlag);

		Out.ar(out, (SinOsc.ar(hz_) + (50 * SinOsc.kr([50,51]))).dup);
	}.play(args: [\out, context.out_b], target: context.xg);

	this.addCommand("hz", "f", { arg msg;
		synth.set(\hz, msg[1]);
	});

	this.addCommand("amp", "f", { arg msg;
		synth.set(\amp, msg[1]);
	});
}

free {
	synth.free;
}

}

Before adding the second SinOsc (kr) it worked fine, so I’m guessing it’s something to do with that.

Please help, I’m desperate to start making my own sounds :slight_smile:

1 Like

Would be good to include the error, maybe in a [details] box?

here, you’re adding the output of an audio rate UGen (SinOsc.ar) to the output of a control rate UGen (SinOsc.kr), which is weird and will maybe cause an error (from channel mismatch if nt rate mismatch - the modulator is stereo due to the array arg)

i’m guessing you want to add the .kr ugen to the frequency argument of the first sinewave, inside the parens. the stereo modulator will then cause the .ar sinewave to expand to stereo and you probably won’t want .dup

1 Like

I don’t get any error details, just the audio engine error on the norns main screen.

Is there a way to get deeper details on SC errors? I’ll tweak as you suggest and report back.

Lua is starting to make sense. SC though… it’s a puzzling creature.

you should be able to see SC output from the SC tab in maiden. since you’re sending the synthdef on the fly at engine alloc time, if the error is there it should show.

i’d try
Out.ar(out, SinOsc.ar(hz_ + (50 * SinOsc.kr([50,51]))) * amp_)

1 Like

When resetting the norns after making changes to the engine (that is still a thing right?) the norns displays (error: AUDIO ENGINE) and the SC tab in maiden shows nothing. Your code makes sense but hasn’t changed this error.

if you have an error in your engine class that prevents compilation, rather than just an error in the synthdef, then SC will not start at all.

you can diagnose this by manually starting sclang from a serial or ssh connection (just type sclang) and getting the resulting errors on the terminal

i’d recommend developing your engine on desktop first. we’ve written a lot about this on the existing norns dev/help threads.

2 Likes

Ahh, perfect. Thank you that solved it. I’d missed a ) while making the SynthDef, after the } and before .play.

sorry, i should have spotted that.

goes to show, really, it’s better to use an IDE that checks braces, auto-indents &c, esp. as SC is relatively verbose. i like scel b/c it works nicely with tramp-mode to remote into norns directly. (but other editors have similar features too, andit’s probably helpful to use ScIDE when you’re getting started - has the best docs integration, autocomplete &c)

1 Like

After transferring the engine into norns via sftp is debugging as easy as launching /usr/bin/sclang in a ssh shell? Does it check all the engines in all the folders?

SC recursively scans these locations in addition to the defaults:

if you add class files, you have to restart the sclang interpreter for them to be compiled. there are a few options:

  • kill and restart sclang from a terminal (yea, afaik that’s the right exec path). this puts sclang I/O on the terminal. (be aware that the lua process might be confused after this, since it wants to initiate handshake with sclang on start.)

  • use ;restart from maiden SC REPL these put sclang I/O on websocket for use with maiden. (you should get compile errors on the SC REPL in maiden, but i think there are some cases where you won’t.)

  • equivalently, restart the norns-sclang systemd service and maybe also norns-matron if you want lua stuff to keep working (the crone and JACK processes should be fine.)

  • to further debug compilation without terminal access, try executing thisProcess.recompile from SC REPL (again, lua process will be confused.)

  • just reset the norns

2 Likes

Superb, thank you :slight_smile:

Now I have it working I’m having a blast, so thank you for your help. Next share will be a drone generation program, have some strange sounds coming out the speakers. It’s like the aliens have landed.

One thing, synth.free doesn’t free the synth for me if I declare a synthdef with a name, but does if I just use synth = {…

I wonder what the benefit of having names synths is?

dunno, SynthDef.new(...).play should return a Synth that can then assigned to a variable, freed/paused/modulated/&c. (the {...}.play method for Functions is a shorthand that adds some extra stuff to the def, like a gate argument.) so i’m not sure exactly what problem you ran into. (i think you mean that you expected SynthDef.new(\foo, {...}) to create a clif.ent-side variable named foo, which it doesn’t - t he id foo is a property of the synthdef object, and it will be registered with the synthdef on the server side. so it should be unique among all synthdefs.)

if you haven’t yet, it would be a good time to look over the SC docs that introduce client/server architecture and all this stuff

i don’t use SynthDef.play() much. the only time it seems better than reusing a synthdef is if you are somehow generating the synthesis graph on the fly.

it’s often both cleaner and more efficient to define, compile, and re-use a synthdef.

here’s an example where we make a single synthdef, then make a bunch of randomized instances of it, then manage the lifecycle of those synth objects.

// do everyting in a Routine so we can do timing stuff
Routine {
	
	// define a synthdef for later use. this is a basic 2-operator sinewave FM sound
	~def = SynthDef.new(\fm2, { arg out=0, amp=0.25, hz=110, rat=1.5, idx=2.0, detune = 0.25;
		var car, mod;
		mod = SinOsc.ar(hz * rat);
		car = SinOsc.ar(hz * (1 + (mod * idx)) + [detune, detune * -1]);
		Out.ar(out, car * (Lag.kr(amp)));
	});
	
	// send the def to the server for immediate compilation
	~def.send(s);
	
	// wait for the server to be done with async commands (like compiling the def)
	s.sync;
	
	// make a bunch of synths, each using the def
	n = 10;
	~synths = Array.fill(n, {
		var params = [\amp, 0.05, \hz, 110 * (10.rand), \rat, 2 ** 2.0.rand2, \idx, 4.0.rand, \detune, 1.0.rand];
		params.postln;
// this is where we supply the name of the def we made
		Synth.new(\fm2, params, s);
	});
	
	// wait, then free the synths one at a time
	2.0.wait;
	n.do({ |i|
		~synths[i].free;
		(1 + 2.0.rand).wait;
	});
}.play; // play the routine
4 Likes

Is there a good SC “template” to work from when creating a norns engine?

2 Likes

This is the one I work from when doing poly synths - obviously not official, just something I’ve used: https://gist.github.com/markwheeler/b88b4f7b0f2870567b55cbc36abbd5ea

5 Likes

looking for some help…

I’m working with something like the following - copied from the TestSine engine.

With how I have this now, I need to send a gate=0 message (noteOff below) or the tone will continue playing.

But… if I put the noteOn in a metro, I can’t figure out how to get the gate back to zero

Is there a way to get gate to default back to 0 each time it’s triggered (without sending the noteoff)?

		synth = {
			arg out, gate=0, hz=220, amp=0.5, amplag=0.02, hzlag=0.01;
			var amp_, hz_;
			amp_ = Lag.ar(K2A.ar(amp), amplag);
			hz_ = Lag.ar(K2A.ar(hz), hzlag);
			Out.ar(out, Gate.ar((SinOsc.ar(hz_) * amp_).dup, gate));
		}.play(args: [\out, context.out_b], target: context.xg);
		// noteOn(freq)
		this.addCommand("noteOn", "f", { arg msg;
			synth.set(\hz, msg[1], \gate, 1);
		});
		this.addCommand("noteOff", "", { arg msg;
			synth.set(\gate, 0);
		});

i don’t understand the question. you want a 1-shot envelope? if so, use a different envelope method. if not, why would you not send noteoff?


in fact i would in any case always use a proper envelope instead of a raw Gate.ar (unless you’re after the hard clicks.) SinOsc.ar() * EnvGen.ar(env, gate)

where env can be something like Env.perc or Env.linen for a oneshot, Env.asr or Env.adsr for sustained.

Apologies for my crappy/unclear question. I still don’t understand a bunch of what I’m trying here.

That code was not actually what I’m working with, but similar.

The Ugen I’m using has it’s own (1-shot?) envelope built in so I just need to trigger it.

But, unless I set gate back to zero I don’t get a second trigger.

cool, but maybe easier if you do post the actual code? :slight_smile:

wild guess, you are using something that expects a trigger input; viz., does a thing on zero->nonzero transition of input value.

you may want to look at TrigIn, which will fire a single impulse whenever a bus is set; amp of impulse == value.


~trig_b = Bus.control(s, 1);

{ SinOsc.ar(220).dup * 0.6 * EnvGen.ar(Env.perc(0.01, 0.1), gate:InTrig.kr(~trig_b.index)) }.play;


Routine { 5.do { arg i;
	postln("boop");
	~trig_b.set(1); // touching the bus will always make a trigger in the synth that reads it with TrigIn
	(0.2 * (i+1)).wait;
} }.play;

Ah… maybe I’ve not understood that there is a difference between trigger and gate.

So here’s the example method

Nes2Square.ar(trig: 0, dutycycle: 0, loopenv: 0, envdecay: 0, vol: 10, sweep: 0, sweeplen: 0, sweepdir: 0, sweepshi: 0, freq: 100, vbl: 0)

And then a bit of sample code (which works great in the SC IDE)

{Nes2Square.ar(Impulse.kr(2), 0, 0, 1, 10, 1, 2, 1, 4, MouseX.kr(0, 1023), MouseY.kr(0, 31))}.play

So then I’m trying to get that into a norns engine… so I have the following based on TestSine as an example:

Engine_NES2y : CroneEngine {
	// Nes2Square.ar(trig: 0, dutycycle: 0, loopenv: 0, envdecay: 0, vol: 10, sweep: 0, sweeplen: 0, sweepdir: 0, sweepshi: 0, freq: 100, vbl: 0)
		
	var <synth;
		
	*new { arg context, doneCallback;
		^super.new(context, doneCallback);
	}

	alloc {
		// Define the synth variable, whichis a function
		synth = {
			// define arguments to the function

			arg out, gate=0, dutycycle=0, loopenv=0, envdecay=0, vol=10, sweep=0, sweeplen=0, sweepdir=0, sweepshi=0, freq=100, vbl=0, pan=0, amp=1 ;
			var z;
			z = Nes2Square.ar(gate, dutycycle, loopenv, envdecay, vol, sweep, sweeplen, sweepdir, sweepshi, freq, vbl);
			Out.ar(out, Pan2.ar(z, pan));
			
		}.play(args: [\out, context.out_b], target: context.xg);

			
		// noteOn(freq)
		this.addCommand("noteOn", "f", { arg msg;
			synth.set(\freq, msg[1], \gate, 1);
		});
		this.addCommand("noteOff", "", { arg msg;
			synth.set(\gate, 0);
		});


		this.addCommand("dutycycle", "f", { arg msg;
			synth.set(\dutycycle, msg[1]);
		});
		this.addCommand("loopenv", "f", { arg msg;
			synth.set(\loopenv, msg[1]);
		});
		this.addCommand("envdecay", "f", { arg msg;
			synth.set(\envdecay, msg[1]);
		});
		this.addCommand("vol", "f", { arg msg;
			synth.set(\vol, msg[1]);
		});
		this.addCommand("sweep", "f", { arg msg;
			synth.set(\sweeplen, msg[1]);
		});
		this.addCommand("sweeplen", "f", { arg msg;
			synth.set(\sweeplen, msg[1]);
		});
		this.addCommand("sweepdir", "f", { arg msg;
			synth.set(\sweepdir, msg[1]);
		});
		this.addCommand("sweepshi", "f", { arg msg;
			synth.set(\sweepshi, msg[1]);
		});
//		this.addCommand("freq", "f", { arg msg;
//			synth.set(\freq, msg[1]);
//		});
		this.addCommand("vbl", "i", { arg msg;
			synth.set(\vbl, msg[1]);
		});
	}
	// define a function that is called when the synth is shut down
	free {
		synth.free;
	}
}

This is using the f0plugins from fredrik olofsson found here: