Dictionary inside Pattern

Couple quick comments, hopefully helpful

  • that looks like an array to me, not a dictionary

  • if your first example works, I’d expect the second to, too. But I’m not conversant with the Pn thing, is it necessary here? Looks kinda redundant. Anyway, you could try Pfunc instead, like this

\dur, Pfunc{|event| Pseq(~absDict.at(event[\index]), inf)}

typing this from scant memory, on tiny phone, with big thumbs, apologies if it’s gibberish,
eddi
https://soundcloud.com/all-n4tural/sets/spotlight
https://alln4tural.bandcamp.com

hey, thanks for your reply. unfortunately your code is not working

ERROR: Primitive '_Event_Delta' failed.

Should be Plazy, not Pfunc, I think.

hjh

thanks @jamshark70 ts working with:

\index, 4,
\dur, Pn(Plazy {|event|
			Pseq(~absDict.at(event[\index]), 1)
		},inf),

ive overlooked something i guess. i tried to sequence ~absDict with \durIndex, Pseq([0,1,2,3,4], inf), and thought i would get a sequence like this:

4.0, 
2.125, 1.875, 
1.25, 0.875, 1.875, 
1.25, 0.875, 1.25, 0.625, 
1.25, 0.875, 0.5, 0.75, 0.625`

but at some point the items in the array get wrapped or something. what has to be changed here?

(
SynthDef(\test, {
	var gainEnv = EnvGen.ar(Env.perc(0.01,0.4), \gt.kr(1), doneAction: Done.freeSelf);
	var sig = SinOscFB.ar(\freq.kr(150), 0.6);
	sig = sig * gainEnv;
	sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.25));
	Out.ar(\out.kr(0), sig);
}).add;
)

~absDict = [ [ 4.0 ], [ 2.125, 1.875 ], [ 1.25, 0.875, 1.875 ], [ 1.25, 0.875, 1.25, 0.625 ], [ 1.25, 0.875, 0.5, 0.75, 0.625 ] ];

(
Pbindef(\test,
	\instrument, \test,

	\durIndex, Pseq([0,1,2,3,4], inf),

	\dur, Pn(Plazy {|event|
		Pseq(~absDict.at(event[\durIndex]), 1).trace
	},inf),

	\midinote, 60,
	\amp, 0.1,

	\out, 0,
).play;
)

What’s happening is \durIndex is getting a new value for every event, as you would expect, but Plazy returns patterns that can span any number of events. The event argument in Plazy only gets reassigned each time it needs to generate a new sub-pattern, but meanwhile multiple events have likely passed during this time, so you are essentially skipping over durIndexes when the Pseq is longer than 1. That is confusing to explain, so here is an example to demonstrate what’s happening:

(
~iter = -1;
Pbindef(\test,
	\instrument, \test,
    \durIndex, Pseq([0,1,2,3,4], inf),
	\dur, Pn(Plazy {|event|
        ~iter = ~iter + 1;
        ~iter.debug("expected \durIndex:");
        event[\durIndex].debug("actual \durIndex:");
		Pseq(~absDict.at(event[\durIndex]), 1).trace
	},inf),
	\midinote, 60,
	\amp, 0.1,
	\out, 0,
).play;
)

Notice that the \durIndex value is going up faster than you expected because it is counting events when you want it to be counting iterations of the Plazy function. So you can solve this problem by using that ~iter variable I created in the previous example:

(
~iter = -1;
Pbindef(\test1,
	\instrument, \test,
	\dur, Pn(Plazy {
        ~iter = ~iter + 1;
		Pseq(~absDict.wrapAt(~iter), 1).trace
	},inf),
	\midinote, 60,
	\amp, 0.1,
	\out, 0,
).play;
)

But really I think it is better to just flatten your ~absDict array and pass that to a Pseq, unless there is some other reason you need to keep it as an array of arrays?

(
Pbindef(\test2,
	\instrument, \test,
    \dur, Pseq(~absDict.flatten, inf).trace,
	\midinote, 60,
	\amp, 0.1,
	\out, 0,
).play;
)

Hope that helps!

2 Likes

The ddwPatterns quark has a cute way to do it (of course, as it’s an extension, you’re unlikely to find it by yourself):

d = Pembedn(
	// this pattern should return patterns (made here by `collect`)
	// each pattern will be embedded one by one
	Pseq(~absDict, inf).collect { |array| Pseq(array, 1) },
	inf
).asStream;

d.nextN(10);
-> [ 4.0, 2.125, 1.875, 1.25, 0.875, 1.875, 1.25, 0.875, 1.25, 0.625 ]

In Pbind, it would be \dur, Pembedn( ... same arguments ... ).

hjh

2 Likes

hey thanks for you helpful replies @PitchTrebler @jamshark70.
i think sequencing the different subarrays with Pseq is more a special case of using ~abDict.
I think the usual one would be: play a specific subarray at an index and change the index at some point in time so another subarray will be played.
I didint know that both of these uses need a different control structure.
Im sorry this has not been coming to my mind yet. i think its even more complicated now:

Im using this function for generating an initial L-System and building abstractions of it and store them in a Dictionary and order them by size (lower to higher rhythmic density), so i can access the different abstractions in Pbind at their index.

(
~rewriteWord = {
	arg word, rules, iter=6;
    iter.do{
		word = Array.newFrom(word)
		.collect{|c| rules[c.asSymbol]}
		.join;
    };
    word
};

~getDur =  {
	arg word, notesDict;
  // streams are created inside this function
  // so every time we call ~getDur, streams are resetted
	var streams = notesDict.collect{|seq| Pseq(seq,inf).asStream};
	Array.newFrom(word).collect{|c| streams[c.asSymbol].next }
};

//function for cropping L-System
~sumUpTo = {
	arg array, total;
	var sum = 0, i = 0, result = Array.new;
	while { i < array.size and: { sum < total } } {
		sum = sum + array[i];
		if(sum > total) {
			result = result.add(array[i] - (sum - total));
		} {
			result = result.add(array[i]);
		};
		i = i + 1;
	};
	result
};

/*
functions for building Abstractions
creating abstractions with lesser density: reduce the density by joining two values
*/

~low_dens = {
	arg array;
	var ioi, result = [], min_index, neighbor_index;
	ioi = array.copy();

	ioi.debug("start ioi");

	while {ioi.size() > 1} {
		// in every step, we will reduce ioi until it has size 1
		var intermediate_result = [];
		min_index = ioi.minIndex;

		// if first element is minimum element, the neighor can only be to the right
		if (min_index == 0) {
			neighbor_index = 1;
		} {
			// else if last element is minimum element, the neighbor can only be to the left
			if (min_index == (ioi.size - 1)) {
				neighbor_index = (ioi.size - 2);
			} {
				// else we're in the middle, so use the neighbor which is smallest (arbitrary choice)
				var dir = 1;
				if (ioi[min_index - 1] < ioi[min_index + 1]) {
					dir = -1;
				};
				neighbor_index = min_index + dir;
			};
		};
		// swap neighbor and min_index so that neighbor is always bigger than min_index
		// - this makes copying easier in the next step
		if (neighbor_index < min_index) {
			var tmp = min_index;
			min_index = neighbor_index;
			neighbor_index = tmp;
		};

		// make a new list consisting of everything before min_index, sum of min el and its neighbor, everything after neighbor
		intermediate_result = ioi.copyRange(0, min_index-1) ++ (ioi[min_index] + ioi[neighbor_index]) ++ ioi.copyRange(neighbor_index+1, (ioi.size-1));

		if (intermediate_result.size >= ioi.size) {
			"ERROR!".postln;
		};

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();
	};

	// return the complete list of reductions
	result;
};

/*
creating abstractions with higher density by replacing a value with:
1. a smaller potential IOI value
2. and the difference between it and the value being replaced
*/

~high_dens = {
	arg array;
	var ioi, ioi_sort, pot_ioi, result = [], index_max;
	ioi = array.copy();
	ioi.debug("start ioi");

	ioi_sort = ioi.as(Set).as(Array).sort;
	pot_ioi = ioi_sort.minItem;

	while { ioi.maxItem > pot_ioi } {
		var intermediate_result = [];

		index_max = ioi.findAll([ioi.maxItem]).choose;

		intermediate_result = intermediate_result.add(ioi);
		intermediate_result = intermediate_result.insert(index_max+1, [ioi[index_max] - pot_ioi, pot_ioi]).flat;
		intermediate_result.removeAt(index_max);

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();

	};

	result;
};

// now define rules
~rules = (A:"AB", B:"A");

// define durations for each symbol
~lsys = (
	A: [0.25, 0.5],
	B: [0.5, 0.125],
);

// Axiom
~axiom = "A";

// rewriting iteration
~axiom = ~rewriteWord.(~axiom, ~rules, 6);

// getting durations
~durations = ~getDur.(~axiom, ~lsys);

//crop L-System to specific length
~lsys = ~sumUpTo.(~durations, 4);

//create Dictionary for abstractions
~absDict = Dictionary.new;

//add abstractions with lower density to the dictionary
~low_abs = ~low_dens.(~lsys);
~low_abs.do({
	arg el, elindex;
	~absDict.put((el.size), el);
});

//add initial L-System to Dictionary
~absDict.put(~absDict.size+1, ~lsys);

//add abstractions with higher density to the dictionary
~high_abs = ~high_dens.(~lsys);
~high_abs.do({
	arg el, elindex;
	~absDict.put((el.size), el);
});

//get Dictionary in order array.size low -> high
~absDict = ~absDict.atAll(~absDict.order);

~absDict.debug("abs dict");
)

The last part where i put everything in ~absDict is a bit clunky i guess and also pretty weird because the result is not a Dictionary. its an array with subarrays if im not mistaken.
Im also not sure if a Dictionary or an array with subarrays is better for my intended use in Pbind. But this beeing said the array cannot be flattened. the whole thing is about having these different rhythmic cells independent from each other stored at their index.

EDIT: ive tried to rework the function so you can call ~getLsys and get a new Lsystem and abstractions on the fly. probably some coding flaws here but its working. But the dictionary / array confusion is still there. any ideas?

(
~rewriteWord = {
	arg word, rules, iter=6;
    iter.do{
		word = Array.newFrom(word)
		.collect{|c| rules[c.asSymbol]}
		.join;
    };
    word
};

~getDur =  {
	arg word, notesDict;
  // streams are created inside this function
  // so every time we call ~getDur, streams are resetted
	var streams = notesDict.collect{|seq| Pseq(seq,inf).asStream};
	Array.newFrom(word).collect{|c| streams[c.asSymbol].next }
};

//function for cropping L-System
~sumUpTo = {
	arg array, total;
	var sum = 0, i = 0, result = Array.new;
	while { i < array.size and: { sum < total } } {
		sum = sum + array[i];
		if(sum > total) {
			result = result.add(array[i] - (sum - total));
		} {
			result = result.add(array[i]);
		};
		i = i + 1;
	};
	result
};

//creating abstractions with lesser density: reduce the density by joining two values

~low_dens = {
	arg array;
	var ioi, result = [], min_index, neighbor_index;
	ioi = array.copy();

	ioi.debug("start ioi");

	while {ioi.size() > 1} {
		// in every step, we will reduce ioi until it has size 1
		var intermediate_result = [];
		min_index = ioi.minIndex;

		// if first element is minimum element, the neighor can only be to the right
		if (min_index == 0) {
			neighbor_index = 1;
		} {
			// else if last element is minimum element, the neighbor can only be to the left
			if (min_index == (ioi.size - 1)) {
				neighbor_index = (ioi.size - 2);
			} {
				// else we're in the middle, so use the neighbor which is smallest (arbitrary choice)
				var dir = 1;
				if (ioi[min_index - 1] < ioi[min_index + 1]) {
					dir = -1;
				};
				neighbor_index = min_index + dir;
			};
		};
		// swap neighbor and min_index so that neighbor is always bigger than min_index
		// - this makes copying easier in the next step
		if (neighbor_index < min_index) {
			var tmp = min_index;
			min_index = neighbor_index;
			neighbor_index = tmp;
		};

		// make a new list consisting of everything before min_index, sum of min el and its neighbor, everything after neighbor
		intermediate_result = ioi.copyRange(0, min_index-1) ++ (ioi[min_index] + ioi[neighbor_index]) ++ ioi.copyRange(neighbor_index+1, (ioi.size-1));

		if (intermediate_result.size >= ioi.size) {
			"ERROR!".postln;
		};

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();
	};

	// return the complete list of reductions
	result;
};

/*
creating abstractions with higher density by replacing a value with:
1. a smaller potential IOI value
2. and the difference between it and the value being replaced
*/

~high_dens = {
	arg array;
	var ioi, ioi_sort, pot_ioi, result = [], index_max;
	ioi = array.copy();

	ioi.debug("start ioi");

	ioi_sort = ioi.as(Set).as(Array).sort;
	pot_ioi = ioi_sort.minItem;

	while { ioi.maxItem > pot_ioi } {
		var intermediate_result = [];

		index_max = ioi.findAll([ioi.maxItem]).choose;

		intermediate_result = intermediate_result.add(ioi);
		intermediate_result = intermediate_result.insert(index_max+1, [ioi[index_max] - pot_ioi, pot_ioi]).flat;
		intermediate_result.removeAt(index_max);

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();

	};

	result;
};

~getLsys = {
	arg rules, lsys, axiom, length;
	var durations, low_abs, high_abs;
	//create Dictionary for abstractions
	var absDict = Dictionary.new;

	// rewriting iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// getting durations
	durations = ~getDur.(axiom, lsys);

	//crop L-System to specific length
	lsys = ~sumUpTo.(durations, length);

	//add abstractions with lower density to the dictionary
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		absDict.put((el.size), el);
	});

	//add initial L-System to Dictionary
	absDict.put(absDict.size+1, lsys);

	//add abstractions with higher density to the dictionary
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		absDict.put((el.size), el);
	});

	//get Dictionary in order low -> high
	absDict = absDict.atAll(absDict.order);

	absDict.debug("abs dict");
};

// define rules
~rules = (A:"AB", B:"A");
// define durations for each symbol
~lsys = (A: [0.25, 0.5], B: [0.5, 0.125]);
// Axiom
~axiom = "A";

~absDict = ~getLsys.(~rules, ~lsys, ~axiom, 4);
)

I think you are right, you need multiple control structures to do this. ~absDict needs to be accessed just once per measure, but your note pattern needs to generate one event per note, and the note pattern needs to know the durations obtained from the current measure of ~absDict. You also have a separate function to update ~absDict on the fly (~getLsys). So that is really 3 independent processes (update measure, update notes, update ~absDict). The way I would do this is by having a separate pattern for measures and notes, and then sharing data between these patterns. There are many ways to share data between patterns (see http://doc.sccode.org/Tutorials/A-Practical-Guide/PG_06g_Data_Sharing.html).

In the code below, I create 2 Pbindefs: the first one generates one event per measure, and the second one generates one event per note. I use a Pdefn to store the note duration data obtained from the measure pattern, which is then shared with the note pattern. Timing Offset is used to ensure the note durations are shared before the note events are generated.

Pdef.clear;
(
Pbindef(\measure,
    \type, \rest,
    \measureIndex, Pseries(0, 1, inf).trace(prefix: "measure index: "),
    \measure, Pfunc{|event| ~absDict.wrapAt(event.measureIndex)},
    \dur, Pfunc{|event| event.measure.sum},
    \shareData, Pfunc{|event| Pdefn(\noteDurs, Pseq(event.measure, inf))},
).play(quant: Quant(1.0, timingOffset: 0.01));

Pbindef(\note,
	\instrument, \test,
    \dur, Pdefn(\noteDurs).trace,
	\midinote, 60,
	\amp, 0.1,
	\out, 0,
).play(quant: 1.0);
)

// update measureIndex independently:
Pbindef(\measure, \measureIndex, Pseries(0, -1, inf).trace(prefix: "measure index: ")); // go backwards

// update ~absDict on the fly:
(
// define rules
~rules = (A:"AB", B:"A");
// define durations for each symbol
~lsys = (A: [0.25, 0.5], B: [0.5, 0.125]);
// Axiom
~axiom = "A";

~absDict = ~getLsys.(~rules, ~lsys, ~axiom, 4);
)

I hope this is what you need to get your system running as you specified. I think it is a good idea to keep ~absDict as an array because the order is important, plus it is easy to increment the index and just use the wrapAt method to keep it in bounds. By the way, that’s a nice little fractal system you have there!

1 Like

thanks so much, your explanations are really good :slight_smile:
i also like the fact that you have added (prefix: "measure index: "), so its more clear at with abstraction you are at the moment, thats really nice :slight_smile:
so you could either sequence trough all the abstractions with Pseries, just stay at one specific abstraction or do a sequence of a specific or an arbitrary selection of abstractions with Pseq Prand etc., thats really great, thanks a lot :slight_smile:
Pbindef(\measure, \measureIndex, Pseq([7,3,4],inf).trace(prefix: "measure index: ")); // sequence selection of abstractions

the only confusion i have, in ~getLsys Im defining absDict as a dictionary with
var absDict = Dictionary.new;
do you have any ideas for improving the function itself, so its more clear thats its output is an array with subarrays and not a dictionary?

~getLsys = {
	arg rules, lsys, axiom, length;
	var durations, low_abs, high_abs;
	//create Dictionary for abstractions
	var absDict = Dictionary.new;

	// rewriting iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// getting durations
	durations = ~getDur.(axiom, lsys);

	//crop L-System to specific length
	lsys = ~sumUpTo.(durations, length);

	//add abstractions with lower density to the dictionary
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		absDict.put((el.size), el);
	});

	//add initial L-System to Dictionary
	absDict.put(absDict.size+1, lsys);

	//add abstractions with higher density to the dictionary
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		absDict.put((el.size), el);
	});

	//get Dictionary in order low -> high
	absDict = absDict.atAll(absDict.order);

	absDict.debug("abs dict");
};

One thing I noticed:

absDict.put((el.size), el);

This line ensures that there is exactly one abstraction of each size. If that’s what you want, then I think you made a good choice in using a dictionary to build up your abstraction collection, and then converting it to an array at the end. However, what if you want to use a system that creates multiple abstractions of the same size? In that case, when you put an abstraction in the dictionary you will be overriding previous entries of the same size. To avoid this you should probably be using an array from the beginning, and you can still sort it by size at the end…

(
~getLsys = {
	arg rules, lsys, axiom, length;
	var durations, low_abs, high_abs;
	//create Array for abstractions
	var abstractions = Array.new;

	// rewriting iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// getting durations
	durations = ~getDur.(axiom, lsys);

	//crop L-System to specific length
	lsys = ~sumUpTo.(durations, length);

	//add abstractions with lower density to the dictionary
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//add initial L-System to Dictionary
	abstractions = abstractions.add(lsys);

	//add abstractions with higher density to the dictionary
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//get abstractions in order by size: low -> high
    abstractions = abstractions.sort({|a, b| a.size < b.size});

	abstractions.debug("abstractions array");
};
)

I also changed the name “absDict” to “abstractions” because I don’t want to get arrested by the naming police :laughing: They lurk behind every corner, you know…

thanks so much. ive also added lsys.debug("cropped L-System"); so you know what the initial L-System is after it has been cropped to its specific length and hard coded the length. i think it makes no sense to use another number then 4. i also tried out 8 but thats maybe a bit too long, i think you want some kind of rhythmic identity with the initial one. maybe the axiom could also be hardcoded i think you probably always start with A. is there a way to know at which index the initial L-System is, right away? Would be nice if its also in the post window with the two other things.

~getLsys = {
	arg rules, lsys, axiom;
	var durations, low_abs, high_abs;
	var abstractions = Array.new;

	// rewrite iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// get durations
	durations = ~getDur.(axiom, lsys);

	//crop initial L-System to a specific length
	lsys = ~sumUpTo.(durations, 4);

	lsys.debug("cropped L-System");

	//add abstractions with lower density to the array of abstractions
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//add initial L-System to the array of abstractions
	abstractions = abstractions.add(lsys);

	//add abstractions with higher density to the array of abstractions
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//order array of abstractions by size: low -> high
    abstractions = abstractions.sort({|a, b| a.size < b.size});

	abstractions.debug("L-System abstractions");
};

// define rules
~rules = (A:"AB", B:"A");
// define durations for each symbol
~lsys = (A: [0.25, 0.5], B: [0.5, 0.125]);
// Axiom
~axiom = "A";

~absArray = ~getLsys.(~rules, ~lsys, ~axiom);

I think you mean something like this?

(
Pbindef(\measure,
    \type, \rest,
    \measureIndex, Pseries(0, 1, inf).trace(prefix: "measure index: "),
    \absDictIndex, Pfunc{|event| event.measureIndex % ~absDict.size}.trace(prefix: "absDict index: "),
    \measure, Pfunc{|event| ~absDict.at(event.absDictIndex)}.trace(prefix: "abstraction: "),
    \dur, Pfunc{|event| event.measure.sum},
    \shareData, Pfunc{|event| Pdefn(\noteDurs, Pseq(event.measure, inf))},
).play(quant: Quant(1.0, timingOffset: 0.01));

Pbindef(\note,
	\instrument, \test,

    \dur, Pdefn(\noteDurs).trace,

	\midinote, 60,
	\amp, 0.1,

	\out, 0,
).play(quant: 1.0);
)

hey, thanks again for you help :slight_smile: \absDictIndex is giving me the same value then \measureIndex i thought more of something which is imbeded in ~getLsys so you know at which index in the array the initial L-System ~lsys is beside the abstractions build of it with ~low_abs and ~high_abs after you have evaluated ~getLsys

Ah, I see. Well, you can deduce this from your code in ~getLsys. You know the following:

  1. The size of your initial lsys (“cropped L-System”) is 4.
  2. The final abstractions array has one entry for each size.
  3. The abstractions array is ordered by size (smallest to largest)

Therefore, the initial lsys of size 4 must be at the 4th index, so the index is 3.

ah okay :slight_smile: . but actually i think for these start values its at index 11. and i think it depends on which values you choose for A: and B: in the beginning. when the values are bigger then these once ive chosen for testing the index for ~lsys should be smaller.

// define rules
~rules = (A:"AB", B:"A");
// define durations for each symbol
~lsys = (A: [0.25, 0.5], B: [0.5, 0.125]);
// Axiom
~axiom = "A";

~absArray = ~getLsys.(~rules, ~lsys, ~axiom);
cropped L-System: 0.25, 0.5, 0.5, 0.25, 0.125, 0.5, 0.5, 0.25, 0.5, 0.125, 0.25, 0.25

~absArray.at(11);
-> [ 0.25, 0.5, 0.5, 0.25, 0.125, 0.5, 0.5, 0.25, 0.5, 0.125, 0.25, 0.25 ]

My mistake, I misunderstood ~sumUpTo :confounded: I thought it was supposed to return an array of size 4, but it returns an array with a sum equal to 4. You can just return the index of lsys along with the abstractions array:

~getLsys = {
	arg rules, lsys, axiom;
	var durations, low_abs, high_abs;
	//create Array for abstractions
	var abstractions = Array.new;

	// rewriting iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// getting durations
	durations = ~getDur.(axiom, lsys);

	//crop L-System to specific length
	lsys = ~sumUpTo.(durations, 4);
    lsys.debug("cropped L-System");

	//add abstractions with lower density to the dictionary
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//add initial L-System to Dictionary
	abstractions = abstractions.add(lsys);

	//add abstractions with higher density to the dictionary
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//get abstractions in order by size: low -> high
    abstractions = abstractions.sort({|a, b| a.size < b.size});
    [abstractions, abstractions.indexOf(lsys)]
};

Then unpack this into different variables:

(
// define rules
~rules = (A:"AB", B:"A");
// define durations for each symbol
~lsys = (A: [0.25, 0.5], B: [0.5, 0.125]);
// Axiom
~axiom = "A";

~lsysresult = ~getLsys.(~rules, ~lsys, ~axiom);
~abstractionsArray = ~lsysresult[0];
~initialIndex = ~lsysresult[1].debug("index of initial lsys: ");
)

hey thanks a lot. ~sumUpTo should crop it to a sum equal to 4 or any other number of bars so you have a kind of rhythmic theme, when its longer then 4 its harder to find a rhythmic identity in the durations. my final solution is this:

(
~getAbs = {|rules, durations|
	var axiom, lsys, low_abs, high_abs, abstractions = [];

	// axiom
	axiom = "A";

	// rewrite iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// get durations
	lsys = ~getDur.(axiom, durations);

	//crop initial L-System to a specific length
	lsys = ~sumUpTo.(lsys, 4);
	lsys.debug("L-System");

	//add abstractions with lower density to the array of abstractions
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//add the initial L-System to the array of abstractions
	abstractions = abstractions.add(lsys);

	//add abstractions with higher density to the array of abstractions
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//order array of abstractions by size: low -> high
    abstractions = abstractions.sort({|a, b| a.size < b.size});

	abstractions.indexOf(lsys).debug("L-System index");
	abstractions.debug("abstractions");
};

// define rules
~rules = (A:"AB", B:"A");

// define durations for each symbol
~durations = (A: [0.25, 0.5], B: [0.5, 0.125]);

~absArray = ~getAbs.(~rules, ~durations);
)

I keep coming back to this thread because this idea of an index that advances only on phrase boundaries lines up neatly with list patterns’ embedding of items – that is, in Pseq([pattern1, pattern2]), first pattern1 takes complete control and runs until the end, before pattern2 takes over.

The catch is that the data structure contains the data to Pseq, but not Pseqs themselves – and the data need to be directly available to the L-system.

One way to hack it might be to create a function to update the data storage. Then, have one dictionary for the arrays and one dictionary for patterns.

(
~absDict = ();
~patDict = ();

~putL = { |key, array|
	~absDict.put(key, array);
	~patDict.put(key, Pseq(array, 1));
};

// or, wipe out the whole system and start again with 'arrays'
~resetL = { |arrays|
	~absDict.clear;
	~patDict.clear;
	arrays.do { |array, i|
		~putL.(i, array);
	};
};
)

… and all changes to the dictionaries go through this.

I usually try to avoid parallel collections, but in this case, it would enable you to do:

~resetL.([ [ 4.0 ], [ 2.125, 1.875 ], [ 1.25, 0.875, 1.875 ], [ 1.25, 0.875, 1.25, 0.625 ], [ 1.25, 0.875, 0.5, 0.75, 0.625 ] ]);

~absDict
-> ( 1: [ 2.125, 1.875 ], 0: [ 4.0 ], 2: [ 1.25, 0.875, 1.875 ], 4: [ 1.25, 0.875, 0.5, 0.75, 0.625 ], 
  3: [ 1.25, 0.875, 1.25, 0.625 ] )

(
p = Pbind(
	\degree, Pn(Pseries(-7, 1, 15), inf),
	\dur, Pdict(~patDict, Pn(Pseries(0, 1, 5), inf))
).play;

// or
(
var index = Pn(Pseries(0, 1, 5), inf).asStream;

p = Pn(Pbind(
	\degree, Pseries(0, 1, inf),
	\dur, Pdict(~patDict, Pfin(1, index))
), inf).play;
)

hjh

1 Like

hey, thanks for your further ideas :slight_smile: at least for the initial L-System which is created by ~getDur inside ~getAbs you already creating streams. is this kind of redundant then, when combined with ~resetL ?

(
~rewriteWord = {|word, rules, iter=6|
	iter.do{
		word = Array.newFrom(word)
		.collect{|c| rules[c.asSymbol]}
		.join;
	};
	word
};

~getDur = {|word, durDict|
	// streams are created inside this function
	// so every time we call ~getDur, streams are resetted
	var streams = durDict.collect{|seq| Pseq(seq,inf).asStream};
	Array.newFrom(word).collect{|c| streams[c.asSymbol].next }
};

// function for cropping L-System

~sumUpTo = {|array, total|
	var sum = 0, i = 0, result = Array.new;
	while { i < array.size and: { sum < total } } {
		sum = sum + array[i];
		if(sum > total) {
			result = result.add(array[i] - (sum - total));
		} {
			result = result.add(array[i]);
		};
		i = i + 1;
	};
	result
};

// creating abstractions with lesser density: reduce the density by joining two values

~low_dens = {|array|
	var ioi, min_index, neighbor_index, result = [];
	ioi = array.copy();

	while {ioi.size() > 1} {
		// in every step, we will reduce ioi until it has size 1
		var intermediate_result = [];
		min_index = ioi.minIndex;

		// if first element is minimum element, the neighor can only be to the right
		if (min_index == 0) {
			neighbor_index = 1;
		} {
			// else if last element is minimum element, the neighbor can only be to the left
			if (min_index == (ioi.size - 1)) {
				neighbor_index = (ioi.size - 2);
			} {
				// else we're in the middle, so use the neighbor which is smallest (arbitrary choice)
				var dir = 1;
				if (ioi[min_index - 1] < ioi[min_index + 1]) {
					dir = -1;
				};
				neighbor_index = min_index + dir;
			};
		};
		// swap neighbor and min_index so that neighbor is always bigger than min_index
		// - this makes copying easier in the next step
		if (neighbor_index < min_index) {
			var tmp = min_index;
			min_index = neighbor_index;
			neighbor_index = tmp;
		};

		// make a new list consisting of everything before min_index, sum of min el and its neighbor, everything after neighbor
		intermediate_result = ioi.copyRange(0, min_index-1) ++ (ioi[min_index] + ioi[neighbor_index]) ++ ioi.copyRange(neighbor_index+1, (ioi.size-1));

		if (intermediate_result.size >= ioi.size) {
			"ERROR!".postln;
		};

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();
	};

	// return the complete list of reductions
	result;
};

// creating abstractions with higher density by replacing a value with:
// a smaller potential IOI value and the difference between it and the value being replaced

~high_dens = {|array|
	var ioi, ioi_sort, pot_ioi, index_max, result = [];
	ioi = array.copy();

	ioi_sort = ioi.as(Set).as(Array).sort;
	pot_ioi = ioi_sort.minItem;

	while { ioi.maxItem > pot_ioi } {
		var intermediate_result = [];

		index_max = ioi.findAll([ioi.maxItem]).choose;

		intermediate_result = intermediate_result.add(ioi);
		intermediate_result = intermediate_result.insert(index_max+1, [ioi[index_max] - pot_ioi, pot_ioi]).flat;
		intermediate_result.removeAt(index_max);

		// add it to the list of results
		result = result.add(intermediate_result);

		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();

	};

	result;
};

~getAbs = {|rules, durations|
	var axiom, lsys, low_abs, high_abs, abstractions = [];

	// axiom
	axiom = "A";

	// rewrite iteration
	axiom = ~rewriteWord.(axiom, rules, 6);

	// get durations
	lsys = ~getDur.(axiom, durations);

	//crop initial L-System to a specific length
	lsys = ~sumUpTo.(lsys, 4);
	lsys.debug("L-System");

	//add abstractions with lower density to the array of abstractions
	low_abs = ~low_dens.(lsys);
	low_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//add the initial L-System to the array of abstractions
	abstractions = abstractions.add(lsys);

	//add abstractions with higher density to the array of abstractions
	high_abs = ~high_dens.(lsys);
	high_abs.do({
		arg el, elindex;
		abstractions = abstractions.add(el);
	});

	//order array of abstractions by size: low -> high
    abstractions = abstractions.sort({|a, b| a.size < b.size});

	abstractions.indexOf(lsys).debug("L-System index");
	abstractions.debug("abstractions");
};

// define rules
~rules = (A:"AB", B:"A");

// define durations for each symbol
~durations = (A: [0.25, 0.5], B: [0.5, 0.125]);

~absArray = ~getAbs.(~rules, ~durations);
)

in the end im trying to use the L-System abstractions as a Pulse inside PSPdiv and not sure right now if Data Sharing with Pbindef(\measure) from @PitchTrebler or your appraoch works best here. but this is already working correctly i think:

(
Pdef(\kick,
	Pbind(
	[...]
)
);

Pdef(\sd,
	Pbind(
	[...]
)
);

Pdef(\hh,
	Pbind(
	[...]
)
);
)

(
~index = Pn(Pseq([3,5,7], inf)).asStream.trace(prefix: "measure index: ");
~pulse = Pdict(~patDict, Pfin(1, PL(\index))).trace;

~div_kick = 4;
~div_sd = 3;
~div_hh = 5;

~divBase_kick = 1;
~divBase_sd = 2;
~divBase_hh = 1;

~divType = \seq;

Pdef(\rhythms,
	PSPdiv(
		PL(\pulse),
		[Pdef(\kick), Pdef(\sd), Pdef(\hh)],
		[PL(\div_kick), PL(\div_sd), PL(\div_hh)],
		[PL(\divBase_sd), PL(\divBase_sd), PL(\divBase_hh)],
		PL(\divType),
	),
).play(t, quant:1);
)

// update ~absArray on the fly:
(
~rules = (A:"AB", B:"AC", C:"BA");
~durations = (A: [0.25, 0.5], B: [0.5, 0.125], C: [0.333, 0.167]);
~absArray = ~getAbs.(~rules, ~durations);
~resetL.(~absArray);
)