Simple visual sequencer example?

Hi @jamshark70 ,

I’ve been working on the project some more and everything is going great. There is one issue though, that I would love to get resolved:

I want to be able to stop/start the player (and make it ‘reset’ to time zero, after it is stopped). Currently the scheduling algorithm is working on the TempoClock default instance (or currentThread.clock), and as you know the player is based on the clock.beats to schedule the notes. I tried to replace the clock in your original code with a TempoClock instance, but whenever insert more than one note, everything get’s shifted in time, so the reassigning of player-part is not functioning correctly I believe:

// swap out stream players
clock = p.clock;
p.stop;
p = EventStreamPlayer(~stream);
clock.schedAbs(reschedTime, p.refresh);

Do you have any idea how to get this working with a TempoClock instance?
You can test it out by running code below (your original code but with a TempoClock instance, and then run the latest two lines consecutively)

(
var patternDur = 4,
baseBarBeat;

~myClock = TempoClock();

a = LinkedList.new;

[
	(degree: 0, time: 0),
	(degree: 1, time: 1),
	(degree: 2, time: 2),
	(degree: 3, time: 3)
].do { |item| a.add(item) };

// can't just Pseq because we need to account for insertions
~pattern = Prout { |inval|
	var item, node,
	clock = ~myClock,
	barline = clock.nextBar,
	nextTime, delta;
	// the rescheduling function needs to be aware
	// of the most recent phrase boundary for this thread
	baseBarBeat = barline;
	// nodeAt is "private" but we need access to the chain
	node = a.nodeAt(0);
	while {
		item = node.tryPerform(\obj);
		item.notNil
	} {
		// wait until next time
		nextTime = barline + item[\time];
		if(clock.beats < nextTime) {
			inval = Event.silent(nextTime - clock.beats).debug("rest").yield;
		};
		[clock.beats, item].debug("got item");
		// now update counters and do this one
		if(node.next.notNil) {
			delta = node.next.obj[\time] - item[\time];
		} {
			delta = patternDur - item[\time];
		};
		if(clock.beats + delta - barline >= patternDur) {
			barline = barline + patternDur;
			baseBarBeat = barline;
		};
		inval = item.copy.put(\dur, delta).yield;
		node = node.next;
		if(node.isNil) { node = a.nodeAt(0) };  // loop
	}
};

~stream = ~pattern.asStream;

// functions to change sequence
~reschedule = { |time|
	var phaseNow, reschedTime, nextToPlay, clock;
	phaseNow = (p.clock.beats - baseBarBeat) % patternDur;
	phaseNow.debug("phaseNow");
	nextToPlay = a.detect { |item| item[\time] >= phaseNow };
	nextToPlay.debug("nextToPlay");
	if(nextToPlay.notNil) {
		reschedTime = baseBarBeat + nextToPlay[\time];
	} {
		// next "pattern barline":
		reschedTime = (p.clock.beats - baseBarBeat).roundUp(patternDur);
	};
	reschedTime.debug("reschedTime");

	// swap out stream players
	clock = p.clock;
	p.stop;
	p = EventStreamPlayer(~stream);
	clock.schedAbs(reschedTime, p.refresh);
};

~insertNote = { |time, degree|
	var node, new;
	// search for place to insert
	node = a.nodeAt(0);
	while {
		node.notNil and: { node.obj[\time] < time }
	} {
		node = node.next;
	};
	new = LinkedListNode((degree: degree, time: time));
	if(node.notNil) {
		// change A --> C into A --> B --> C; B = new; C = node
		new.prev = node.prev;  // B <-- A
		node.prev.next = new;  // A --> B
		new.next = node;  // B --> C
		node.prev = new;  // C <-- B
	} {
		new.prev = a.last;  // add at the end
		a.last.next = new;
	};

	~reschedule.(time);
};

~deleteNote = { |time, degree|
	var node, next;
	// search for node to delete
	node = a.nodeAt(0);
	while {
		node.notNil and: {
			node.obj[\time] != time and: { node.obj[\degree] != degree }
		}
	} {
		node = node.next;
	};
	if(node.notNil) {
		next = node.next;
		next.prev = node.prev;
		node.prev.next = next;
	};
	// not really necessary to reschedule
	// the Prout will add rests automatically
	// if the next note to play was deleted
};
)

p = EventStreamPlayer(~stream).play(~myClock, doReset: true);
~insertNote.(1.75, 8);
~insertNote.(1.33, 8);