Multichannel expansion with patterns fails when Rest() is included

When a Rest() is included in an array in a pattern it seems to be ignored.

(
// works
Pbind(
	\degree, Rest()
).play;
)
(
// doesn't work
Pbind(
	\degree, [Rest()]
).play;
)

Is this a bug?

hi David,
i think ýou are right, there seems to be a bug with Rest() in the context of degree multichannel expansion.
It appears to work as expected for midinote.

(
// with degree, the Rest is unexpectedly audible (as the default note)
p.stop;
p = Pbind(
	\degree, [Rest(), -10, -7]
).trace.play;
)

(
// with midinote, the Rest is not audible, as expected 
p.stop;
p = Pbind(
	\midinote, [Rest(), 43, 48]
).trace.play;
)

will you file an issue?

cheers,
eddi

From the Rest help file:

Event: -isRest checks every item in the event to see if it meets the condition to be a rest: it may be a Rest instance, the Rest class, or either of the symbols \ or \r. If any item meets the condition, the event will be considered a rest and it will not take action when played.

(Hm, actually “the Rest class” is deprecated.)

So it checks the event item itself to be a rest instance. If the event item is a collection, it doesn’t go into the collection to check for rests.

Mainly this is for speed. (The normal case – event is not a rest – is also the worst case for performance, in that we have to scan every event item to be sure it isn’t a rest.) If we have to enter every collection and check, it would degrade performance of any event involving multichannel expansion.

So, what is the desired behavior? To treat the entire event as a rest because of one Rest object in an array? Or to suppress the OSC message for that specific argument set (i.e., in alln4tural’s example, to play two notes instead of three)?

It appears to work as expected for midinote.

It doesn’t. If you were watching the server status bar, you would see that you still get three notes. It’s just that one of them is about 8 Hz and you couldn’t hear it.

But, for that matter, “as expected” is exactly the issue here.

Developers had extensive discussions about the expected behavior of Rests. It turns out that it’s very easy to expect Rests to do any number of things, but many of these things conflict with each other and it’s been remarkably difficult to hammer it down to a consistent behavior. I don’t want to be flippant, but it actually is true here that one user might expect a Rest object to do one thing in this case while another user might expect something completely different. So we have to be quite clear about what is “expected” and not simply assume that everyone has the same idea about it.

If, as I’m guessing, the intention is to suppress one of a number of multi-node-expanded notes while playing the others, then in some ways, the optimal solution is to generate the event with exactly the number of notes that will play, instead of generating extra data and expecting something else to suppress some of it.

hjh

Thanks - I think :). I do take exception to your condescending last point, though. I was trying to do something that seemed very idiomatic to patterns. Something along the lines of this:

(
Pbind(
	\degree, Ptuple([
		Pseq([0, 2, 4, 6], inf),
		Pseq([-2, Rest(), -5], inf),
	])
).play
)

This seems optimal to me in terms of syntactical expressiveness. Obviously I have no choice but to tackle the problem from a different angle. I’ll give that some thought.

Not necessarily, Functions are your friend !

(
f = { |x| x.reject(_.isRest) };

Pbind(
	\degree, Ptuple([
		Pseq([0, 2, 4, 6], inf),
		Pseq([-2, Rest(), -5], inf),
	]).collect(f)
).trace.play
)

Here the Rest object works just as a marker, you could take anything else:

(
f = { |x| x.reject(_.isArray) };

Pbind(
	\degree, Ptuple([
		Pseq([0, 2, 4, 6], inf),
		Pseq([-2, [], -5], inf),
	]).collect(f)
).trace.play
)
1 Like

i went down the path of setting the amp to 0 for the channel if there was a rest detected, as such:

\amp, Pkey(\degree).collect({arg val; val.collect({arg degree; if (degree.isRest) {0}{1} }) }) * 0.1

but i like your idea better. thanks

ugh! using the Daniel’s technique, i can still potentially end up with this, however:

\degree, []

which produces the default note (i wouldn’t expect any other behavior in this scenario)

I apologize for any perceived condescension. It wasn’t intended. I might have gotten too excited and spoken carelessly.

The use case that you mention is reasonable, but in fact it has never been supported in SC in exactly this form. Rests have, to date, always been considered to apply to the event as a whole. The entire event is either a rest or not. Anything else is, currently, undefined behavior.

Even if you go back to the original rest implementation (which was to use a symbol in \degree, \note, \midinote or \freq), a so-called “rest” in an array would not suppress the “resting” element.

// with OSC-dump turned on:
(degree: \r).play;  // nothing

(degree: [0, 2, \r]).play;

[ "#bundle", 16242532102477424678, 
  [ 9, "default", 1003, 0, 1, "out", 0, "freq", 261.626, "amp", 0.1, "pan", 0 ],
  [ 9, "default", 1004, 0, 1, "out", 0, "freq", 329.628, "amp", 0.1, "pan", 0 ],
  [ 9, "default", 1005, 0, 1, "out", 0, "freq", "r", "amp", 0.1, "pan", 0 ]
]

Note the "freq", "r" – scsynth will simply ignore that argument pair and use the default frequency defined in the SynthDef (440 Hz). So instead of the “expected” major 3rd dyad, you get an A minor triad in first inversion.

(Vis-a-vis “undefined behavior,” the previous implementation of the Rest class would have suppressed the entire event – Ptuple merely touching a Rest would have marked the event as a rest and you would have gotten 0 notes.)

So it’s really a new feature request. I can see the point of it, actually. It would have to be implemented for many event types, and it would have some performance impact that would have to be evaluated (I ran a quick test and it seems to be around 8-9% slower, not massive but not insignificant either) – so it isn’t as simple as “it seemed like this should be reasonable” and the answer in the end might be “it’s too complex or bad for performance.” But it’s worth raising.

using the Daniel’s technique, i can still potentially end up with this, however: \degree, [] which produces the default note

Perhaps this?

f = { |x|
	x = x.reject(_.isRest);
	if(x.isEmpty) { Rest(0) } { x }
};

hjh

1 Like

thank you for your thorough and thoughtful reply. your code snippet works well. i’m a little disappointed i couldn’t figure that out myself :slight_smile: but it’s hard sometimes switching between feeling like you’re composing music and feeling like you’re debugging code