Dynamic Arrays for Patterns?

Pseq doesn’t like having an array passed into it from a Pdefn:

(
SynthDef (\sine) {
	arg freq = 440;
	var sig = SinOsc.ar (freq);
	sig = Pan2.ar (sig, 0, 0.2);
	Out.ar (0, sig);
}.add;

TempoClock.tempo_ (91 / 60);

Pmono (\sine,
	\delta, 1 / 4,
	\freq, Pdefn (\arpeggio),
).play;
)

(
Pdefn (\freq_array) {
	{ ([ 0, 3, 7, 10, 19] + 57).midicps.yield }.loop;
};

Pdefn (\arpeggio) {
	Pseq (([ 0, 3, 7, 10, 19] + 57).midicps, inf); // works
	// Pseq (Pdefn (\freq_array), inf);  // doesn't work
};
)

is there a way around this? … I want to be able to change the number of steps in the arpeggio dynamically.

Pseq accept an array of pattern, but the array cannot be the pattern itself

You can always use a Prout to do what you want:

(
Pdefn (\freq_array) {
	{ ([ 0, 3, 7, 1,10, 19] + 57).midicps.yield }.loop;
};

Pdefn (\arpeggio, 
	Prout({ arg ev;
		Pdefn(\freq_array).asStream.do({ arg arr;
			Pseq(arr).embedInStream
			//arr.do({ arg val; val.yield }) // alternate way
		})
	})
);
)

or with your syntax i didn’t know about (but i believe it just create a Prout under the hood)

(
Pdefn (\freq_array) {
	{ ([ 0, 3, 7, 1,10, 19] + 57).midicps.yield }.loop;
};

Pdefn (\arpeggio) {
	Pdefn(\freq_array).asStream.do({ arg arr;
		Pseq(arr).embedInStream
		//arr.do({ arg val; val.yield }) // alternate way
	})
}
)
1 Like

A friend and I tracked down an elegant way to do this recently:

(
Pdefn(\notes, 40 + [0, 3, 5, 7]);

Pdef(\seq, Pbind(
	\scale, Scale.chromatic,
	\dur, 0.25, \legato, 3,
	\midinote, Pdefn(\notes).composeBinaryOp(\wrapAt, Pseries()).trace
)).play;

fork {
	loop {
		Pdefn(\notes, 40 + [0, 3, 5, 9]);
		8.rand.wait;
		Pdefn(\notes, 40 + [0, 3, 5, 8]);
		8.rand.wait;
		Pdefn(\notes, 40 + [0, 3, 5, 7]);
		8.rand.wait;
	}
}
)

Not only does this allow runtime redefinition of \notes, it actually keeps the same position in the sequence when you change the list. If you want something other than a Pseq style sequence, you’ve only got to change the Pseries to some other pattern that produces indexes that reflect how you want to iterate through the list.

5 Likes

You might check PLx patterns from miSCellaneous_lib.

(
SynthDef (\sine) {
	arg freq = 440;
	var sig = SinOsc.ar (freq);
	sig = Pan2.ar (sig, 0, 0.2);
	Out.ar (0, sig);
}.add;

TempoClock.tempo_ (91 / 60);

~a = 57 + [0, 3, 7, 10, 19];

p = Pmono (\sine,
	\delta, 1 / 4,
	\midinote, PLseq(\a),
).play;
)

// replace

~a = 57 + [2, 4, 9]

p.stop

Note that PLx patterns in general follow an immediate replacement paradigm (replace with next item from stream), for preservation of full loops see PLn or take Pn + Plazy.

1 Like

this helped me, thank you. but I am still having trouble conceptualising how I need to structure my Pdefns to generate the array dynamically. take the following:

(
Pdefn (\low_freq) { { 220.yield }.loop };
Pdefn (\high_freq) { { 440.yield }.loop };
Pdefn (\tet) { { 12.yield }.loop };
Pdefn (\delt) {
	log2 (Pdefn (\high_freq) / Pdefn (\low_freq)) / Pdefn (\tet);
};

Pdefn (\new_array) {
	Prout {
		Array.fill (Pdefn (\tet)) {
			arg i;
			Pdefn (\low_freq) * (2 ** (Pdefn (\delt) * i));
		};
	};
};
)

1 / 12; // hoping for this
Pdefn (\delt).asStream.next; // works

(57..69).midicps; // hoping for this
Pdefn (\new_array).asStream.next; // doesn't work

I want to derive the semitones between the A below middle C and the A above it dynamically, so I can stretch the end points, change the TET number, etc. arbitrarily as it is playing.


cute. I’ll probably end up doing it this way - thanks !!

doesn’t solve for dynamic array generation however (see above).


woah cool. just installed the quark. ima have a look at PLx suite - looks really nice. thanks !!

My impression is that you could save typing by switching to Pbindef, e.g.

(
Pbindef(\a,
	\loFreq, 220,
	\hiFreq, 440,
	\tet, 12,
	\delt, (Pkey(\hiFreq) / Pkey(\loFreq)).log2 / Pkey(\tet),
	\new_array, Pfunc { |e| 
		{ |i| e[\loFreq] * (2 ** (e[\delt] * i)) } ! e[\tet] 
	},
	\freq, Pkey(\new_array).collect(_.choose),
	\dur, 0.2
).trace.play
)
	
// exchange param

Pbindef(\a, \hiFreq, 300)

As I mentioned PLx before, you could also try PLbindef, where the replacement reduces to

~a.hiFreq = 300

Also a number of conversion params are already built into the Event framework, so you could use ‘stepsPerOctave’, ‘root’ etc, but I see the point that you might prefer to define your customized calculations.

1 Like

edit 2: worked it out. disregard!

(
~sine_synth = Synth (\sine);
Pbindef(\a,
	\type, \set,
	\id, ~sine_synth.nodeID,
	\loFreq, 220,
	\hiFreq, 440,
	\tet, 12,
	\delt, (Pkey(\hiFreq) / Pkey(\loFreq)).log2 / Pkey(\tet),
	\new_array, Pfunc { |e| 
		{ |i| e[\loFreq] * (2 ** (e[\delt] * i)) } ! e[\tet] 
	},
	\freq, Pkey(\new_array).collect(_.choose),
	\dur, 0.2
).play
)

edit:

is there a Pbindef for Pmono ?


yeh this looks awesome. many thanks Daniel !! :pray: :pray:

You can combine Pbindef with Pmono via Pchain:

Pchain(Pbindef(\a, \foo, 1, \bar, 2), Pmono(\a)).trace.play;
Pbindef(\a, \foo, 2, \bar, 3);
1 Like

Only if you don’t change the list length because wrapAt works modulo that.

Sometimes what you want, compositionally, is “add one more note to this sequence”. It’s actually not trivial to do this with PLseq either (especially when using an EnvirGui) because replacing the whole list that PLseq works of actually restarts it; you have to change only individual elements for PLseq to continue. What I mean is that if you do something “obvious” like

Pdef(\seq).set(\notelist, 50 + [2, 7, 12, 14, 17]);

(Pdef(\seq, Pbind(
	\scale, Scale.chromatic,
	\dur, 0.5, \legato, 2,
	\midinote, PLseq(\notelist, envir: Pdef(\seq).envir))));

Pdef(\seq).gui // hit play in there then change list

whenever you change the list in the gui, the play will restart from the first element of the list. (You could put elements of the list individually in the gui/envir, but that is quite cumbersome, esp. with regards to list length changes.) Also cutItems: false doesn’t make any difference here.

Plazy will sorta fix this by playing one full iteration of the old array before switching, i.e.:

(Pdef(\seq, Pbind(
	\scale, Scale.chromatic,
	\dur, 0.5, \legato, 2,
	\midinote, Pn(PlazyEnvir({|notelist| Pseq(notelist)})))));

But if you really, really want (to have your cake and eat it, i.e. replace the list right away and not restart it… I don’t effing know. You’d think that if you keep the same list in PLseq and only replace its elements it would do that, but that’s not reliable. It only seem to work if you shorten the list, but not if you lengthen it. I.e. the following will keep playing from the current note when you shorten the list in the gui and the list will end sooner, as you’d expect, but if you lengthen it instead, you get one extra full iteration of the current list, i.e. for lengthening it works like Pn(Plazy) above.

(Pdef(\seq, Plazy({var singl = List(); Pbind(
	\scale, Scale.chromatic,
	\dur, 0.5, \legato, 1,
	\notelist, Pfuncn({|ev| singl.postln; singl.clear.addAll(ev[\notelist])}, inf),
	\midinote, PLseq(singl))})));

It’s weird because the following simpler (non-event) list extension works as expected:

r = Plazy({var singl = List(); singl.add(55); PLseq(singl) <> Pfuncn({ singl.add(77) }, 2)}).asStream

r.nextN(3); // -> [ 55, 77, nil ]

// and so does clearing the list and rewriting it

r = Plazy({var singl = List(); singl.add(55); PLseq(singl) <> Pfuncn({ singl.clear.addAll([77, 88]) }, 2)}).asStream;

r.nextN(3);  //-> [ 77, 88, nil ]

Edit: I’ll have to read the answers below carefully, but I that’s what’s happening in the live-edit example is that the list already got to start… Because otherwise the replacement seems to work as intended, even in a Pbind:

(r = Plazy({var singl = List(); singl.add(55); 
	Pbind(\midinote, PLseq(singl)) <> Pfuncn({ |ev| singl.clear.addAll([77, 88]); ev }, 3)}).asStream)

r.nextN(4, ()) // -> [ ( \midinote: 77 ), ( \midinote: 88 ), ( \midinote: 77 ), nil ]

But the live replacement is equivalent to doing something like

(r = Plazy({var singl = List(); singl.add(55);
	Pbind(\midinote, PLseq(singl)) <> Pfuncn({ |ev| singl.clear.addAll(ev[\notelist]); ev }, 3)}).asStream)

r.nextN(1, (notelist: [22]))      // -> [ ( \notelist: [ 22 ], \midinote: 22 ) ]
r.nextN(3, (notelist: [77, 88]))  // -> [ ( \notelist: [ 77, 88 ], \midinote: 77 ), ( \notelist: [ 77, 88 ], \midinote: 88 ), nil ]

Note that the list restarts now on the second line…

1 Like

It’s stated in the help file and in this thread that this is not PLseq’s behaviour. PLseq / PL / PLn are for immediate replacement or protection of periods. The replacing behaviour you’re after - and I can well understand that this is also something to want - would be something different: an action that should depend on current position and replacement type (shorten, lengthen, single replacements with/without shortening/lengthening). A number of subtle case distinctions would have to be treated, IMO that’s the reason, that there doesn’t seem to be an obvious solution for that. I don’t have a general solution at hand either, but I’ll keep the ideas in mind.

That’s the big picture, but often simple workarounds might be sufficient for specific cases. What about this:

a = [52, 57, 62, 64, 67];
c = -1;

r = { 
	loop { 
		c = c + 1;
		(c >= a.size).if { c = 0 };
		c.yield
	}
};

p = Pbind(
	\dur, 0.2,
	\midinote, Prout(r).collect(a[_])
).play


a = [52, 57, 62, 64, 67, 71];

a = a ++ 73;

a = a ++ 78;

a = a.drop(-2);

a = a ++ [73, 78, 81];

p.stop

Actually it can be even shorter with Pfunc

a = [52, 57, 62, 64, 67];
c = -1;

f = { 
	c = c + 1;
	(c >= a.size).if { c = 0 }{ c };
};

p = Pbind(
	\dur, 0.2,
	\midinote, Pfunc(f).collect(a[_])
).play


a = [52, 57, 62, 64, 67, 71];

a = a ++ 73;

a = a ++ 78;

a = a.drop(-2);

a = a ++ [73, 78, 81];

p.stop

I don’t remember all details when thinking about the design of PLx, but I think such an option would be rather specific to Pseq, not to all ListPatterns, anyway I didn’t include it. But it would certainly pay to write a PseqDyn / PdynSeq fur such a purpose.

2 Likes

Simplified version:

(p = Pbind(
	\dur, 0.2,
	\midinote, Pfunc { c = (c+1) % a.size; a[c] }
).play)

Self-contained version, with array from event (so e.g. Pdef.gui editable)

Pdef(\p).set(\a, [52, 57, 62, 64, 67])

(Pdef(\p, Pbind(
	\dur, 0.2,
	\midinote, Plazy { var c = -1; 
         Pfunc { |ev| c = (c+1) % ev[\a].size; ev[\a][c] } })))

Pdef(\p).gui

This is still a bit odd if you insert elements e.g. at the beginning of the list, as it will repeat the current note (since it gets shifted right). Without some additional assumption that e.g. list elements are distinct, the latter issue is probably impossible to fix in general, but assuming distinct elements, one can do a search when the list length changes; for the “non-fancy” version that looks like:

a = [52, 57, 62, 64, 67, 71];

v = 0;
z = a.size;
c = -1;

(p = Pbind(
	\dur, 0.2,
	\midinote, Pfunc {
		// find could return nil if v was deleted from list
		if (a.size != z) { "len changed".postln; z = a.size; c = a.find([v]) ? c };
		c = (c + 1) % a.size;
		v = a[c] }
).play)

a = [48, 52, 57, 62, 64, 67, 71]; // no note doubling now

a = a ++ 73;

a = [48, 52, 57, 62, 64, 67, 70, 73]; // value replace (no list shift)

a = [66, 77]; // test corner case, nothing found fo sho

One could even make the search a bit more fancy with some kind of “find in context” by saving nearby elements as well (since find looks for a sub-array anyway). Although that will work less well unless the context search tolerates some changes, i.e. like the approximate (alignment) searches done for DNA.

SC has a doubly linked list. It does require a little bit of hackery because its interface tries to hide the underlying LinkedListNode objects, but once you get access to those, you can insert and delete at will without losing the current read position.

l = LinkedList[0, 1, 2, 3];

n = l.nodeAt(0);
n.obj;  // 0

n = n.next;
n.obj;  // 1

n = n.next;
n.obj;  // 2

// insert before second item
// there's currently not a method for this,
// but it wouldn't be hard
(
var nodeAfter = l.nodeAt(1),
new = LinkedListNode(10);

new.prev = nodeAfter.prev;
new.next = nodeAfter;

nodeAfter.prev.next = new;
nodeAfter.prev = new;  // this must be the last alteration!
)

n = n.next;
n.obj;  // 3 -- still OK

l  // what's in l?
-> LinkedList[ 0, 10, 1, 2, 3 ]

So you would just need an alternate Pseq that steps through a linked list by next.

(at etc. are slower with linked lists because there’s no way except to scan. But this use case seems to be frequent sequential access and only occasional random access, so the performance hit wouldn’t be continual.)

hjh

Yeah, but all current (JIT) guis work with object replacement. You get an entirely new LinkedList when you hit “enter”. So more work would be needed if gui editing of the list is desired.

It sounds like a design problem then. The current design doesn’t suit that use case (which is not ideal, just stating the reality). If the goal is to satisfy the use case without changing the design (of pattern implementation or GUI or both), or minimizing changes in design, then there will always be some odd limitation somewhere.

Desired features are:

  • Arbitrarily insert or remove any number of array elements at any time…

  • … without losing the position within the array or dropping or repeating items… (so, a pattern or stream replacement strategy isn’t ideal)

  • easy access to all of this functionality in JIT GUIs (which does follow a replacement strategy).

(Maybe I overlooked some other features.)

So the choices seem to be: work within the current framework and accept that there will be some things that can’t be done, or redesign with the full set of requirements in mind.

hjh

Yes, definitely these are conflicting requirements. The semantics that work best with the JIT gui’s are full replacement after one iteration of the old list (unchanged). Easily done PlazyEnvir etc., which by the way @hemiketal, is what Pdef and friends instantiate when you pass a function directly to them.

The linked list approach needs a player/sequencer that works of a reference/pointer as the current element, rather than an index. One might be better off doing that in a more “symbolic” fashion e.g. having events with a “next” field that point to other events, either directly or (probably better) though Pdef-life lookup by name of that “next”. (E.g. a derivative of Pdict might be useful.)


(e = (
	intro: (midinote: 55, nexte: \coda), // "next" is taken/method in Event
	coda: (midinote: 77, nexte: \intro)
))

~inserte = { |e, afterkey, k, v| var afknext = e[afterkey].nexte; e[afterkey].nexte = k; v.nexte = afknext; e.put(k, v) }

(p = p { |inev|
	var ed, crtek = \intro; // in a Pattern class these should be c'tor args
	loop {
		ed = e; // reread before each iter/event
		inev = inev.composeEvents(ed[crtek]).yield; // "next" from Event class does this
		crtek = ed[crtek][\nexte]; // overrides \nexte from inev...
		// but could be done differently, e.g. if you want to able to "jump" by changing this in inev...
		// although then there's the issue that it will stay on the same note until inev changes that...
	}
})

r = p.asStream

r.nextN(3, ())

q = (Pbind(\dur, 0.5) <> p).play

~inserte.(e, \intro, \middle, (midinote: 66))
~inserte.(e, \middle, \moarm, (midinote: 66))

q.stop

// somewhat similar of course e.g. to...
q = (Pbind(\dur, 0.5) <> Pdict(e, Pseq([\intro, \middle, \coda]))).play
// but there's the chicken-and-egg problem that you now need to change the list of symbols now...

// so actual equivalent needs a Prout for the "which" of Pdict
(q = (Pbind(\dur, 0.25) <> Pdict(e, p {
	var crtek = \intro; loop { crtek.yield; crtek = e[crtek][\nexte] } } )).play)

~inserte.(e, \coda, \coda2, (midinote: 79))
~inserte.(e, \coda2, \prein, (midinote: 47))

// [efficient] delete somewhat weird with single linked list
~delnext = { |e, kpred| var nextk = e[kpred].nexte; e[kpred].nexte = e[nextk].nexte; e.put(nextk, nil) }

// an advantage is that you can do it many times on same key, eventually deleting all
~delnext.(e, \middle)

Efficient delete would be somewhat weird with this single linked list (e.g. by specifying the predecessor, unless you want to do a search for that–a lookaside “prev” dict could also be used.) Also, I’m actually using a (real) circular list here.

e itself above is somewhat editable as a single field in an EvirGui (because “nexte” is symbolic), albeit pretty gaudy that way. If you make a separate EvirGui for it, there’s the issue that EnvirGui has no “buttons” to add/delete fields… (Although deleted works by setting a value to nil, IIRC.)

One is of course not limited to linear linked lists this with this general (Pdict-like) approach. You could make on-the-fly editable equivalents of Pfsm/Pdfsm.

N.B. somewhat more complicated version of inserte that can handle empty lists, and generally “insert after nothing”.

(~inserte = { |e, kpred, k, v|
	var kpnext = e[kpred] !? (_.nexte) ? k;
	v.nexte = kpnext;
	e.put(k, v);
	if(kpred.notNil) { e[kpred].nexte = k };
	e })

e = ()

~inserte.(e, nil, \intro, (midinote: 55))
~inserte.(e, \intro, \coda, (midinote: 77))

// insert disconnected bits, making `e` is a "multi/forest-list"
~inserte.(e, nil, \lone, (midinote: 44))

// for this to a bit useful, e.g. "one-off intro", manual connection is necessary, e.g.
e[\lone].nexte = \coda;

(q = (Pbind(\dur, 0.5) <> Pdict(e, p {
	var crtek = \lone; loop { crtek.yield; crtek = e[crtek][\nexte] } } )).play)

// perhaps insterte should also take an optional argument to set this nexte to some non-default (i.e. not the predecessor's next)

The delnext function I posted won’t handle correctly indegrees higher than 1 through (because it deletes the entry from the dict even if there are other pointers to it).

N.B. Turning the choosing of next into a Pfsm style stuff is quite easy by just making the nexte eval’d as a function

~inserte.(e, \intro, \middle, (midinote: 66))

(q = (Pbind(\dur, 0.25) <> Pdict(e, p {
	var crtek = \lone; loop { crtek.yield; crtek = e[crtek][\nexte].value } } )).play)

e[\middle].nexte = { [ \coda, \intro, \lone ].choose };