Traverse pattern lists with some quirks

i’m trying to turn a hand-writtern diagram/score into an autonomously playing pattern. it consists of a number of sub-patterns that play one after another, except some can break this sequence and jump to another level/layer of the sequence. for simplicity sake, here it is with 2 levels/layers:

[1, 2, 3], [3, 2, 1], [4, 4, 4], [3, 5, 9], [7, 7, 9]
[8, 7, 5], [6, 6, 5], [4, 3, 2], [8, 8, 8], [1, 1, 1]

so, for instance, after the third pattern, [4, 4, 4], it can either go on undisturbed onto [3, 5, 9] or jump down one level to [8, 8, 8]. and so on…

((note that i’m placing these into arrays just to represent the sequences, this is very much just a sketch)).

what would you say would be the most effective and flexible way to implement this?

i thought of dictionaries or multidimensional arrays but i don’t quite know how to traverse those with these rules which affect only some of the patterns

thanks!
n

Pfsm, a finite state machine, might work well, based on how you’re describing it. Though it won’t work based on going down one level, you’ll set up options for where each sub-pattern can go next.

http://doc.sccode.org/Tutorials/A-Practical-Guide/PG_Cookbook06_Phrase_Network.html

http://doc.sccode.org/Classes/Pfsm.html

Or maybe Prewrite which is an L-system. The help file also has a pretty good example, but I find that class a bit confusing. Prewrite | SuperCollider 3.11.2 Help

Finite state machine. How did i not think of that??? Amazing, appreciated.

Is there a way to trace and send the current Pfsm state index elsewhere via MIDI or whatever?
thanks!

This is adapted from the help file, and it’s maybe not the most elegant way to do it, but what I did is provide an array for every state’s item. The first entry in the array is the value(s) that you care about, and the second entry is the index of the state inside the machine.

The Routine runs the sequence, and takes in the state’s item, splitting it into two variables.

The first one controls the frequency of a synth, the second provides the index.

(
a = Pfsm([
	#[0,1],
	[90, 0], #[0, 0, 3],
	[60, 1], #[2],
	[30, 2], #[0, 2],
	Pseq([74, 75, 76, 77] +++ 3, 1), #[2, 3, 3],
	nil, nil
], inf).asStream;

Routine({
	loop({
		var val, index;
		#val, index = a.next;
		{ SinOsc.ar(\freq.kr(440)) * EnvGen.kr(Env.perc(0.01, 0.1, 0.2), doneAction: 2) ! 2 }.play(args: [freq: val.postln.midicps]);
		index.postln;
		0.3.wait;
	})
}).play;
)

The Pseq section is a bit unfortunate. It didn’t seem to work right when I just had [Pseq([74, 75, 76, 77], 1), 3], so the +++ laminates the index into all the steps of the Pseq.

You can then build out the Routine with a switch or bunch of if statements that use the index to do stuff:

MIDIClient.destinations // Find your device here, note the index
m = MIDIOut(1)

(
a = Pfsm([
	#[0,1],
	[90, 0], #[0, 0, 3],
	[60, 1], #[2],
	[30, 2], #[0, 2],
	Pseq([74, 75, 76, 77] +++ 3, 1), #[2, 3, 3],
	nil, nil
], inf).asStream;

Routine({
	loop({
		var val, index;
		#val, index = a.next;
		{ SinOsc.ar(\freq.kr(440)) * EnvGen.kr(Env.perc(0.01, 0.1, 0.2), doneAction: 2) ! 2 }.play(args: [freq: val.postln.midicps]);
		
		switch(index.postln,
			0, { m.noteOn(chan: 0, note: val, veloc: 100) },
			1, { m.noteOn(chan: 0, note: val, veloc: 100) },
			2, { m.noteOn(chan: 0, note: val, veloc: 100) },
			3, { m.noteOn(chan: 0, note: val, veloc: 100) }
		);
		0.3.wait;
		
		m.noteOff(0, val);
	})
}).play;
)