Random and non-random in the same pattern


#1

Hello,
In a Pbind, I would like the first note of a set of 8 beats is exactly on the first beat, then random durations and random positions for the next notes on the rest of the 7 beats, and so on next sets of 8 beats.
In fact, the idea is to have a random rhythm, but with a first beat always marked in the same way.
Do you think it’s possible to do that kind of thing in a way that is not too complicated?
Thanks for the help.


#2

Off the top of my head, I think you’re after something like:

Pbind(
    \beat, Pseries() % 8,
    \legato, Pfunc({|ev| (ev.beat > 0).if { 0.1.rrand(0.9) } { 0.5 } }),
    \timingOffset, Pfunc({|ev| (ev.beat > 0).if { -0.1.rrand(0.1) } { 0 } })
);

#3

Thank you for the answer that may be very useful,
But in this case, if I change the note durations with a pattern as you suggest, the duration of 8 beats is changed (in seconds). I would like this duration to remain fixed. For example, 8 beats = 8 seconds.

(
a = Pbind(
\beat, Pseries() % 8,
\amp, Pfunc({|ev| (ev.beat > 0).if {0.1.rrand(0.2) } { 0.9} }).trace,
\dur, Pfunc({|ev| (ev.beat > 0).if {0.125.rrand(2)} { 1 } }),
\timingOffset, Pfunc({|ev| (ev.beat > 0).if { -0.1.rrand(0.1) } { 0 } })
);
)
a.play;


#4

\timingOffset shouldn’t affect the onset time of the next Event in the Stream, unlike \dur. You can confirm this by playing two Pbinds in parallel and observing that they line up every eight beats:

(
a = Pbind(
    \beat, Pseries() % 8,
    \legato, Pfunc({|ev| (ev.beat > 0).if { 0.1.rrand(0.9) } { 0.5 } }),
    \timingOffset, Pfunc({|ev| (ev.beat > 0).if { -0.1.rrand(0.1) } { 0 } })
);

b = Pbind(
    \beat, Pseries() % 8,
    \midinote, 72
);

c = Ppar([a, b], inf);

d = c.asEventStreamPlayer;

d.play
)

Another way you could do this is by calculating an Array of durations that sum to 8 every “bar”, but I think the first approach is nicer:

(
a = Pbind(
    \beat, Pseries() % 8,
    \durs, Pfunc({ Array.rand(8, 1.0, 2.0).normalizeSum * 8 }).stutter(8),
    \legato, Pfunc({|ev| (ev.beat > 0).if { 0.1.rrand(0.9) } { 0.5 } }),
    \dur, Pfunc({|ev| ev.durs[ev.beat] })
);

b = Pbind(
    \beat, Pseries() % 8,
    \midinote, 72
);

c = Ppar([a, b], inf);

d = c.asEventStreamPlayer;

d.play
)

#5

An easy solution would be this one: make a little helper function, that divides a given number x in n numbers of arbitrary size, e.g.:

 ~part = { |sum, n| ([0, sum] ++ ({ rand(sum.asFloat) } ! (n-1))).sort.differentiate.drop(1) };

// test

a = ~part.(2, 5);
a.sum;

// 8 random lengths of sum ~t
(
~n = 8;
~t = 3;

Pbind(
    \midinote, Pseq([80, Pwhite(60, 75, ~n-1)], inf),
    \dur, Pn(Plazy { Pseq(~part.(~t, ~n)) }),
    \amp, Pseq([0.5] ++ (0.1!(~n-1)), inf),
).trace.play
)

// always same length of first beat
(
~n = 8;
~t = 3;
~t_1 = 1;

Pbind(
    \midinote, Pseq([80, Pwhite(60, 75, ~n-1)], inf),
    \dur, Pn(Plazy { Pseq([~t_1] ++ ~part.(~t - ~t_1, ~n-1)) }),
    \amp, Pseq([0.5] ++ (0.1!(~n-1)), inf),
).trace.play
)

For more differentiated control of the partition, e.g. min and max length of durations, quantization etc. see method ‘enum’ from miSCellaneous_lib quark.

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


#6

Here’s another approach. This looks more verbose, but in my mind it’s actually a little more straightforward and easy to understand and modify.

(
SynthDef(\chk, {
	var sig, env;
	
	env = Env.perc(0.01, \release.kr(1)).kr(gate:1, doneAction:2);
	sig = WhiteNoise.ar;
	sig = BLowPass.ar(sig, \filtFreq.kr(1200));
	sig = env * sig;
	sig = Pan2.ar(sig, \pan.kr(0));
	
	Out.ar(0, sig);
}).add;
)

(
Pdef(\beats, {
	// Assuming our first dur is 1, we want 7 more that add up to a
	// predictable amount. This ensure they add up to 1, we multiply
	// by 7 later so our sequence is always 8 long
	var divisions = ({1.0.rand} ! 7).normalizeSum;
	
	// For each of our params, we want a sequence where we take our
	// FIRST value from the outer \beatSequence event, and then additional
	// values are random varations on that.
	Pbind(
		\instrument, \chk,
		\dur, 		Pkey(\dur) / 8 * Pseq([
			1, 
			Pseq(divisions) * 7
		]).trace,
		\release, 	Pkey(\release) * Pseq([
			1,
			Pfunc({ rrand(0.5, 1.5) })
		]),
		\filtFreq,	Pkey(\filtFreq) * Pseq([
			1,
			Pfunc({ rrand(0.5, 1.5) })
		]),
		\pan,		Pkey(\pan) + Pseq([
			0,
			Pfunc({ rrand(-0.5, 0.5) })
		])
	)
});

Pdef(\beatSequence, Pbind(
	\type, \phrase,
	\instrument, \beats,
	\dur, 8,
	\filtFreq, Pseq(300 * [1, 2, 4, 8, 16, 32], inf),
	\release, Pseq([0.1, 0.5, 0.1, 0.25], inf)
)).play;
)

Here’s the gist of it:

  1. Create a Pbind that represents JUST your 8 beat phrase - one fixed initial event plus 7 random variations. Store that in Pdef(\beats).
  2. Create an outer Pbind that triggers your phrase every 8 (if you use \type, \phrase, it will look up a Pdef correspinding to your \instrument name and play it).

This approach is nice for a couple reasons…
You can specify multiple different phrases, and sequence them in your outer pattern, e.g.
\instrument, Pseq([ \beats1, \beats2, \beats2 ])
You can effectively pass parameters from your outer Pdef into your inner phrase and access them via Pkey OR arguments to the function stuffed in the Pdef - so, e.g. if you wanted the duration of your first note to be a parameter, you could do:

// in \beatSequence
\firstDur, Pseq([2, 3, 2, 3])

// in \beats
\dur, 	Pkey(\dur) / 8 * Pseq([
			Pkey(\firstDur), 
			Pseq(divisions) * (8 - Pkey(\firstDur))
		])

This means you can basically hide the complexity of your phrase inside of Pdef(\beats), and work compositionally in the simpler outer pattern. If you search for “recursive phrasing” in the help docs you can find more information around the \phrase key - but to be honest, even the above usage allows for more than enough complexity and power without digging any deeper into ALL of the recursive phrasing stuff.


#7

Thank you so much. All of this is a little complex for me and I need some time to study it and try to understand. I come back to you as soon as I have advanced a little :slight_smile:


#8

I’m not sure if I understand what you need exactly, but why not do something simple? Just put a Pseq and Prand/Pwrand/Pxrand inside a Pseq.

\dur, Pseq([
	  Pseq([1], 1),
	  Prand([1, 0.5, 3, 4], 7), // or whatever number of beats you need
      ], inf),
\nextPair

Another thing you could look at is using Pseed which could produce a nice (controllable random) alternative. Once you like what you’ve got stay with it.


#9

The challenge here is that the goal is for your 8 events (one of dur one and seven with random durs) to add up to be different each time, but add up to exactly 8. You’re right, though, you can probably achieve pretty close to the right thing by using Pseq and Pxrand (not Prand though), and just hand-picking values that add up to the right amount. It will probably be impossible to hear that you’re using a fixed bag of durations anyway — but it depends how important randomness is in the outcome…


#10

I refine my specifications:
First note always on the first beat of the set of 8 beats, then adding notes whose durations randomly chosen from a list until 8 beats. So, there are not necessarily 7 notes after the first, but as much as it takes to fill the space of 8 beats (according to their durations).
I’m trying to go in this direction:

(
var ser, dur, maxDur, total, lastDur, durCh;
maxDur = 8;
dur = [0.125, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
total = 0;
ser = {total < maxDur}.while({
durCh = dur.choose;
total = total + durCh;
total.postln;
});
)

This is not very far from what I’m looking for, except that we should be able to anticipate the duration of the last note of the serie produced by the variable ser, which often exceeds 8 beats, to reduce this last duration and force it to finish at 8. I do not know if it’s clear :confused:


#11

One way would be to use Pconst. There’s a warning in the help file about using it for duration parameters, but I think this use case would be ok:

Pconst(8, Prand([0.125, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2], inf))

#12

My recipe for doing that kind of thing is:

Pbindef.defaultQuant_(4)

Pbindef(\a, \dur, Pseq(({1.0.rrand(4)}!16).normalizeSum * 4,inf))

I’m not sure if the arithmetic entirely makes sense, but does produce an irregular rhythm lasting four beats starting on the bar.


#13

Mixing the method @scztt et @jpburstrom :slight_smile:

(
SynthDef(\chk, {
var sig, env;

env = Env.perc(0.01, \release.kr(1)).kr(gate:1, doneAction:2);
sig = WhiteNoise.ar;
sig = BLowPass.ar(sig, \filtFreq.kr(1200));
sig = env * sig;
sig = Pan2.ar(sig, \pan.kr(0));

Out.ar(0, sig);
}).add;
)

(
Pdef(\beats, {
// For each of our params, we want a sequence where we take our
// FIRST value from the outer \beatSequence event, and then additional
// values are random varations on that.
Pbind(
\instrument, \chk,
\dur, Pkey(\dur) / 8 * Pseq([
1,
Pconst(7, Prand([0.125, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2], inf))
]).trace, // \dur - 1 in Pconst does not work (why?) - so Pconst(7,…
\release, Pkey(\release) * Pseq([
1,
Pfunc({ rrand(0.5, 1.5) })
]),
\filtFreq, Pkey(\filtFreq) * Pseq([
1,
Pfunc({ rrand(0.5, 1.5) })
]),
\pan, Pkey(\pan) + Pseq([
0,
Pfunc({ rrand(-0.5, 0.5) })
])
)
});

Pdef(\beatSequence, Pbind(
\type, \phrase,
\instrument, \beats,
\dur, 8,
\filtFreq, Pseq(300 * [1, 2, 4, 8, 16, 32], inf),
\release, Pseq([0.1, 0.5, 0.1, 0.25], inf)
)).play;
)

Thank you, I’m really learning a lot with you all :smile:


#14

You can also do this with directly taking over the integer partition helper function defined in miSCellaneous_lib’s ‘enum’ help file Ex. 3:

// multiply it to get only integers (*8), sum has then also to be multiplied

x = [1,2,4,6,8,10,12,14,16]
z = h.(64, 8, x)   // this calculates all possibilities, actually not necessary here
z.choose / 8   

-> [ 0.5, 0.75, 0.75, 1.25, 1.5, 1.75, 2, 2 ]


z = h.(64, 5, x)
z.choose / 8

-> [ 0.5, 1.75, 1.75, 2, 2 ]

#15

This might be useful: in the \phrase example I gave, the phrase you’re triggering, Pdef(\beats), is only played as long as the ~duration * ~legato from the outer sequence - basically, as long as a note from the outer sequence would have lasted. If it goes longer than that, it’s actually cut off!

This means that even if your inner phrase is infinitely long, it will still only get played for 8 beats if your outer \dur is 8. So, you don’t really have to do ANY of the fine-tuning of duration values, you can just do:

\dur, Pfunc({ rrand(0.2, 1.0) }} // random durations between 0.2 and 1

You’ve got a guarantee that the pattern won’t progress beyond 8.

Caveats:

  1. If your inner phrase is at time=7, and your last \dur value is 2, then no more events will be played, but I THINK the last event will extend beyond the end of the 8th beat, potentially overlapping with the next phrase. This wouldn’t really matter for percussion, but could make a difference for sustained notes.

  2. To ensure your phrase is the right length, I think you’ll need to be careful to specify \legato as well as \dur:

\dur, 8, \legato, 1    // 8 beat phrase, no overlaps
\dur, 8, \legato, 2    // 16 beat phrase, started every 8 beats (so 2x overlap)

#16

@scztt What are the braces that are at the beginning and end of the Pdef \ beats? It seems to me that it works just as well without them. What is their role?


#17

When you hit Cmd+Return in the IDE, it will execute all the code inside of the largest group of parentheses - so it’s not necessary, but it’s sometimes conventional in SuperCollider to wrap a bunch of code in parentheses so it will all be executed at once.

And FYI you can mark a section of your post on this forum as code by using three accent marks ` on a line at the beginning and the end, or a [code] tag:

``` 
some code
``` 
[code] some code [\code]

#18

Thank you, but I was talking about these braces : { } … But it seems to me that I have to study the JITLib that I do not know at all.


#19

Oh! That’s an even better question. The pragmatic answer is - that’s generally the way you build phrases with Pdef. :slight_smile:

The technical answer is - when you stick a { function } in a Pdef rather than, for example, a Pbind it simply calls that function with the current Event being constructed as both the environment as the arguments to that function (if there are any). The result of the function is used anywhere the Pdef is embedded - if the result of the function is a pattern, then that pattern is embedded and provides values until it runs out (at which point the function will get called again).

(
Pdef(\octave, {
	"Playing note: %".format(~degree).postln; // environment variable used to access this event property
	[3, 4, 5].choose; // return an octave #
});

Pdef(\legato, {
	|octave|
	"Octave is: %".format(octave).postln; // function argument used to access - equivalent to using ~octave
	octave.linlin(3, 5, 3, 1.5); // longer legato for lower notes
});

Pdef(\degree, {
	Pseq([ [0, 3], [-3, 4], [2, 9] ]); // return a pattern - this pattern is used until it runs out, then the function is called again
});
	
Pdef(\pattern, Pbind(
	\degree, Pdef(\degree).repeat,
	\octave, Pdef(\octave).repeat,
	\legato, Pdef(\legato).repeat
)).play
)

You can do the phrase thing without wrapping in {} but in my experience the behaviors you get from that aren’t often what you want.

The above usages can sometimes be interesting because you can build a pattern dynamically based on other things going on in your Pbind. You can also use it very simply as a hot-swappable version of Pfunc, since if you change the definition of a Pdef, other patterns using it will pick up the changes.