Function for fractals from initial rhythm pattern

hey,

im using this function for creating fractals from an initial rhythm pattern:

(
var 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;
};

var 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;
	//pot_ioi = 1;

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

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

		intermediate_result = 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();

	};

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

var getAbstractions = { |durations|
	var low_abs, high_abs, abstractions = [];

	durations.debug("rhythm");

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

	//add the initial rhythm pattern to the array of abstractions
	abstractions = abstractions.add(durations);

	//add abstractions with higher density to the array of abstractions
	high_abs = high_dens.(durations);
	high_abs.do{|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(durations).debug("rhythm index");
	abstractions.debug("abstractions");
};

var convertToBinary = { |arrays|
	arrays.collect { |row|
		var max;
		row = row.integrate;
		max = row.maxItem;
		Array.fill(max, 0).putEach(row % max, 1)
	};
};

~getAbs = { |durations|

	var arrays = getAbstractions.(durations);
	var binary = convertToBinary.(arrays);

	var patterns = arrays.collect { |array|
		Pseq(array, 1);
	};

	(arrays: arrays, binary: binary, patterns: patterns);
};
)

There is one issue i would like to fix and it has something to do with the high_dens function:

when you pass in an array which includes 1 as the smallest integer value it works nicely:

~getAbs.([3, 1, 2, 2])[\arrays];

but when you pass in an array which does not include 1 as the smallest value you dont get the full set of abstractions:

~getAbs.([3, 3, 2])[\arrays];

is it enough to just exchange pot_ioi = ioi_sort.minItem; with pot_ioi = 1; in the high_dens function? and do you have any other suggestions in terms of coding style for this function? thanks.

1 Like

This is really a matter of your design of the algorithm spec… for you to decide. So far the one thing that’s known is, you’re not satisfied with the result of using minItem.

  • [1, 2, 3] you want 1
  • [2, 3, 3] you said above that you didn’t want 2
  • What about [2, 4, 4] then?
    • Could be just 1?
    • Or maybe you want the GCD? In that case pot_ioi = 2 in this case, where [2, 3] would give you 1.

In any case, nobody here can tell you whether it’s “enough” to use a constant 1 – because it’s your algorithm and your requirement.

hjh

hey thanks for your reply,

i think the current implementation is nice for an initial array which contains 1 as the lowest value.
then you get an array with just one item == array.size at index 0 and another array filled with just 1s for the last index.
so minimum rhythmic density at index 0 vs. maximum rhythmic density at the last index and you could scale them via multiplication / division and go through them via a specific sequence for the index.

unfortunately the [3, 3, 2] example doesnt create an array filled with 1s at the last index with my implementation and im not sure right now if the function works correctly if i just hardcode 1 for pot_ioi.

never thought of GCD, maybe that would work.

i was also thinking what happens if you are not using integer values for the initial rhyhtm pattern.
i like this blending idea between eucledian and equal divisions:

// morphing between eucledian and equal divisions
(
~blend = { |n, k, blend|
	Bjorklund2(n, k).blend(Psubdivide(n, k).asStream.nextN(n), blend);
};
)

// eucledian divisions
~blend.(3, 8, 0);

// 50% blend between eucledian and equal divisions
~blend.(3, 8, 0.5);
1 Like