Scheduling Pattern - play, stop issue

Hi everyone,

I’d like to share a new question that’s come up as a result of the experiments I’ve been conducting on synchronization.

This post is somewhat related to the other two (link and link) I wrote recently on the topic of synchronizing audio file playback with patterns. Today I’ll try to break the problem down into smaller parts and address the one that’s been giving me the most trouble lately.

In short, we have a sort of score, consisting of patterns that are played upon receiving an OSC message. The score can also be interrupted by receiving another OSC message.

The base code is as follows:

// 1) boot the server
s.boot;


// 2) define part (and its internal components)
(
~intro_bars = 4;

~p_pause = Pbind(
	\instrument, \default,
	\degree, Pseq([\rest], inf),
	\dur, Pseq([4], 1),
	\amp, 1.0,
	\callback, Pfunc({ |e|
		"bar %: one bar pause\n".postf(t.bar.asInteger);
	})
);

~p_melody = Pbind(
	\instrument, \default,
   	\root, 0,
	\scale,  Scale.major,
	\degree, Pseq( (0..7), 1),
	\dur, 4,
	\octave, 5,
	\amp, 1.0,
    \legato, 0.9,
	\callback, Pfunc({ |e|
		e.use{
			"playng note %\n".postf( ~degree )
		}
	})
);

~my_part = Pseq([
	Pseq([~p_pause], ~intro_bars),
	Pseq([~p_melody], 1)
], 1).asEventStreamPlayer;
);


// 3) define the address where to send OSC messages
~my_addr   = NetAddr("127.0.0.1", 15600);

// 4 ) define OSC message receivers
(
OSCdef( \start, {
	| msg, time, addr, recvPort |

	t = TempoClock.new(110/60);
	~my_part.reset.play(t, quant:Quant(1, 0, 0));

}, "/player/start/", recvPort: ~my_addr.port );

OSCdef( \stop, {
	| msg, time, addr, recvPort |

	~my_part.stop;

}, "/player/stop/", recvPort: ~my_addr.port );
);

Now I can execute the score by evaluating the following line

~my_addr.sendMsg("/player/start/");

The output in the post window is as follows (as I expected):

bar 0: one bar pause
bar 1: one bar pause
bar 2: one bar pause
bar 3: one bar pause
playng note 0
playng note 1
playng note 2
playng note 3
playng note 4
playng note 5
playng note 6
playng note 7

During execution, I can always pause the playback by evaluating the line:

~my_addr.sendMsg("/player/stop/");

or by using the line ~my_addr.sendMsg(“/player/start/”); again to restart playback (on a new TempoClock).

So I tried evaluating the two lines simultaneously in this form:

(
~my_addr.sendMsg("/player/stop/", 0);
~my_addr.sendMsg("/player/start/", 1);
)

but I get results that are hard to interpret.

If I run these lines quickly, say twice, I get an output like this:

bar 0: one bar pause
bar 0: one bar pause
bar 1: one bar pause
bar 1: one bar pause
playng note 0
playng note 1
playng note 2
playng note 3
playng note 4
playng note 5
playng note 6
playng note 7

If I do the same thing by evaluating the lines repeatedly—this time three times—I get something like this:

bar 0: one bar pause
bar 0: one bar pause
bar 0: one bar pause
bar 1: one bar pause
playng note 0
playng note 1
playng note 2
playng note 3
playng note 4
playng note 5
playng note 6
playng note 7

And the musical notes play out of sync or in some other unexpected way. I would have expected the pattern to stop and start over, just as it did when the lines were evaluated separately.

Why is this happening?

The only way I’ve found to work around this problem is to let a little time pass between the pattern stopping and the new playback starting.

However, this is not the ideal situation because, in actual use—especially during the initial fine-tuning phase—it is necessary to be able to stop the execution and start over immediately, and to do so repeatedly.

I’m convinced that this approach to the design requirement isn’t the best method, but I thought it was certainly one of the possible methods, and I’d like to explore it further. I think the issue I’m facing has to do with pattern scheduling, but I’m not sure how to proceed.

I’m also not entirely convinced about reassigning a new TempoClock to the variable t every time I receive a start message (where does the old TempoClock go? Couldn’t that create conflicts?).

However, it’s the only way I’ve found to “reset” the TimeClock and start it over from the beginning (start counting beats from zero at an arbitrary and unpredictable point in time).

Do you have any other suggestions?
Thank you so much for your support

A somewhat opaque feature of SC scheduling is that scheduled items cannot be removed from a clock. When you stop a Routine or Task or EventStreamPlayer, it isn’t immediately and transparently un-scheduled. Instead, it remains on the clock. It will wake up at its scheduled time. “Stopping” a playing thread only sets the object’s state so that, when it wakes up, it will do nothing (including not rescheduling itself).

You should not re-play the object while it’s still on the clock. If you do, then, at its stop time, it will be in “waiting” state (when it should have been in “stopped” state) and it will continue running, at a time it wasn’t supposed to.

With Routines, there’s nothing you can do about it. With Task or EventStreamPlayer, you can transfer the contents to an all-new player and then it will work.

Next thing – do you want the pattern to resume in the middle, or to start over from the beginning? If the former, use the transfer trick. If the latter, simply discard the old player and make a new one. (“Recycling” and “avoiding waste” doesn’t apply to Task and EventStreamPlayer – these are truly disposable objects. You actually lose functionality by trying to recycle them.)

// .reset.play way
// (if you delete .reset, it will resume in the middle)
(
p = Pbind(
	\degree, Pseries(0, 1, inf),
	\dur, 1
).asEventStreamPlayer;
)

p.play;

(
var saveStream = p.stream;

p.stop;

p = EventStreamPlayer.new.stream_(saveStream)
.reset.play;
)


// if you want to reset, this is easier
(
p = Pbind(
	\degree, Pseries(0, 1, inf),
	\dur, 1
);
)

q = p.play;

// 'q = ' because q will be a new player
// the old player gets garbage collected
q.stop; q = p.play;

hjh

1 Like

Thank you @jamshark70

Thank you for helping me better understand the mechanism at work behind the scenes. What I’ve learned from this discussion is that, from a practical standpoint, it’s best to keep patterns and their respective players well separated. Furthermore, I can feel confident about not reusing players: I can easily instantiate new ones and let the others be automatically garbage collected.

In my particular case, I’m interested in restarting the pattern from the beginning. Therefore, following your suggestions, my code has been transformed as follows:

// 1) boot the server
s.boot;

// 2) define part (and its internal components)
(
~intro_bars = 4;

~p_pause = Pbind(
	\instrument, \default,
	\degree, Pseq([\rest], inf),
	\dur, Pseq([4], 1),
	\amp, 1.0,
	\callback, Pfunc({ |e|
		"bar %: one bar pause\n".postf(t.bar.asInteger);
	})
);

~p_melody = Pbind(
	\instrument, \default,
   	\root, 0,
	\scale,  Scale.major,
	\degree, Pseq( (0..7), 1),
	\dur, 4,
	\octave, 5,
	\amp, 1.0,
    \legato, 0.9,
	\callback, Pfunc({ |e|
		e.use{
			"playng note %\n".postf( ~degree )
		}
	})
);

~my_part = Pseq([
	Pseq([~p_pause], ~intro_bars),
	Pseq([~p_melody], 1)
], 1);
);


// 3) define the address where to send OSC messages
~my_addr   = NetAddr("127.0.0.1", 15600);

// 4 ) define OSC message receivers
(
OSCdef( \start, {
	| msg, time, addr, recvPort |

	t = TempoClock.new(110/60);
	~my_part_player = ~my_part.reset.play(t, quant:Quant(1, 0, 0));

}, "/player/start/", recvPort: ~my_addr.port );

OSCdef( \stop, {
	| msg, time, addr, recvPort |

	~my_part_player.stop;

}, "/player/stop/", recvPort: ~my_addr.port );
);

Now, after repeatedly evaluating the following lines one after another, I no longer encounter any problems.

(
~my_addr.sendMsg("/player/stop/", 0);
~my_addr.sendMsg("/player/start/", 1);
)

As for the TempoClock, do you think my current approach is okay? That is, reinitializing the TempoClock by overwriting the variable t?

Maybe I should also pause the timer inside the stop function, like this:

OSCdef( \stop, {
	| msg, time, addr, recvPort |

	~my_part_player.stop;
	t.stop;

}, "/player/stop/", recvPort: ~my_addr.port );

Thank you so much for your help

Every TempoClock runs its own C++ thread in the backend, so, creating a new TempoClock creates a new thread. Stopping a TempoClock releases the backend thread. So your approach now is leaking threads (which tbh isn’t a huge problem unless you’re doing it many many hundreds of times in a session – the threads get cleaned up when quitting or recompiling the class library anyway).

So I think it’s a good idea to stop the old clock, but not immediately. A note event’s release is also scheduled on the clock. Stopping the clock would cancel the release, leaving stuck notes.

But you also couldn’t just do { t.stop }.defer(5); because, after those 5 seconds, t will be the new clock. So I’d make a helper function that uses local variable scope to retain the old clock.

~stopClockLater = { |clock, delay = 5|
    { clock.stop }.defer(delay)
};

Then you can do ~stopClockLater.(t);, and change t as you like, and it won’t affect the clock passed into the function.

hjh

1 Like

Thank you so much @jamshark70