Routines + passing arguments (also effecting assigning tempoclock with ".play")

Hello, I’m trying to get more than one Routine playing simultaneously, and to have one Routine be a main generator that sends arguments to other routines to use and execute. I’m not using Patterns (I find the workarounds to manage polymorphism too complicated), and just using Routines and play.

I’ve managed to develop a stream to play in one Routine, and get that to pass it’s duration argument into another routine, and play that “micro” or “sub” routine (which divides the duration argument and iterates through it, a kind of stutter micro-pattern). But for some reason it’s not quite acting as I expected - and instead of playing a synth four times (something I’ve hardwired temporarily whilst developing), it’s only playing three. I can’t figure out why!

I had also been playing the Routines using a permanent tempoclock (which I can adjust via cc control), but it doesn’t seem to use this tempoclock, and seems to default to another. Can anyone offer any tips as to why this unexpected routine behaviour is happening, and why the tempoclock is no longer responding in this setup? (it’s just a standard tempoclock setup, so it’s very much the code here which is causing the problem).

I’ve found the pattern approaches in supercollider quite hard and somewhat limited when trying to implement polymorphism (and I find a lot of the help files that try to address this really difficult to read), so for now this approach is the way I would like to try and figure out this problem, but I’m still very much searching for anything that will work! It’s informed a lot by Nathan Ho’s approach to Routines and using s.bind, and I generally prefer the flexibility of the language-side Routine approach, but I’ve hit a bit of a limit here as to what i can figure out myself. I come from a background in Pure Data, so I’m still trying to get used to the server- and language-side distinctions and limits. Thanks!

var rt1MicroSeq, rt1MainSeq;

SynthDef(\sine, {|freq=400|
	var sig, env;
	sig = SinOsc.ar(freq, mul: 0.1);
	env = EnvGen.kr(Env.perc(0.001, 0.1, curve: -4),doneAction: 2);
	Out.ar(0, sig*env);
}).add;

rt1MicroSeq = Routine({
	|inputDur|
	var internalList = List[(inputDur/8),(inputDur/8),(inputDur/8),(inputDur/8)];
	
	(internalList.size).do({
		|i|
		("PLAY SYNTH. Iteration # " ++ i).postln;
		s.bind{
			Synth(\sine,[\freq, ((i+1)*100)]);
		};
		internalList[i].wait;
		});
});

rt1MainSeq = Routine({
	var listA = Pseq([2, 3, 1],inf).asStream;
	3.do({
		var currentDur = listA.next;
		rt1MicroSeq.reset;rt1MicroSeq.play(t).(currentDur);
		s.bind{
			Synth(\sine,[\freq, 250]);
		};
	currentDur.wait;
		});
}).play(t);

)

For good or ill, when a Routine wakes up on a clock, the function argument / “inval” is reserved for clock time (IIRC) – you can’t use it for your own value. (When the routine is scheduled, the clock calls the routine – you don’t have control over the .next call, hence you don’t have control over what gets passed in.)

A couple of approaches:

  • Set inputDur in a shared variable, and the “consumer” routine reads the shared variable.
  • Or, if you really want to pass a value in, you could .play one routine, and that routine’s job would be to call .next on another routine playing the synths.

Also, “passed-in values” via .next need to be updated after a yield – Pattern Guide Reference 01: Pattern Internals | SuperCollider 3.12.2 Help – but this matters only for the second approach.

hjh

Thanks Jamshark, I was trying too hard to keep to local variables, so it’s good to know environment variables weren’t the worst way to approach this! Greatly appreciate your response!