Creating rhythmic meso / macro structure with Demand trigger mask

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);

It might be helpful to visualize the way that indices map from a matrix onto a serial collection.

Let’s say you’re making sure that your matrix is a true rectangle (every row has the same number of columns). Then you can serialize this matrix easily by .flat.

And let’s start with three sequences, each with five notes. Then the indices within the flat collection go like this.

  • Row 0: Indices 0, 1, 2, 3, 4
  • Row 1: Indices 5, 6, 7, 8, 9
  • Row 2: Indices 10, 11, 12, 13, 14

OK, now let’s try three sequences, each with four notes.

  • Row 0: Indices 0, 1, 2, 3
  • Row 1: Indices 4, 5, 6, 7
  • Row 2: Indices 8, 9, 10, 11

Then look for patterns. (This is an important part of the process. The code is just the end result. The ideas behind the code are more important than the code.)

One pattern (which should be somewhat evident?) is that the first index in each row = the row index * the number of elements in each row.

Another pattern is that each row just counts up by 1.

So if you have an index for the row that you want to play right now, and index for the position within the row, then… what is the index within the flattened collection?

(rowIndex * notesPerRow) + columnIndex

For this to work, you have to guarantee 1/ that rowIndex is a whole number (.floor) – this is very important! a fractional rowIndex would start in the middle of a row and spill over to the next – and 2/ 0 <= columnIndex < notesPerRow. Neither of these is difficult to satisfy.

Now, there is nothing in any of this that requires hardcoded sizes. You need hardcoded maximum sizes, but both notesPerRow and numRows can be synth arguments = variable usage sizes at runtime.

This also applies equally well to an arrayed control or a buffer. For both, you need an index into the collection, and this behaves the same either way. The only difference is Dswitch1 (array) vs Dbufrd (edit: or Index).

hjh

1 Like

thanks, i have serialized the matrix to a flat array and instead of de-serialising it and indexing into an array of arrays im indexing into a flat array. i have set the maximum size of arrayOfBinaries to 16x16 = 256.

updating ~binaryDurations of a different size is working nicely, but right now its not possible to pass an array of rowIndices.

(
var triggerMask = { |trig, maskOn, arrayOfBinaries, rowIndex, notesPerRow|

	var columnIndex = Dseries(0, 1, notesPerRow);
	var arrayIndex = (rowIndex.floor * notesPerRow) + columnIndex;

	var mask = Demand.ar(trig, 0, Dswitch1(arrayOfBinaries, Dseq(arrayIndex, 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(256, 1)),
		\rowIndex.kr(0),
		\notesPerRow.kr(8)
	);

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

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

(
~binaryDurations_8x8 = [
	[ 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;

~binaryDurations_12x12 = [
	[ 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, 1, 0, 0, 1, 0, 0, 0, 0, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 ],
	[ 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0 ],
	[ 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0 ],
	[ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0 ],
	[ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1 ],
	[ 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
].flat;
)

(
r = Routine{

	s.makeBundle(s.latency, {

		x = Synth(\test, [

			\tFreq, 8,

			\rowIndex, 2,
			\notesPerRow, 8,

			\arrayOfBinaries, ~binaryDurations_8x8,

		]);
	});

	1.wait;
	
	\update_rowIndex.postln;

	x.set(\rowIndex, 3);

	1.wait;
	
	\update_arrayOfBinaries.postln;

	x.set(\arrayOfBinaries, ~binaryDurations_12x12, \notesPerRow, 12);

	2.5.wait;

	x.free;

}.play;
)

What’s the purpose of an array of row indices? (And, what have you tried?)

hjh

In my first implementation i have serialised ~binaryDurations using .flat and then de-serialised it to a matrix again to be able to pass an array of indices to index into the array of sequences. with this implementation ~binaryDurations has a fixed size of 8x8 = 64 but you can pass an array of indices for a meso / macro sequence of different fractal Rhythms.

(
var triggerMask = { |trig, maskOn, arrayOfBinaries, arrayOfIndices, numIndices|
	
	var maxSize = 8;
	var arrayIndices = maxSize.collect{ |index|
		(index * maxSize..index + 1 * maxSize - 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(8);
	var trig = Impulse.ar(tFreq);

	trig = triggerMask.(
		trig,
		\maskOn.kr(1),
		\arrayOfBinaries.kr(Array.fill(64, 1)),
		\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 ]
].flat;

Synth(\test, [

	\arrayOfBinaries, ~binaryDurations,
	\numArrays, 8,

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

]);
)

To be able to have variable sizes for ~binaryDurations i have been following your latest explanations (thank you very much) and have passed the serialised version of ~binaryDurations and havent de-serialised it to a matrix again, but instead have passed rowIndex to sequence different columns of just one row.

(
var triggerMask = { |trig, maskOn, arrayOfBinaries, rowIndex, notesPerRow|

	var columnIndex = Dseries(0, 1, notesPerRow);
	var arrayIndex = (rowIndex.floor * notesPerRow) + columnIndex;

	var mask = Demand.ar(trig, 0, Dswitch1(arrayOfBinaries, Dseq(arrayIndex, inf)));

	mask.poll(trig, 'trigger mask');
	trig * Select.ar(maskOn, [K2A.ar(1), mask]);
};

SynthDef(\test, {

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

	trig = triggerMask.(
		trig,
		\maskOn.kr(1),
		\arrayOfBinaries.kr(Array.fill(256, 1)),
		\rowIndex.kr(0),
		\notesPerRow.kr(8)
	);

	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 ]
].flat;

Synth(\test, [
	
	\rowIndex, 2,
	\notesPerRow, 8,
	
	\arrayOfBinaries, ~binaryDurations,
	
]);
)

I think that syncing of clocks between language and server is a problem here, so i thought when passing a sequence of indices like in the first exampke i could minimize the times i have to use .set to update the index to pick an array of the collection.

1.) But i have no clue how to combine the two examples to have flexible sizes and also the ability to pass a sequence of indices.
2.) and if syncing between language and server is really a problem here or how to integrate this in to a Synths or Pattern infrastructure.

sidenote:

Beside ~convertToSequentialBinary i have implemented ~convertToSegmentedBinary where im not so sure if this makes so much sense in terms of low and high rhythmic density for the collection of durations but here it is:

(
~durations = [
	[ 8 ], 
	[ 3, 5 ], 
	[ 3, 3, 2 ], 
	[ 3, 1, 2, 2 ], 
	[ 2, 1, 1, 2, 2 ], 
	[ 2, 1, 1, 2, 1, 1 ], 
	[ 2, 1, 1, 1, 1, 1, 1 ], 
	[ 1, 1, 1, 1, 1, 1, 1, 1 ] 
];
)

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

~convertToSequentialBinary.(~durations);
)

(
~convertToSegmentedBinary = { |arrays|
	arrays.collect{ |array|
		array.collect{ |item, index|
			Array.fill(item, index.even.asInteger);
		}.flat;
	};
};

~convertToSegmentedBinary.(~durations);
)

Ah ok, a sequence. When I read your message yesterday, I thought you meant polyphony within the SynthDef.

The basic model of variable “size” array synth args is that you declare the arg with a fixed maximum size, and then use only some of the elements. This principle doesn’t change. What does change is the way that it’s done in different contexts.

If this is going to be an important use case for you, then you might take notes somewhere about what works for different situations. The Dswitch1 approach above is one of those.

For the row index, you could do it the same way actually, just leave off the * part: Dseries and Dswitch1 (extend a technique that you already know).

Alternately: Dswitch1 + the index calculation was necessary mainly because switching rows would mean non-sequential access. In a 1D array with sequential access only, you could use Dser to control the number of elements used.

But you want the row index to advance only after finishing a row. If you just put a rowIndex pattern in, then it will switch rows on every value, which is nonsense. See Ddup for a solution to that.

hjh

Or use the column index as a trigger.

(
var maxSeq = 10, maxNotes = 20;

SynthDef(\rowseq, { |out, gate = 1,
	numSequences = 1, notesPerSeq = 3,
	trigFreq = 8, decay = 0.1, amp = 0.1, t_reset|

	var serialized = NamedControl.kr(\seq, 0 ! (maxSeq * maxNotes));

	var trig = Impulse.ar(trigFreq);
	var noteInSeq = Demand.ar(trig, t_reset, Dseq([Dseries(0, 1, notesPerSeq)], inf));

	// why? because, when jumping to the next row,
	// noteInSeq goes from a higher to a lower value
	var rowTrig = HPZ1.ar(noteInSeq) < 0;
	
	// inputs
	var rowSeq = NamedControl.kr(\rowSeq, Array.fill(10, 0));
	var rowSeqSize = NamedControl.kr(\rowSeqSize, 3);
	
	// one gotcha: you always need one trigger at the start
	var row = Demand.ar(rowTrig + Impulse.ar(0), t_reset,
		Dseq([Dser(rowSeq, rowSeqSize)], inf)
	);
	var seqOffset = row.floor * notesPerSeq;
	var note = Demand.ar(trig, t_reset,
		Dswitch1(serialized, seqOffset + noteInSeq)
	);

	var sig = LFTri.ar(note.midicps);
	var eg = EnvGen.ar(Env.perc(0.01, decay), trig);
	var mainEg = EnvGen.kr(Env.asr(releaseTime: 0.01), gate, doneAction: 2);

	Out.ar(out, (sig * eg * amp).dup);
}).add;

~sendMatrix = { |synth, matrix, reset = 1|
	synth.setn(
		\t_reset, reset,
		\numSequences, matrix.size,  // number of rows
		\notesPerSeq, matrix[0].size,  // columns (assuming all are the same)
		\seq, matrix.flat  // this is a basic "serialize" operation
	);
};
)

(
a = Synth(\rowseq, [
	numSequences: 3,
	notesPerSeq: 10,
	amp: 0.2,
	rowSeq: [0, 1],
	rowSeqSize: 2,
	seq: Array.fill(3, {  // 3 rows
		Array.fill(10, {  // 10 columns per row
			rrand(48, 72)
		})
	}).flat  // serialize
]);
)

(
~sendMatrix.(a, [
	[60, 64, 67, 62],
	[64, 65, 67, 72],
	[52, 57, 55, 62],
	[43, 52, 53, 60]
], 1);
)

a.set(\rowSeq, [3, 1, 2, 0], \rowSeqSize, 4);

a.release;

hjh

thank you very much for all the effort you have put into this.

is HPZ1.ar(noteInSeq) < 0 equal to PulseCount.ar(noteInSeq) > 0 in this case?

hey, in this thread Masking triggers and adjust overlap legato style i have been asking how to adjust the overlap in a “legato style” when masking triggers by binary values. i have made this sketch which is working already (see the plot). But i would like to calculate the legato values from the serialized binary matrix which im passing, inside of triggerMask and have two events mask and legato as outputs. I think it would be more intuitive to use the initial ~matrixOfDurations for the legato values instead of the calculated ~legato matrix. How can i do that?

(
~seqOfRows = [2, 0];

~matrixOfDurations = [
	[ 12 ],
	[ 6, 6 ],
	[ 6, 3, 3 ],
	[ 3, 3, 3, 3 ],
	[ 3, 3, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 2, 1, 1, 1, 1 ],
	[ 2, 1, 1, 2, 1, 1, 1, 1, 1, 1 ],
	[ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];

// matrixOfDurations converted to binary values
~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, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 ],
	[ 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];

// manually calculated legato values for these specific binaryDurations
~legato = [
	[ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 ],
	[ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 ],
	[ 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3 ],
	[ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 ],
	[ 3, 3, 3, 3, 3, 3, 2, 2, 1, 3, 3, 3 ],
	[ 3, 3, 3, 1, 2, 2, 2, 2, 1, 3, 3, 3 ],
	[ 3, 3, 3, 1, 2, 2, 2, 2, 1, 2, 2, 1 ],
	[ 2, 2, 1, 1, 2, 2, 2, 2, 1, 2, 2, 1 ],
	[ 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1 ],
	[ 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1 ],
	[ 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];
)

(
var timingInformation = { |numChannels, trig, grainRate|
	var rate = if(trig.rate == \audio, \ar, \kr);
	var arrayOfTrigsAndPhases = numChannels.collect{ |i|
		var localTrig = PulseDivider.perform(rate, trig, numChannels, i);
		var hasTriggered = PulseCount.perform(rate, localTrig) > 0;
		var localPhase = Sweep.perform(rate, localTrig, grainRate * hasTriggered);
		[localTrig, localPhase];
	};
	var trigsAndPhasesArray = arrayOfTrigsAndPhases.flop;
	(\trigger : trigsAndPhasesArray[0], \phase: trigsAndPhasesArray[1]);
};

var triggerMask = { |trig, reset, arrayOfValues, arrayOfLegatos, valuesPerRow, seqOfRows, rowSeqSize|
	var noteInSeq = Demand.ar(trig, reset, Dseq([Dseries(0, 1, valuesPerRow)], inf));
	var rowTrig = HPZ1.ar(noteInSeq) < 0;
	var row = Demand.ar(rowTrig + Impulse.ar(0), reset,
		Dseq([Dser(seqOfRows, rowSeqSize)], inf)
	);
	var seqOffset = row.floor * valuesPerRow;
	var mask = Demand.ar(trig, reset, Dswitch1(arrayOfValues, seqOffset + noteInSeq));
	var legato = Demand.ar(trig, reset, Dswitch1(arrayOfLegatos, seqOffset + noteInSeq));
	(mask: mask, legato: legato);
};

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

{
	var numChannels = 4;

	var sig, tFreq, trig, grainRate, grainWindow, phase, maskedTrig, legato, timings;

	tFreq = \tFreq.kr(400);
	trig = Impulse.ar(tFreq);

	maskedTrig = triggerMask.(
		trig: trig,
		reset: \reset.tr(0),
		arrayOfValues: ~binaryDurations.flat,
		arrayOfLegatos: ~legato.flat,
		valuesPerRow: ~binaryDurations.size,
		seqOfRows: ~seqOfRows,
		rowSeqSize: ~seqOfRows.size,
	);

	grainRate = (tFreq / (\overlap.kr(1) * maskedTrig.legato));

	timings = timingInformation.(numChannels, trig * maskedTrig.mask, grainRate);

	grainWindow = hanningWindow.(timings.phase);

	sig = SinOsc.ar(440);

	sig = sig * grainWindow;

}.plot(0.2);
)

ive figured out how i can pass an array of durations and calculate the binary values in the Synthdef, so that i can use the binary sequence to mask the triggers and the legato values to scale the envelope without having two pass parallel collections of data:

(
var seqOfDurations = [8, 3, 5];
var seqSize = 3;

{
	var tFreq = 2;
	var trig = Impulse.ar(tFreq);

	// poll values per duration for binary and legato sequence
	var valuesPerDuration = Ddup(2, Dseq([Dser(seqOfDurations, seqSize)], inf));

	var countValuesPerDuration = Demand.ar(trig, 0, Dseq([Dseries(0, 1, valuesPerDuration)], inf));
	var binarySequence = HPZ1.ar(countValuesPerDuration) < 0 + Impulse.ar(0);

	var legatoSequence = Demand.ar(binarySequence, 0, valuesPerDuration);

	binarySequence.poll(trig);
	legatoSequence.poll(binarySequence);

	Silent.ar;
}.play;
)

Im now trying to combine that with the matrix approach. The goal would be to be able to pass a flattened 2d array with durations of this format and sequence an array of indices. The difficulty is the different number of items per row (one observation: every higher index has one more item).

~matrixOfDurations = [
	[ 12 ],
	[ 6, 6 ],
	[ 6, 3, 3 ],
	[ 3, 3, 3, 3 ],
	[ 3, 3, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 2, 1, 1, 1, 1 ],
	[ 2, 1, 1, 2, 1, 1, 1, 1, 1, 1 ],
	[ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];

EDIT:

i think the seqOffset could be calculated like this:

(
var index = 3;
var seqOffSet = (index * (index + 1)) / 2;
seqOffSet;
)

Im currently stuck with calculating the number of notes in the sequence.
When passing data in this format, every row has the same number of items:

~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, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1 ],
	[ 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 ],
	[ 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];

(
var valuesPerRow = ~binaryDurations[0].size;
var seqOfRows = [0, 1, 2];
var rowSeqSize = 3;

{
	var trig = Impulse.ar(2);
	var noteInSeq = Demand.ar(trig, 0, Dseq([Dseries(0, 1, valuesPerRow)], inf));
	var rowTrig = HPZ1.ar(noteInSeq) < 0  + Impulse.ar(0);
	var row = Demand.ar(rowTrig, 0,
		Dseq([Dser(seqOfRows, rowSeqSize)], inf)
	);
	row.poll(rowTrig);
	Silent.ar;
}.play;
)

so one could use: var noteInSeq = Demand.ar(trig, reset, Dseq([Dseries(0, 1, valuesPerRow)], inf)); and then trigger every new row with HPZ1.ar.

when passing data in this format:

~matrixOfDurations = [
	[ 12 ],
	[ 6, 6 ],
	[ 6, 3, 3 ],
	[ 3, 3, 3, 3 ],
	[ 3, 3, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 3 ],
	[ 3, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 2, 1, 2, 1 ],
	[ 2, 1, 1, 2, 2, 1, 1, 1, 1 ],
	[ 2, 1, 1, 2, 1, 1, 1, 1, 1, 1 ],
	[ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
	[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
];

each row has a different number of items (index of the row + 1)

Does somebody know how i can calculate the number of items for noteInSeq dependent on the current index of the sequence? thanks