Creating rhythmic meso / macro structure with Demand trigger mask

hey,

i have a function which creates fractals from an initial rhythm pattern and stores them as Pseqs in a dictionary. i would then sequence different indices of this dictionary with Pdict to access the different fractal rhythms likes this:


(
// fractals from initial rhythm: [ 3, 1, 2, 2, 1, 3 ] stored in ~durations
~durations = [
	[ 12 ],
	[ 6, 6 ],
	[ 6, 3, 3 ],
	[ 3, 3, 3, 3 ],
	[ 3, 3, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 3 ], // initial rhythm at index 5
	[ 2, 1, 1, 2, 2, 1, 3 ],
	[ 2, 1, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 1, 1, 1, 2, 1 ],
	[ 1, 1, 1, 1, 2, 1, 1, 1, 2, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];

~getDict = { |durations|
	var arrays, dictionary = ();
	dictionary.clear;
	durations.do { |array, key|
		dictionary.put(key, Pseq(array, 1));
	};
	dictionary;
};

// store Pseqs with durations in a dictionary
~patDict = ~getDict.(~durations);

// sequence of indices
~index = Pseq([0, 4, 2, 1], 1);
)

// sequence different fractal patterns with Pdict
x = Pbind(\instrument, \default, \dur, 0.125 * Pdict(~patDict, ~index).trace).play;

x.stop;

lets say i would convert these arrays stored in ~durations into binary form like this:

(
~binaryDurations = [
	[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0 ],
	[ 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1 ],
	[ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];
)

and would store them in a dictionary like this:

(
~getDict = { |durations|
	var arrays, dictionary = ();
	dictionary.clear;
	durations.do { |array, key|
		dictionary.put(key, array);
	};
	dictionary;
};

~patDict = ~getDict.(~binaryDurations);
)

how can i sequence different arrays in binary form stored in ~patDict to use them as a trigger mask with the following SynthDef to create a rhythmic meso / macro structure? is it in anyway possible when using the .set method to access a different array of ~patDict to sync these with the triggers from Impulse.ar ? how would you go about that?

(
var hanningWindow = { |phase|
	(1 - (phase * 2pi).cos) / 2 * (phase < 1);
};

var triggerMask = { |trig, maskOn, sequence, mod|
	var mask = Demand.ar(trig, 0, Dseq([Dser(sequence, mod)], inf));
	mask.poll(trig, 'sequence');
	trig * Select.ar(maskOn, [K2A.ar(1), mask]);
};

SynthDef(\pulsar, {

	var tFreq = \tFreq.kr(10);
	var trig = triggerMask.(Impulse.ar(tFreq), \maskOn.kr(1), \sequence.kr(Array.fill(16, 1)), \mod.kr(16));
	var grainFreq = \freq.kr(400);
	var phase = Sweep.ar(trig, grainFreq);

	var grainWindow = hanningWindow.(phase);

	var sig = sin(phase * 2pi);

	sig = sig * grainWindow * \amp.kr(0.25);

	sig = Pan2.ar(sig, \pan.kr(0));
	OffsetOut.ar(\out.kr(0), sig);
}).add;
)

// masking sequence
~sequence = [1, 0, 1, 1, 0];

x = Synth(\pulsar, [\tFreq, 10, \freq, 800, \maskOn, 1, \sequence, ~sequence, \mod, 5]);

// use another sequence
x.set(\sequence, [1, 1, 0, 0, 1]);

// set mod
x.set(\mod, 3);

// disable trigger mask
x.set(\maskOn, 0);

x.free;
1 Like

Very confused by your question could you briefly rephrase it?
Are you asking how to hold off updating a value until a trigger happens? If so, the answer is Latch.

hey, thanks for your reply.

the first block of code shows my implementation of how to setup a dictionary filled with fractal patterns and the ability to sequence them with Pdict to lay out a meso / macro structure of a piece of music when using control rate triggers. thats working fine.

some of my latest posts have been about creating meso / macro structures with audio rate triggers Limitations of Composition in SC - #21 by dietcv and Making transitions with language or server side sequencing

i hope it is somehow possible to sequence these fractal rhythms when converted to binary form to use them with the Demand trigger mask in the same way i have used them before with Pdict.
I would like to sequence different indices of arrays stored in the dictionary synced to the triggers from Impulse.ar.

i thought of using .set with a sequence of different indices to index the fractal patterns in binary form inside the dictionary but dont know how to go about language and server sync.

i have also never really used Synths before, so i dont know what can be done with Tasks or Routines.

1 Like

A starting point here would be to get rid of the dictionary and keep the durations in an array (you already have the array :wink: and the dictionary keys are an integer sequence starting at 0, so… you gain nothing from the dictionary! simplify…), and use Pswitch instead of Pdict.

The server doesn’t understand dictionaries… but the demand UGens do include Dswitch mirroring Pswitch. So if you eliminate the unnecessary complication, you’re closer to what the server can do.

For the on/off values, you can Dseq the arrays, put these into a Dswitch array, and Demand them by a constant-rate trigger. If other values should update only upon “true,” use this binary Demand as the trigger for other Demand units. – EDIT: though if you do this directly, then multiple 1s in a row might just become one long trigger. I believe TDuty would handle that: TDuty.ar(trigFreq.reciprocal, ...) instead of Demand.ar(Impulse.ar(trigFreq), ...) – this is for the Dswitch timing pattern – from this you would get triggers suitable to use in Demand units for other parameters.

hjh

3 Likes

hey, thanks alot this is my first attempt. i also tried out TDuty instead of Demand but then i get constant 1s as triggers when i increase the trigger frequency.

(
~binaryDurations = [
	[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0 ],
	[ 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1 ],
	[ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];
)

(
var triggerMask = { |trig, maskOn, arrayOfBinaries, arrayIndex|
	var arrayOfSequences = arrayOfBinaries.collect { |array|
		Dseq(array, 1);
	};
	var mask = Demand.ar(trig, 0, Dswitch(arrayOfSequences, Dseq(arrayIndex, inf)));
	mask.poll(trig, 'sequence');
	trig * Select.ar(maskOn, [K2A.ar(1), mask]);
};

SynthDef(\test, {

	var tFreq = \tFreq.kr(10);
	var trig = Impulse.ar(tFreq);
	var sig;

	trig = triggerMask.(trig, \maskOn.kr(1), ~binaryDurations, \index.kr(Array.fill(4, 1)));

	sig = Pan2.ar(trig, \pan.kr(0));

	OffsetOut.ar(\out.kr(0), sig);
}).add;
)

x = Synth(\test, [\tFreq, 10, \maskOn, 1, \index, [10, 4, 2, 1]]);

x.free;

EDIT: i think i can already see a problem when wanting to have the ability to create a new set of ~binaryDurations or use another size for the index array while the Synth is playing.

EDIT: for the conversion to binary i think i found a solution:

(
~durations = [
	[ 12 ],
	[ 6, 6 ],
	[ 6, 3, 3 ],
	[ 3, 3, 3, 3 ],
	[ 3, 3, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 3 ],
	[ 2, 1, 1, 2, 2, 1, 3 ],
	[ 2, 1, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 1, 1, 1, 2, 1 ],
	[ 1, 1, 1, 1, 2, 1, 1, 1, 2, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];
)

(
~convertToBinary = { |arrays|
	arrays.collect{ |array|
		array.collect{ |item, index|
			Array.fill(item, { |i| if(i == 0, { 1 }, { 0 } ) });
		}.flat;
	};
};
~binaryDurations = ~convertToBinary.(~durations);
)

is there a way to go about my implementation to maximize its flexibility for composition?

i mean hardcoding of ~binaryDurations on the one hand and also only having the possibility for a specific size for arrayIndex after evaluating the SynthDef.

i thought of this implementation:

var mask = Demand.ar(trig, 0, Dswitch(arrayOfSequences, Dseq([Dser(arrayIndex, arraySize)], inf)));

but then you have always to change arraySize in relation to the array you pick for arrayIndex. is there an implementation which does this automatically when changing the array for arrayIndex with a different size?

and is ~binaryDurations always fixed with SynthDef evaluation, there is no way to change it afterwards?

and what do you think of my ~convertToBinary function ? is this the right way to do it?

I believe the answer is no. You have to predefine the maximum array size in the SynthDef, and this has to exist all the time – the array’s allocated size doesn’t change. The “size I’m using” is a separate concept, so you have to provide it separately.

Use an arrayed control.

That will work.

Here’s another way:

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

hjh

1 Like

How about?

~convertToBinary =  _.collect{|durs| 
	durs.collect{ |d| [1] ++ [0].dup(d - 1) }.flat 
};
1 Like

thanks for you replies :slight_smile:
then i would go with something like that:

(
var triggerMask = { |trig, maskOn, arrayOfBinaries, arrayIndex, arraySize|
	var arrayOfSequences = arrayOfBinaries.collect { |array|
		Dseq(array, 1);
	};
	var mask = Demand.ar(trig, 0, Dswitch(arrayOfSequences, Dseq([Dser(arrayIndex, arraySize)], inf)));
	mask.poll(trig, 'trigger mask');
	trig * Select.ar(maskOn, [K2A.ar(1), mask]);
};

{
	var tFreq = \tFreq.kr(10);
	var trig = Impulse.ar(tFreq);

	trig = triggerMask.(trig, \maskOn.kr(1), \arrayOfBinaries.kr(Array.fill(16, Array.fill(16, 1))), \arrayIndex.kr(Array.fill(16, 1)), \arraySize.kr(4));

	[...]
};
)

is it also possible to implement an arraySize for arrayOfSequences? i think it would be nice to pass multidimensional arrays with different sizes, to have bars of different lengths.

in terms of the “size im using” is it possible to implement an arraySize for arrayOfSequences? i think it would be nice to have the ability to pass multidimensional arrays with different sizes.

I hasn’t responded because the answer is obviously yes, it’s possible.

When you create the arrayed control, it just gives you a conduit for x number of values. Any row/column format within that is 100% up to you. In terms of what’s possible with algorithms, if you can vary the size in one dimension then you can vary it in the other dimension too.

hjh

thanks, im just confused with:

\arrayOfBinaries.kr(Array.fill(16, Array.fill(16, 1)))

and implementing the same logic with:

Dseq([Dser(arrayIndex, arraySize)], inf))

To be more specific: You have a multidimensional data structure, which needs to be serialized (converted into a flat, serial format) for transmission to the server, and then de-serialized on the other side for use. (Serializing is a mature technology – when you save a Word document, it’s serialized for disk storage, for instance. There’s not an arbitrary limit on complexity here.)

The easiest way to serialize a 2D array is to choose a fixed size in both dimensions… but you need variable sizes.

If you want the number of rows to vary, then the row count can be passed as one extra parameter.

You could also send a “sizeOfRow” parameter – then you would have a flexible rectangular layout. Every row has the same size, but you can choose both sizes freely within the constraint m * n <= arrayedControlSize. If m = row size and n = number of rows,

  • Row 0 = 0 … m-1
  • Row 1 = m … 2*m - 1
  • etc

Or you could even send an array of row sizes, and integrate this to get the starting position of each row. Then you would have a variable number of rows with each row being variable size (up to the constraint that the total of all row sizes must be <= the arrayed control size).

hjh

okay, thanks a lot.
the beauty with the data im passing is that all the “subarrays” have the same size. so i think its enough to implement a rowSize parameter. i will try that.

i have flattened ~binaryDurations to one dimension to be used with an arrayedControl and then have thought alot about implementing:

and came up with this, which wont work in the SynthDef context i guess:

(
~binaryDurations = [
	[ 1, 0, 0, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0 ],
	[ 1, 0, 0, 1, 1, 0, 1, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1 ],
	[ 1, 0, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1 ]
];
)

(
~triggerMask = { |arrayOfBinaries, sizeOfRow|
	var arrayIndices = sizeOfRow.collect{ |i|
		(i * sizeOfRow..i + 1 * sizeOfRow - 1)
	};
	arrayIndices.collect { |index|
		var array = arrayOfBinaries[index];
		array;
		//Dseq(array, 1);
	};
};
~triggerMask.(~binaryDurations.flat, ~binaryDurations.size);
)

I also dont know how to sync the Impulses when updating ~binaryDurations via Synth.set. When updating is not in sync i think its not worth the effort to implement the possibility to pass ~binaryDurations of different sizes. I dont know if this is leading anywhere useful.

sorry for beeing slow on the uptake but if somebody knows how to pass ~binaryDurations of different sizes to the SynthDef i would be very happy:

(
var triggerMask = { |trig, maskOn, arrayOfBinaries, sizeOfRow, arrayOfIndices, numIndices|
	var arrayIndices = sizeOfRow.collect{ |i|
		(i * sizeOfRow..i + 1 * sizeOfRow - 1)
	};
	var arrayOfSequences = arrayIndices.collect { |index|
		var array = arrayOfBinaries[index];
		Dseq(array, 1);
	};
	var mask = Demand.ar(trig, 0, Dswitch(arrayOfSequences, Dseq([Dser(arrayOfIndices, numIndices)], inf)));
	mask.poll(trig, 'trigger mask');
	trig * Select.ar(maskOn, [K2A.ar(1), mask]);
};

SynthDef(\test, {

	var tFreq = \tFreq.kr(5);
	var trig = Impulse.ar(tFreq);

	trig = triggerMask.(
		trig,
		\maskOn.kr(1),

		\arrayOfBinaries.kr(Array.fill(64, 1)),
		8,//\sizeOfRow.kr(8),

		\arrayOfIndices.kr(Array.fill(8, 1)),
		\numIndices.kr(1)
	);

	trig = Pan2.ar(trig, \pan.kr(0));

	OffsetOut.ar(\out.kr(0), trig);
}).add;
)

(
~binaryDurations = [
	[ 1, 0, 0, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0 ],
	[ 1, 0, 0, 1, 1, 0, 1, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1 ],
	[ 1, 0, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1 ]
];

Synth(\test, [

	\tFreq, 8,

	\maskOn, 1,

	\arrayOfBinaries, ~binaryDurations.flat,
	\numArrays, 8,

	\arrayOfIndices, [2, 3, 2, 5, 4, 2, 3, 2],
	\numIndices, 8,

]);
)

You cannot pass an array of variable size. You could, however, set a max size and current size, similar to how something like DelayN works. You would need to pass an array of max size in as an argument so you’d need a function to wrap smaller sizes into the max.

There are two options:

  1. Define a synthdef with a function, where the name of the synthdef is (name+rows+columns).asSymbol. You can predefined a bunch of different sizes if you know you are likely to need some more than others - recording which sizes have already been defined so you don’t duplicate any work.

  2. The other option, which might not work here depending on how you are looping, is to use buffers. Put the values into a flat buffer, send the buffer number as an argument. Then use the demand rate ugens to read from the buffer. The issue is you can’t have loops nor arrays of varying size - but you can have different sizes buffers. Perhaps you could rethink the mask operation as multiplying two buffers? That way you can generate the buffers outside of the synthdef, and read at a specific index? Again, not too sure if this will work with your design, but as far as I can think right now, these are the only two options.

thank you very much. i know that the array size is fixed with SynthDef evaluation.

thats the thing im struggeling with.

I will think about this once more and also look at a buffer solution, which could when rethinking the masking strategy maybe reduce the size of the data im passing.

Maybe one of these methods is what you’re looking for?

~maxSize = 12;

~binaryDurations.wrapExtend(~maxSize);
~binaryDurations.foldExtend(~maxSize);
~binaryDurations.clipExtend(~maxSize);

okay, maybe using Dbufrd isnt a bad option, dont know yet how to go about the different sizes but i think like this you dont have to pass big array data.

(
~binaryDurations = [
	[ 1, 0, 0, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0 ],
	[ 1, 0, 0, 1, 1, 0, 1, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1 ],
	[ 1, 0, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1 ]
].flat;
)

b = Buffer.alloc(s, ~binaryDurations.size, 1);
b.setn(0, ~binaryDurations);
b.getn(0, b.numFrames, {|x| x.postln });

(
SynthDef(\test, { 
	
	var tFreq = \tFreq.kr(8);
	var trig = Impulse.ar(tFreq);
	
	var maxSize = 8;
	var seq = maxSize.collect{ |i|
		(i * maxSize..i + 1 * maxSize - 1)
	};
	
	var index = Dseq(seq[2], inf);
	
	var mask = Demand.ar(trig, 0, Dbufrd(b, index));
	
	mask.poll(trig, \mask);
	
	trig = trig * mask;
	
	trig = Pan2.ar(trig, \pan.kr(0));
	
	Out.ar(\out.kr(0), trig);
}).add;
)

Synth(\test);