Crone / individual engines could fix this by scheduling note end events (e.g. .set(\gate, 0) with latency rather than immediately (which is generally though not always how Synths are scheduled). This effectively becomes a minimum note duration. For example, in Engine_PolySub:
removeVoice { arg id;
if(true, { //voices[id].notNil, {
s.makeBundle(s.latency, {
voices[id].set(\gate, 0);
});
//voices.removeAt(id);
});
}
The latency-minimizing version of this would track the note start time, and only use additional latency if the \gate=0 is within some “minimum note duration” window.
The perfect-perfect-reliability version would do something like this (e.g. don’t stop until you know it’s started):
if (voices[id].isRunning) {
voices[id].set(\gate)
} {
voices[id].addDependant({
|n, m|
if (m == \n_go) { voices[id].set(\gate) }
})
}
There’s may be one or two narrow cases of this problem that can be fixed in the server, but at the end of the day it’s represents a true ambiguity: the \gate value (or any value for that matter…) has been set to both 1 and 0 at the same logical time. Does this mean my gated envelope started and ended (and thus should e.g. free’d), or does it mean I’ve simply set my gate to zero by idempotence, and the envelope has not started at all?