How to prevent future release messages?

I stumbled upon this question today. Minimal example:

(
g = Group.new;
p = Pbind(\dur, 10, \group, g).play;
)
// run this before the note reaches its end. 
g.release; p.stop;

Using Groups as above solves the question of stopping long notes before their planned end point, and I understand that p.stop stops the EventStreamPlayer so that no further notes will be generated.

But at the end of the 10 beats the EventStreamPlayer – as expected – still attempts to release that note, and when it does not find the corresponding Node, we get a “FAILURE IN SERVER /n_set Node not found” message.

Is there a way to tell the EventStreamPlayer that we no longer care about future release messages and prevent it from sending them? (I don’t want to use the \type, \on as suggested in another thread as I may care about release messages at first, until I decide I want to stop an Event midway).

These threads touch on the topic, but I as far as I could tell this specific question is not answered there:


Thanks in advance for any hints.

Bruno

It isn’t the stream player, but rather the individual events that are sending the release messages.

The easiest fix is to modify the \note event type to wrap the release bundles in a [\error, -1][\error, -2] pair (to disable the error posting temporarily).

http://doc.sccode.org/Reference/Server-Command-Reference.html#/error

Here’s a usage example: https://github.com/jamshark70/ddwVoicer/blob/master/VoicerNode.sc#L179

I had requested \error to be added for Voicer, in fact – my point of view was: if you are requesting a node to stop, then it doesn’t matter if the node was there or not at the time of the gate-off message, because in both cases, the final state is the desired state (no node). Formally, the default behavior is correct – it’s asking for something to be done which can’t be done – but here, it’s good that we have an option for the user to say, “I don’t care about warnings for these specific messages.”

hjh

Thank you so much for the reply, I understand it better now! I am not used to reading and modifying source code for Classes, but here is what I did after looking at the usage example you provided. If there is a simpler way, I’d love to learn.

Instead of changing the \note eventType itself, I wanted to see if I could create a \note2 eventType that includes the change you suggest, but otherwise is identical to \note.

I looked into Event.sc and found this line and this line to be the relevant ones.

For the sake of documenting the thought process, after a few unsuccessful attempts I went back to Server Command help file to understand exactly how the bundles should be formatted. Writing the following short example helped clarify it for me:

// create three nodes
(
x = Synth(\default);
y = Synth(\default, [\freq, 550]);
z = Synth(\default, [\freq, 660]);
)

// stop them
s.freeAll;

// sending only release bundles throws "node not found" error messages as expected:
s.listSendBundle(0.2, [15, [x.nodeID, y.nodeID, z.nodeID], \gate, 0].flop);

// now wrapping bundles: nodes are not found, but error messages are not shown (desired behavior)
s.listSendBundle(0.2, ([#[error, -1]] ++ [15, [x.nodeID, y.nodeID, z.nodeID], \gate, 0].flop ++ [#[error, -2]]));

Then I basically copied the entire Function for the \note type (from Event.sc) into my new \note2 in a new document, replace the two relevant lines with code that ‘wraps’ release messages with the proper \error code:

(
Event.addEventType(\note2, {|server|
	var freqs, lag, strum, sustain;
	var bndl, addAction, sendGate, ids;
	var msgFunc, instrumentName, offset, strumOffset;
	
	// var schedBundleArray;
	
	freqs = ~detunedFreq.value;
	
	// msgFunc gets the synth's control values from the Event
	msgFunc = ~getMsgFunc.valueEnvir;
	instrumentName = ~synthDefName.valueEnvir;
	
	// determine how to send those commands
	// sendGate == false turns off releases
	
	sendGate = ~sendGate ? ~hasGate;
	
	// update values in the Event that may be determined by functions
	
	~freq = freqs;
	~amp = ~amp.value;
	~sustain = sustain = ~sustain.value;
	lag = ~lag;
	offset = ~timingOffset;
	strum = ~strum;
	~server = server;
	~isPlaying = true;
	addAction = Node.actionNumberFor(~addAction);
	
	// compute the control values and generate OSC commands
	bndl = msgFunc.valueEnvir;
	bndl = [9 /* \s_new */, instrumentName, ids, addAction, ~group] ++ bndl;
	
	
	if(strum == 0 and: { (sendGate and: { sustain.isArray })
		or: { offset.isArray } or: { lag.isArray } }) {
		bndl = flopTogether(
			bndl,
			[sustain, lag, offset]
		);
		#sustain, lag, offset = bndl[1].flop;
		bndl = bndl[0];
	} {
		bndl = bndl.flop
	};
	
	// produce a node id for each synth
	
	~id = ids = Array.fill(bndl.size, { server.nextNodeID });
	bndl = bndl.collect { | msg, i |
		msg[2] = ids[i];
		msg.asOSCArgArray
	};
	
	// schedule when the bundles are sent
	
	if (strum == 0) {
		~schedBundleArray.(lag, offset, server, bndl, ~latency);
		if (sendGate) {
			~schedBundleArray.(
				lag,
				sustain + offset,
				server,
				
				// original source code:
				// [15 /* \n_set */, ids, \gate, 0].flop,
				
				// my modification:
				([#[error, -1]] ++ [15, ids, \gate, 0].flop ++ [#[error, -2]]).postln,
				
				~latency
			);
		}
	} {
		if (strum < 0) {
			bndl = bndl.reverse;
			ids = ids.reverse
		};
		strumOffset = offset + Array.series(bndl.size, 0, strum.abs);
		~schedBundleArray.(
			lag, strumOffset, server, bndl, ~latency
		);
		if (sendGate) {
			if (~strumEndsTogether) {
				strumOffset = sustain + offset
			} {
				strumOffset = sustain + strumOffset
			};
			~schedBundleArray.(
				lag, strumOffset, server,
				// my modification, same as above:
				([#[error, -1]] ++ [15, ids, \gate, 0].flop ++ [#[error, -2]]).postln,
				~latency
			);
		}
	}
}
);
)

This seems to work as desired, but it breaks when strum is > 0:

// this works:
(
g = Group.new;
p = Pbind(
	\type, \note2,
	\dur, 5,
	\degree, Pwhite(0, 10) + [0, 2],
	\group, g
).play;
)
// run this before notes reach the end.
// As desired, no error message is shown.
g.release; p.stop;

// HOWEVER, it fails miserably when strum > 0 ;-)
(
g = Group.new;
p = Pbind(
	\type, \note2,
	\dur, 2,
	\degree, Pwhite(0, 10) + [0, 2],
	\group, g,
	\strum, 0.1
).play;
)

// even before running the line below, we can hear that the release mechanism is now broken -- notes never stop.
g.release; p.stop;

This last issue with the \strum I haven’t been able to figure out yet. Any tips appreciated.

Bruno

EDIT: looking further into that bit of source code I am seeing that, when strum is not 0, strumOffset, which is the second argument to ~schedBundleArray, becomes itself an Array, as opposed to a single offset value as in the similar line earlier (when the strum==0). I am also seeing that the function ~schedBundleArray internally calls the method schedBundleArrayOnClock, which can work with a SimpleNumber or an Array as receiver. So the issue has to do with how to properly format the bundle array including the #[error, -1] and #[error, -2] for this case when schedBundleArrayOnClock is being called on an Array of offsets. Have been trying a few ideas but no luck so far.

Definitely on the right track. Event.addEventType is the right way to add a new function.

It’s unfortunate that the event type functions are not well modularized – it’s necessary to replace the whole thing, rather than just replacing the release part. Maybe a future version…

Yes, that’s right… I hadn’t worked this out earlier.

This is complicated by the fact that SequenceableCollection:schedBundleArrayOnClock assumes that only single messages will be sent per offset value.

So I think it might be necessary to rewrite this entire branch:

		if (strum < 0) {
			bndl = bndl.reverse;
			ids = ids.reverse
		};
		strumOffset = offset + Array.series(bndl.size, 0, strum.abs);
		~schedBundleArray.(
			lag, strumOffset, server, bndl, ~latency
		);
		if (sendGate) {
			if (~strumEndsTogether) {
				strumOffset = sustain + offset
			} {
				strumOffset = sustain + strumOffset
			};
			bndl = [15 /* \n_set */, ids, \gate, 0].flop.collect { |msg|
				[#[\error, -1], msg, #[\error, -2]]
			};
			strumOffset.do { |offset, i|
				~schedBundleArray.(
					lag, offset, server, bndl[i], ~latency
				);
			};
		}

That points to an inconsistency in the method interfaces:

  • aNumber.schedBundleArrayOnClock(clock, [[... msg0...], [... msg1...], [... msg2...]]...) sends all of the messages as a single bundle, after the requested time offset.

  • anArray.schedBundleArrayOnClock(clock, [[... msg0...], [... msg1...], [... msg2...]]...) sends the messages individually as separate bundles, and there is no facility to replace any of the messages with an array to send as a bundle.

This inconsistency means that it’s impossible to use a single call to schedule all of the strum releases.

hjh

PS [#[\error, -1], msg, #[\error, -2]] is likely to be slightly faster than [#[\error, -1]] ++ msg ++ [#[\error, -2]] – not a big deal but consider it.

1 Like

Thank you! Very interesting rabbit hole I ended up going into… :slight_smile:
Learned a lot along the way. I will try this last revision tomorrow morning when I get to my work laptop.
Bruno

For the record, here’s the solution incorporating all of the tips above. Thanks James!

(
Event.addEventType(\note2, {|server|
	var freqs, lag, strum, sustain;
	var bndl, addAction, sendGate, ids;
	var msgFunc, instrumentName, offset, strumOffset;
	
	// var schedBundleArray;
	
	freqs = ~detunedFreq.value;
	
	// msgFunc gets the synth's control values from the Event
	msgFunc = ~getMsgFunc.valueEnvir;
	instrumentName = ~synthDefName.valueEnvir;
	
	// determine how to send those commands
	// sendGate == false turns off releases
	
	sendGate = ~sendGate ? ~hasGate;
	
	// update values in the Event that may be determined by functions
	
	~freq = freqs;
	~amp = ~amp.value;
	~sustain = sustain = ~sustain.value;
	lag = ~lag;
	offset = ~timingOffset;
	strum = ~strum;
	~server = server;
	~isPlaying = true;
	addAction = Node.actionNumberFor(~addAction);
	
	// compute the control values and generate OSC commands
	bndl = msgFunc.valueEnvir;
	bndl = [9 /* \s_new */, instrumentName, ids, addAction, ~group] ++ bndl;
	
	
	if(strum == 0 and: { (sendGate and: { sustain.isArray })
		or: { offset.isArray } or: { lag.isArray } }) {
		bndl = flopTogether(
			bndl,
			[sustain, lag, offset]
		);
		#sustain, lag, offset = bndl[1].flop;
		bndl = bndl[0];
	} {
		bndl = bndl.flop
	};
	
	// produce a node id for each synth
	
	~id = ids = Array.fill(bndl.size, { server.nextNodeID });
	bndl = bndl.collect { | msg, i |
		msg[2] = ids[i];
		msg.asOSCArgArray
	};
	
	// schedule when the bundles are sent
	
	if (strum == 0) {
		~schedBundleArray.(lag, offset, server, bndl, ~latency);
		if (sendGate) {
			~schedBundleArray.(
				lag,
				(sustain + offset).postln,
				server,
				
				// original source code:
				// [15 /* \n_set */, ids, \gate, 0].flop.postln,
				
				// modification (wrap release message with error-message-supression code)
				[ #[error, -1], [15, ids, \gate, 0].flop, #[error, -2] ],
				
				~latency
			);
		}
	} {
		if (strum < 0) {
			bndl = bndl.reverse;
			ids = ids.reverse
		};
		strumOffset = offset + Array.series(bndl.size, 0, strum.abs);
		~schedBundleArray.(
			lag, strumOffset, server, bndl, ~latency
		);
		if (sendGate) {
			if (~strumEndsTogether) {
				strumOffset = sustain + offset
			} {
				strumOffset = sustain + strumOffset
			};
			
			bndl = [15 /* \n_set */, ids, \gate, 0].flop.collect { |msg|
				[#[\error, -1], msg, #[\error, -2]]
			};
			// here instead of single call to ~schedBundleArray, we do individual calls iterating over the strumOffset array:
			strumOffset.do { |offset, i|
				~schedBundleArray.(
					lag, offset, server, bndl[i], ~latency
				);
			};
		}
	}
}
);
)

// Testing:

(
g = Group.new;
p = Pbind(
	\type, \note2,
	\dur, 5,
	\degree, Pwhite(0, 10) + [0, 2],
	\group, g,
).play;
)
// run this before notes reach the end. No error message shown as desired.
g.free; p.stop;


// Works with strum too thanks to modified call to ~schedBundleArray (through strumOffset.do) 
(
g = Group.new;
p = Pbind(
	\type, \note2,
	\dur, 2,
	\degree, Pwhite(0, 10) + [0, 2],
	\group, g,
	\strum, 0.1,
).play;
)
g.free; p.stop;