I’ve been exploring @nathan’s tracker technique recently, also as documented recently in this live stream. I’ve got things working smoothly with only one voice playing back at a time.
What I’d like to do, though is have multiple voices, with independent play and stop control, going in parallel, and sync up the starting points of the loops. I haven’t yet been able to get this to work reliably, though I’ve gotten two voices simultaneously, though within the same Routine, and even that has been kind of flaky.
I seem to keep causing the server to become unresponsive, losing the ability to interact with it, hear sound, actually get Synths to play back etc. It fails silently though, because I can’t kill all nodes with cmd + .
and client side calculations continue to work as if there were nothing wrong, but no error messages, no unresponsiveness from the interface or locking up of anything…
I’ve read through the a bunch of help files, tutorials related to Routines and Streams, and various threads on here, but have seen no examples of several independent Routines running simultaneously, and no mention of doing it, or not doing it. This leads me to believe that it’s super obvious that it can’t be done without doing something else.
In reading, also I think I understand that a Routine becomes a Stream, and that there’s only one Stream in SuperCollider.This means that I can’t evaluate one block of code that’s a looping Routine, and then after evaluate another.
So far the best I’ve been able to do is use
a = s.makeBundle(nil, firstRoutine.play(quant: 1));
b = s.makeBundle(nil, secondRoutine.play(quant: 1), a);
to mostly work, however, while I’m able to quantize to the clock, I’m not able to actually sync the start of the loops reliably. I’m hoping maybe to stick some sort of quantization in place of (one of those) nils, but haven’t seen anything explicitly documented. Though, at this point, this is less of a concern – I haven’t had a chance to attempt to solve for that in earnest yet, as I keep haven’t to restart the software.
Also adding c = s.makeBundle(nil, thirdRoutine(quant: 1), b)
to this seems to cause trouble. Causing the unresponsiveness, though not always immediately, but definitely eventually. Sometimes it’s on playback, other times it’s after xRoutine.stop
, and reinitializing all the code again…
Is there a more reliable way to do this?
When losing the ability to interact with the Server like this, is there a way to get it back? I’ve tried s.reboot
, which just seems to hang, I’ve tried "killall scsynth".unixCmd
, which also doesn’t fix it, or allow me to reconnect. So I’ve been having to quit and restart the IDE.
And generally, everything seems brittle, for lack of a better word. Starting and stopping things a few times seems sort of bound to lock up the Server like this, though while that’s fairly predictable, exactly when isn’t. (Or of course, for me, why).
Is there a way to run multiple Routines like this? Or Tasks? I’m using the two classes somewhat interchangeably in this post. Eg, if it can be done with one but not the other, I’d be fine with that.
I’m hoping for the ability to not have to preplan so much, so unless there’s a simple way to add additional data into an already running Routine, it doesn’t seem practical for me to run all these parallel voices within one Routine capable of handling the entire parallel load. … It’s be great to keep playing without having to stop the sound.
Thanks!
Code below:
(
// initialization:
SynthDef(\kik, { |freq = 57, amp = 0.5|
var sig, env;
env = Env.perc(0.001, 0.5).ar(2);
sig = SinOsc.ar(XLine.kr(freq * 3.03, freq, 0.03));
sig = sig + (Decay.ar(Impulse.ar(0), 1) * 0.5).tanh;
sig = sig * env * amp;
OffsetOut.ar(0, sig ! 2);
}).add;
SynthDef(\snr, { |freq = 177, amp = 0.5|
var sig, env;
env = Env.perc(0.007, 0.25).ar(2);
sig = LFTri.ar(XLine.kr(305, freq, 0.02), 2) * 0.2;
sig = sig * Env.perc(0.001, 0.2).kr;
sig = sig + (HPF.ar(PinkNoise.ar, freq * 0.5) * 0.7);
sig = sig * env * amp;
OffsetOut.ar(0, sig ! 2);
}).add;
SynthDef(\hat, { |freq = 1400, amp = 0.5|
var sig, env;
env = Env.perc(0.01, 0.4).ar(2);
sig = LPF.ar(HPF.ar(WhiteNoise.ar, freq), freq * 2);
sig = sig * env * amp;
OffsetOut.ar(0, sig ! 2);
}).add;
~map = IdentityDictionary[$k -> \kik, $s -> \snr, $h -> \hat];
~beat = 0.5;
// simple testers
~rhythm1 = "k.";
~rhythm2 = "h.";
~rhythm3 = ".s";
a = Routine({
loop({
~rhythm1.do({ |char|
char.postln;
if(~map[char].notNil) {
Synth(~map[char]);
~beat.wait;
} {
~beat.wait;
}
})
})
});
b = Routine({
loop({
~rhythm2.do({ |char|
char.postln;
if(~map[char].notNil) {
Synth(~map[char]);
~beat.wait;
} {
~beat.wait;
}
})
})
});
c = Routine({
loop({
s.makeBundle(s.latency, {
~rhythm3.do({ |char|
char.postln;
if(~map[char].notNil) {
Synth(~map[char]);
~beat.wait;
} {
~beat.wait;
}
})
});
})
});
)
// run this:
d = s.makeBundle(nil, a.play(quant: 1))
// then this:
e = s.makeBundle(nil, b.play(quant: 1), d)
// for me that works, other than reliably being able to sync both loops
// running the below usually causes things to go haywire:
f = s.makeBundle(nil, c.play(~clock, 1), e)
////////
// or
// works on its own
a.play;
// but adding either of these screws things up...
// b.play;
// c.play;