Ever incrementing time loop

I want to create a ‘time loop’ in which a series of events occur. I’m slightly intimidated about how to approach this in SC. In essence I want a ‘master clock’ that will make sure that over time things do not drift out of time.

Having learned a bit of Processing/Javascript, I’m still really struggling with getting my head around not using for/while loops regularly. I know the following code is wrong, but I don’t know why. The idea is the loop starts at 5 seconds, then each time it runs it gets one second longer…

x = 5;
TempoClock.default.sched(1, { x + 1; x.postln; });

I also want eventually for it to stop growing at a point. Let’s say 10 seconds. In Processing/Javascript this would be managed by a for loop, but I feel like I’m barking up the wrong tree by thinking like this in SC.

Any help would be greatly appreciated. I’m trying to avoid disappearing down a rabbit hole of misconceptions.

Not at all – looping structures can follow timing when they are written into a Routine or Task.

http://doc.sccode.org/Tutorials/Getting-Started/15-Sequencing-with-Routines-and-Tasks.html

hjh

Could you elaborate a bit more on this? What specifically are you trying to do?

Task

Here is an implementation using Task, I have assumed you don’t want to wait for 10 seconds after the final event.

t = Task({
	"starting task".postln;
	5.do { |n|
		var waitTime = 5 + n;
		postf("doing the %th thing then waiting for %\n", n, waitTime);
		waitTime.wait
	};
	"ending task".postln; // put last action here.
})
t.play
t.stop

and the output

starting task
doing the 0th thing then waiting for 5
doing the 1th thing then waiting for 6
doing the 2th thing then waiting for 7
doing the 3th thing then waiting for 8
doing the 4th thing then waiting for 9
ending task

Your code

First you aren’t changing x, it should be:

{ x = x + 1; x.postln; }

Second to stop the scheduler, the docs say ‘Returning nil will stop the task from being rescheduled.
so you can do this…

x = 5

TempoClock.default.sched(1, {
	x = x + 1; 
	x.postln;
	if(x < 10, x, nil)
});

Patterns

Its also worth mentioning Patterns here - but only if you are trying to change a synth, check out the pattern guides here -
Streams-Patterns-Events/A-Practial-Guide, then you could do this

\dur, Pseq((5..10)) 

That is the first scheduling delta. After that, the function is being re-scheduled, and this time delta comes from the function’s return value.

hjh

Opps, my mistake - edited

The base class has this which doesn’t mention returned values

.sched(delta, item)

Schedule the task at delta seconds relative to the current time, as defined by the drift argument of the constructor.

Regardless of what time a task is scheduled, it will only be awaken the next time seconds is set.

Thanks Jamshark - a nice pointer in the right direction, I’m looking into this now.

Could you elaborate a bit more on this? What specifically are you trying to do?

Sure. I want to take a sample and cut it into x number of slices. Then I want to start by having a loop which equals the length of the sample. The start of that loop could be call 0.

At first all the slices play from point zero, but then the idea is the gradually drift back to their original positions, reassembling the original sample.

So for example if x = 10, the first slice will always stay at point 0 of the loop, whereas the 10th slice will move most quickly away from 0 to reach its original position.

To visualise this is the start

1
2
3
4
5
6
7
8
9
10

… after the process is complete…

1 2 3 4 5 6 7 8 9 10

So the reason I’m talking about a master clock, is that my plan is to have one ‘master’ loop trigger a whole series of parts of the sample that become increasingly delayed, until that resume their original positions. The master clock keeps everything in time, and recognises that the size of the loop will gradually augment, until it has reached its original size.

I hope this makes sense. It’s just been swimming around my brain and I haven’t tried to communicate it to anyone yet.

Here is an implementation using Task, I have assumed you don’t want to wait for 10 seconds after the final event.

Thank you for this. It looks close to what I’m aiming for. Although I presume a for loop might suit my application more easily than do.

Thank you also for the corrections Jordan. Much appreciated. I will revisit the Patterns literature. I’ve read it before, but this current task might help me make better sense of it.

Appreciate your help!

So you should definitely have a go at using Task for this as it will give you lots of flexibility… but you can just do the whole thing on the server… I’ve assumed you are splitting into equal slices, which isn’t the most musical idea, but it does work quite well. This could also be improved by adding some overlap.

Move mouse from left to right slowly, if you jump to the end, it sort of takes of moment to sort itself out.


s.options.memSize = 8192 * 32; // if you have a lot of slices, this might need to be increased
s.boot;
~b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

SynthDef('drift', {
	var numSlices = 8;
	var positionsInBuf = numSlices.collect{ |n| n / numSlices };
	var timeOfEachSlice = BufDur.kr(~b) * numSlices.reciprocal;
	var lerpFactor = MouseX.kr();
	var lerp = 0.blend(positionsInBuf, lerpFactor);
	var triggerPerLoop = Impulse.kr( max(lerp.last * BufDur.kr(~b), timeOfEachSlice).reciprocal );
	var delayTime = lerp * BufDur.kr(~b) ;
	var triggerForSlice = DelayN.kr(triggerPerLoop, BufDur.kr(~b), delayTime.poll);
	var snd = GrainBuf.ar(
		1,
		trigger: triggerForSlice,
		dur: timeOfEachSlice,
		sndbuf: ~b,
		pos: positionsInBuf,
	);
	Out.ar(0, (Mix.ar(snd).tanh)!2) // watch ears when lerpFactor == 0
}).add;

t = Synth('drift')

Actually, n.do is the equivalent of the C style for loop.
Although you should try to think in terms of: collect which takes one collection and transforms it into another, reduce which turns a collection into a single value, inject which pipes together function, and do which will do something for each element (or n times) [but its best to avoid do when one of the others will suffice].

The Getting Started tutorial chapters on scheduling do mention it.

http://doc.sccode.org/Tutorials/Getting-Started/14-Scheduling-Events.html#Caution

You may be right that it should be mentioned here too. (But, Scheduler is not the base class for typical musical scheduling.)

hjh

You know, I’ve never read all those in detail, think I might do that this weekend!

Wow, Jordan, thanks for taking the time to create the code, share it, and provide a description. I’m very grateful. It’s very clearly written, but I’m still trying to get my head around a couple of parts of it.

I’ve assumed you are splitting into equal slices, which isn’t the most musical idea, but it does work quite well.

It was my intention - for the time being - to have things sliced equally, yes. It’s very impressive. I can’t quite reconstruct the original sample, but I’m guessing the overlap might help resolve that.

I’ll try to do as much work over the coming days in research, and to make full sense of your code.

Thanks again!

Btw: Of scheduling documentation, here is the correct place to look for it:

http://doc.sccode.org/Classes/TempoClock.html#-sched

Schedules a task to be performed delta amount of beats after the current Thread’s logical time…

When the scheduling time is up, the task’s awake method is called. If the method returns a number, the task will be rescheduled for the time equal to the last scheduling time plus the returned value.

So it is already documented in the reference, not only in the tutorial.

I can understand why one would land on the Scheduler class when searching for scheduling – but, in practice, I’ve never seen anyone actually use this class, because the clock classes are easier. A better search term would be for sched or play as method selectors.

hjh

Oh yeah! It’s not even the base class of TempoClock, dunno how I missed that :person_facepalming:.
To be fair, I don’t think I’ve actually used either in a project - task or routine is normally easier.

x = -1;

Routine run: { if ( x != 10 ) { yieldAndReset ( x = x + 1 ) } { "done".inform } }

The only way for a thread to reset is by calling yieldAndReset from within the thread (Routine, Task, etc.)

yield and wait create delay intervals throughout the progression of a thread.

There are three types of clocks:

  • SystemClock
  • AppClock (required for graphical code)
  • TempoClock.allSubclasses.printAll

There’s also a default TempoClock… certain methods allow one to write TempoClock (a class) as a shorthand alias for TempoClock.default (an object).

This sort of syntax behaviour is rather unique… and with certain methods, the list in TempoClock.openHelpFile or in the online documentation.

Also… to calculate beats per minute:

TempoClock.default.tempo_(128/60) // 128 bpm

128 divided by 60 is a little more than 2TempoClock.tempo = 2 would make everything run exactly twice as fast.

It’s very practical to make all delay (.wait) times as being either integer 1 or 0.1 and then adjusting the clock’s .tempo.

You can also call reset on the thread, from outside the thread itself. I’d say this is more typical.

Depending on the use case. “Standard” usage would be to set the beats per second, and then wait a desired number of beats (0.5 = 8th note, 1/3 = triplet etc)… but quote marks for the word “standard” because abuse of the technology is part of the spirit of SC :grin:

hjh

Would you be able to expand this comment with a brief example?

Or perhaps point (provide a link) to one which you know of highlighting this usage case?

I never quite fully understood how to make use of time intervals in .beats from .tempo

It’s extremely simple.

If, for example, I want to make a techno track at 132 bpm, then I set the tempo to 132 bpm * 1 min / 60 s = 132/60 bps.

Now a beat value of 1 is exactly the right amount of time for a quarter note. So the 4-on-the-floor kick drum would simply use a time delta = 1.

A hi-hat in 8ths would take time delta = 0.5.

Note that I haven’t discussed any time values in seconds. That’s because the point of TempoClock is to let you think in terms of musical time units without having to bother with conversion at all. (The one case where conversion would be needed is if you need the server to count some time value in seconds, but based on a known number of beats. But this is also not at all difficult: seconds = beats / tempo. In several patterns in my code base, I have something like Pkey(\dur) / Ptempo() where Ptempo is in my ddwPatterns quark and just returns this thread’s clock’s tempo.)

In short, you don’t make use of “beats from tempo.” You orient all of the time handling toward beats (“think in beats”), and let the clock perform it for you (edit: if it’s appropriate for the use case to think in terms of beats – if it makes more sense to think in seconds, then just set tempo = 1 and roll with that).

hjh

This is tremendously insightful, thank you so much.

Just to be crystal clear… how exactly in one line to make a track at a given bpm,

TempoClock.default.tempo_(128/60) // 128 bpm

and: 1.wait will wait exactly one quarter note.

Could you perhaps write a very quick function that would help?

I have to admit here that I have no idea how a function would help, because the usage is already just about as convenient as it could possibly be: set tempo (once) in the preparatory block of code, and after that, rhythmic values orient around a scale in which 1.0 represents one beat.

There’s an example like this in the tutorial series – last example in this page: 16. Sequencing with Patterns | SuperCollider 3.12.2 Help (actually it’s my example :grin: ). I’ll admit that it doesn’t directly explain the rhythmic values, but it does include a cheeky pedagogical question:

// before you play:
// what do you anticipate '\delta, 1' will do?
k = Pbind(\instrument, \kik, \delta, 1, \preamp, 4.5, \amp, 0.32).play(quant: 1);

… which would lead to the inference that 1 is being defined in this example as a rhythmic value which westerners would commonly call a quarter note.

hjh