Pbind and samples with different durations (2)


#1

Hello,

I resume the thread of a previous post:
https://scsynth.org/t/pbind-and-samples-with-different-durations-chain-them/714
The problem was to get the duration of buffers containing sound files, to be able to chain them without interruption in a Pbind.

The solution found by @dkmayer that works well:

Pbind(\instrument, \bufplay,
	\amp, 1,
	\buf, Pxrand(~bufFauvette, inf),
	\dur, Pfunc({|d| (d.buf).duration}),
	\legato, 1.05,
)

Now, I would like to insert a rest of random duration between each musical phrase. Here is what I did, but it works badly:

(
Pdef(\fauvette,
	Pbind(\instrument, \bufplay,
		\amp, 1,
		\buf, Pxrand(~bufFauvette, inf),
		\dur, Pseq([Rest(2), Pseq([Pfunc({|d| (d.buf).duration})], repeats:Pwhite([3, 15]))], inf),
		\legato, 1.05,
));
)

Indeed, the sequence begins normally with a rest, then the chained sounds start well, but the global sequence remains indefinitely blocked on the sounds and does not loop back to the beginning. So, there is no longer rests.

I do not understand why.
What happens and how do you fix that?


#2

Elode,

I’m not on a computer where I can test your code, but one thing I notice is that the Pwhite looks odd. Normally Pwhite takes 3 arguments in ( ) brackets and not [ ] square ones. This would may be the cause of your problem.

Your Pwhite should read Pwhite(3,5,inf) - or whatever.

Fingers crossed!


#3

Thank you @joesh, I made the change as you suggest :slight_smile:
Alas, it does not seem to change the problem :confused:
Here is the code after the change:

(
Pdef(\fauvette,
	Pbind(\instrument, \bufplay,
		\rate, 1,
		\amp, 1,
		\buf, Pxrand(~bufFauvette, inf),
		\dur, Pseq([Rest(2), Pseq([Pfunc({|d| (d.buf).duration})], repeats:Pwhite(3, 15, inf))], inf),
		\legato, 1.05,
));
)

In fact, it does not change the result and the problem remains the same!


#4

Yes, I now see/hear the problem (I got to a computer in the classroom and tried it out), very strange. I’ll be curious to read the solution when someone posts it.


#6

This is a combo of two problems, both not very obvious, so don’t worry that you struggled:

.) Pfunc embeds endlessly, you need Pfuncn
.) For a varying repeats arg you need a Function or a Stream

You could use the inner Pseq for repetition, but Pn is more idiomatic and shorter.
I dropped the buffer part, as it’s not relevant here:

(
p = Pbind(
    \bufDur, Pstutter(2, Pseq([0.4, 0.2], inf)),
    \dur, Pseq([
        Rest(1.2), 
        Pn(Pfuncn { |e| e[\bufDur] }, Pwhite(3, 15).asStream)
    ], inf),
    \midinote, Pwhite(60, 90)
).play;
)

p.stop

#7

Pfuncn has a ‘n’ argument to say how many times to do the function, so:

Pfuncn({ … }, Pwhite(3, 15).asStream)

Pn isn’t needed here.
hjh


#8

That’s funny and totally true !
Don’t know where I had my head when wrapping Pfuncn into a Pn.
Pfuncn itself is Pn + Pfunc

There are problems of repeated embedding which are clumsy with vanilla patterns types,
but this is a rather straight one.


#9

Thanks you to all of you :slight_smile:
Here is my final code:

(
Pdef(\fauvette,
	Pbind(\instrument, \bufplay,
		\buf, Pxrand(~bufFauvette, inf),
		\dur, Pseq([Pwhite(1, 5, 1), Pfuncn({|d| (d.buf).duration}, repeats: Pwhite(1, 20).asStream)], inf),
));
) 

I replaced Rest(2) with Pwhite(1, 5, 1) to introduce randomness over the silence time between groups of sounds.
I first tried to do this in a form like this:

Rest(rrand (1, 5))
//or
Rest(Pfunc({|r| r.rrand(1, 5)})

…but I did not find anything else that works.
I do not understand why here Pwhite works like a rest, but that’s the case…


#10

With such the random value is taken immediately (at Pattern instantiation time) and kept.

But you could write

Pwhite(1, 5, 1).collect(Rest(_))

which is short for

Pwhite(1, 5, 1).collect { |x| Rest(x) }

Rest syntax has been changed at some point.
I’m wondering if there’s a shorter form to write the above, is there ?

Something like

Pattern.asRest
Pattern.rest

should exist IMO


#11

Yes, but your little mischief has the advantage of making me better understand Pn
Thanks :slight_smile:


#12

It’s not really the syntax that changed, but rather the handling.

We’re getting a bit off-topic now, but I would agree with you that, currently, it’s harder than it needs to be to embed a Rest algorithmically into a stream of non-Rests. (By “algorithmically,” I mean that either the rest’s value is calculated or the Rest/non-Rest status is calculated.)

Two things that would help:

  • Provide a fallback in Event:delta to call .value.value on delta, dur and stretch. Then you could do Rest({ ... }) or Rest(something.asStream).

  • A pattern that would conditionally turn a value into a Rest. .asRest or .rest, if they are all or nothing, would be less useful than a pattern that lets you specify a condition. This would make tendency masking trivially easy – doesn’t exist today, but could be something like Prest(0.25, Pwhite(0.0, 1.0, inf) < 0.2) for 20% chance of a rest.

hjh


#13

This looks good to me. A short method for making a value to a Rest is nevertheless necessary IMO, otherwise we’d just have to replace one lengthy syntax by another one. E.g. x.asRest (or x.rest) could turn into Prest(x, 1)
by having a prob arg set to default = 1 (asRest could have the same prob arg as Prest).


#14

I wouldn’t make it a 0.0 – 1.0 probability value, because then you’re enforcing one and only one algorithm to determine whether to turn it into a rest or not (or requiring the user to turn a Boolean value into false = 0 and true = 1). I would just make it Boolean.

If the default is true, then I would argue you don’t need .asRest at all.

Prest(1) // 8 characters
1.asRest // 8 characters = not shorter = no benefit = don’t clutter the interface unnecessarily

(Sure, if it’s x.rest then it would be shorter, but at a loss of some clarity and also breaking away from the precedent of the .asSomething protocol. I also find, the longer I do this, that short conveniences end up causing problems later. It’s better to write code that is slightly longer and much more clear – and it’s much better to encourage new users in this habit.)

Prest : FilterPattern {
	var <>boolean;

	*new { |pattern, boolean = true|
		^super.new(pattern).boolean_(boolean)
	}

	embedInStream { |inval|
		var boolStream = boolean.asStream;
		// true = use embedding behavior for non-pattern values
		^pattern.streamArg(true).collect { |value, inval|
			if(boolStream.next(inval)) {
				Rest(value)
			} {
				value
			}
		}.embedInStream(inval)
	}
}

Then:

// default behavior is like Rest for simple values
Pseq([0, 1, Prest(2), 3], 1).asStream.all
-> [ 0, 1, Rest(2), 3 ]

// a pattern inside Prest expands to its full length
Pseq([0, 1, Prest(Pwhite(10, 20, 3)), 3]).asStream.all
-> [ 0, 1, Rest(11), Rest(12), Rest(11), 3 ]

// probability masking also works
Prest(Pwhite(1, 10, inf), Pn(0.5, inf).coin).asStream.nextN(10)
-> [ Rest(7), Rest(2), 7, 8, Rest(5), 5, Rest(2), Rest(7), 2, Rest(8) ]

// for comparison, the old way is longer and reads uglier
Pwhite(1, 10, inf).collect { |x| if(0.5.coin) { Rest(x) } { x } }.asStream.nextN(10)

hjh


#15

That’d be fine I think.

Yes and no. If the convenience is arbitrary then yes, problems are more likely to occur, if it’s consistent then no. aPattern.rest would be quite similar to the existing convention of aPattern.collect. This is certainly much more used than Pcollect - because it’s shorter. But It’s not only about being shorter, saving brackets is an important point.
I think that users are often detained from Pattern constructs because they end up in a mass of brackets. This is not my view alone, e.g. see this thread, Simon’s basic idea is a good one:

https://www.listarc.bham.ac.uk/lists/sc-users-2018/msg63129.html

If I had time I’d follow my sketches from then and build a suite of Pattern methods, but maybe someone else wants to chime in.


#16

But aPattern.collect follows another model: aCollection.collect. A collection represents multiple values. The pattern makes a stream, which represents multiple values. If it makes sense to “collect” over multiple values from a collection, then it also makes sense to “collect” over multiple values from a stream.

I believe the concept for collections came first (related to map in other languages). So, if we justify something.rest on the basis that we have something.collect, then [0, 1, 2].rest needs to make sense in the same way that [0, 1, 2].collect makes sense.

I don’t think it does. [Rest(0), Rest(1), Rest(2)]? I don’t get it.

I think that users are often detained from Pattern constructs because they end up in a mass of brackets.

I think one reason for that is because the documentation, and list and forum postings, rarely demonstrate the use of indentation to make pattern notation clearer.

That is, we would tend to see examples written like this:

(
p = Pbind(
	\degree, Pseq([Pseries({ rrand(-7, 0) }, 1, { rrand(3, 9) }), Pn(-10, { rrand(4, 8) }), Pseries({ rrand(5, 12) }, -1, { rrand(3, 5) })], inf),
	\dur, Pswitch1([0.125, Rest(0.125)], Pwhite(0.0, 1.2, inf))
).play;
)

p.stop;

Now, I’m used to working with patterns, but that \degree pattern makes even me dizzy.

(
p = Pbind(
	\degree, Pseq([
		Pseries({ rrand(-7, 0) }, 1, { rrand(3, 9) }),
		Pn(-10, { rrand(4, 8) }),
		Pseries({ rrand(5, 12) }, -1, { rrand(3, 5) })
	], inf),
	\dur, Pswitch1(
		[0.125, Rest(0.125)],
		Pwhite(0.0, 1.2, inf)
	)
).play;
)

p.stop;

Syntactically equivalent, but crystal clear.

I get what Simon is saying in an e-mail post, but [ ... super long list of nested patterns ...].seq is still messy to read, without indentation.

Also, especially for live coding, what I would really like to see is more intelligent cursor behavior. The problem Simon is pointing to is, if you have array, then to sequence it, you need to put something before the array and something after, which is slow and inconvenient for live coding (whereas, with his suggestion, you only need to put something after it). But, in the IDE, we are stuck with cursor movement key bindings that are designed for text rather than programming code. We should be able to hit keys to select syntactic elements, and then hit other keys to wrap the selected element in some template.

For live coding performances, I use a TextView instead of an IDE document, with some custom keyDownActions that navigate through my live coding syntax more intelligently. It’s a huge help.

hjh


#17

Why not? The fact that it doesn’t exist yet doesn’t mean that it doesn’t make sense.
Restifying is an operation that you might want to use when dealing with lists of numbers before feeding them into a pattern as well. Sure, you can always use .collect(Rest(_)) to keep the principle at least.

Certainly also true, a lack of spaces is moreover a pitfall concerning readability.


#18

I’m actually open to being convinced. But, we have a history in SC of thinking “Wouldn’t it be nice if…?” and then adding something to the interface, which turns out to cause some problems later that could have been avoided if we took more care with the design initially.

Also, it periodically comes up on the mailing list that a stumbling block for new SC users is that there are too many ways to write the same thing, and to read other people’s code, new users have to learn them all at the same time (and they have to wonder, “why should I use Rest, or Prest, or .rest in this or that case?”).

So my default position with respect to new conveniences is a high level of scrutiny and skepticism. The question is not, “Why not?” The question should be, “Why can we not live without this? And, is this the ideal form that we can’t live without?”

hjh


#19

Well, obviously we can live without a lot of things. This is not a crucial discussion for me personally because I’m quickly adding a new method in my setup and things are done.

But, as Simon’s post shows, a simple demand as differentiated resting is not obvious to be done and this at least indicates that the interface should rather be improved - or the documentation should show up a way to do it.


#20

Personally, I did not find how to do it.


#21

@elode Yes, it was you, not Simon, who started this thread, sorry.