Rotate array with Dswitch1 + Demand sequence of wrapping boundaries

hey, would someone know, how the following example can be rewritten without using % or wrap.
I would like to sequence different wrapping boundaries with another demand sequence per measure trigger. Using % or wrap together with a sequence of different wrapping boundaries seems not work.

(
{ 
	var trig = Impulse.kr(1);
	(0..7).collect{ |i|
		Demand.kr(trig, 0, Dswitch1((1..8), Dseries(0, 1) + i % 8)).poll(trig, \demand);
	};
}.play;
)

The context is this: I would like to translate this piece of code to Demand Ugens, to get the sum of a sequence of different rotated rows of the flattened 2d matrix per multichannel event trigger:

(
var numChannels = 5;
var array = [
	[ 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 ]
].flat;
var measureIndex = [5, 6];
var eventsPerMeasure = measureIndex + 1;
var stepOffset = measureIndex * eventsPerMeasure / 2;

numChannels.collect{ |chan|
	var count = eventsPerMeasure.collect{ |x| Array.series(x, 0, 1) };
	var index = count + chan % eventsPerMeasure + stepOffset;
	index.collect{ |x| array[x] };
}.sum;
)

I have a Demand example which does this already, but only for a fixed row and not for a sequence of different rows. Here are the main parts (i left out alot to isolate the problem), unfortunately the problem cant be isolated further. I hope the problem is the sequence of different mod or wrap values and someone would know how to rewrite the example from above, which would help me to fix it.

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

// the calculation of maxOverlap works if seqOfRows is a single value but not if it is a sequence
seqOfRows = [5, 6];

// create sequence of measure indices
measureIndex = Demand.ar(measureTrigger, reset, Dseq(seqOfRows, inf));
eventsPerMeasure = measureIndex + 1;

// calculate stepOffset of each event for flattened 2d matrix
stepOffset = measureIndex * eventsPerMeasure / 2;

// calculate stepIndex (each measure has measureIndex + 1 items)
stepIndex = Dseq([Dseries(0, 1, eventsPerMeasure)], inf);

[.....]

// create stepTrigger, eventTrigger, calculate legato values and distribute multichannel trigger round robin across the channels

[.....]

// calculate maximum overlap per multichannel trigger
maxOverlap = numChannels.collect{ |chan|
	//Demand.ar(triggers, reset, Dswitch1(arrayOfValues, chan + stepIndex + stepOffset));
	Demand.ar(triggers, reset, Dswitch1(arrayOfValues, Dseries(0, 1) + chan % eventsPerMeasure + stepOffset));
}.sum;

My implementation gives me the correct calculation for row 5:

[3, 1, 2, 2, 1, 3] -> row 5

index: [15, 16, 17, 18, 19] -> durations: [3, 1, 2, 2, 1] -> sum: 9
index: [16, 17, 18, 19, 20] -> durations: [1, 2, 2, 1, 3] -> sum: 9
index: [17, 18, 19, 20, 15] -> durations: [2, 2, 1, 3, 3] -> sum: 11
index: [18, 19, 20, 15, 16] -> durations: [2, 1, 3, 3, 1] -> sum: 10
index: [19, 20, 15, 16, 17] -> durations: [1, 3, 3, 1, 2] -> sum: 10
index: [20, 15, 16, 17, 18] -> durations: [3, 3, 1, 2, 2] -> sum: 11

and the correct calculation for row 6:

[3, 1, 2, 2, 1, 2, 1] -> row 6

index: [21, 22, 23, 24, 25] -> durations: [3, 1, 2, 2, 1] -> sum: 9
index: [22, 23, 24, 25, 26] -> durations: [1, 2, 2, 1, 2] -> sum: 8
index: [23, 24, 25, 26, 27] -> durations: [2, 2, 1, 2, 1] -> sum: 8
index: [24, 25, 26, 27, 21] -> durations: [2, 1, 2, 1, 3] -> sum: 9
index: [25, 26, 27, 21, 22] -> durations: [1, 2, 1, 3, 1] -> sum: 8
index: [26, 27, 21, 22, 23] -> durations: [2, 1, 3, 1, 2] -> sum: 9
index: [27, 21, 22, 23, 24] -> durations: [1, 3, 1, 2, 2] -> sum: 9

But not for the sequence of row 5 and 6:

// sequence of rows [5, 6] gives:

[15, 16, 17, 18, 19] -> [3, 1, 2, 2, 1] -> 9 -> OK!
[16, 17, 18, 19, 20] -> [1, 2, 2, 1, 3] -> 9 -> OK!
[17, 18, 19, 20, 15] -> [2, 2, 1, 3, 3] -> 11 -> OK!
[18, 19, 20, 15, 16] -> [2, 1, 3, 3, 1] -> 10 -> OK!
[19, 20, 15, 16, 17] -> [1, 3, 3, 1, 2] -> 10 -> OK!
[20, 15, 16, 17, 18] -> [3, 3, 1, 2, 2] -> 11 -> OK!

[27, 21, 22, 23, 24] -> FALSE!

[21, 22, 23, 24, 25] -> [3, 1, 2, 2, 1] -> 9 -> OK!
[22, 23, 24, 25, 26] -> [1, 2, 2, 1, 2] -> 8 -> OK!
[23, 24, 25, 26, 27] -> [2, 2, 1, 2, 1] -> 8 -> OK!
[24, 25, 26, 27, 21] -> [2, 1, 2, 1, 3] -> 9 -> OK!
[25, 26, 27, 21, 22] -> [1, 2, 1, 3, 1] -> 8 -> OK!
[26, 27, 21, 22, 23] -> [2, 1, 3, 1, 2] -> 9 -> OK!

[16, 17, 18, 19, 20] -> FALSE!

I still dont know if there is a problem with modulo / wrap and sequencing. But the fundamental problem with my approach seems to be something else.

I think this visualisation of sequencing row 5 and 6 one after the other shows where the problem is:

The events of each row are per measure. The first measure has all the events from row 5: [3, 1, 2, 2, 1, 3] and the second measure all the events from row 6: [3, 1, 2, 2, 1, 2, 1]. But maxOverlap has to be calculated per channel. So already for the third calculation of maxOverlap in the first measure (2 + 2 + 1 + 3 + 3 = 11) you have to take into account one event from the second measure. happy coding…

with all my latest posts on advanced server side sequencing i know there wont be any answers, so im using this more as a diary until i come up with the final solution and editing all my posts to make it more easy to follow the process if you are searching for something similar (about to start a blog haha). But if anybody has an idea to approach this issue feel free to share it with me :slight_smile:

I wonder if it would help here to add an offset input to Dseq (and Dser), to match Pseq.

Adding inputs needs to be done with care, for compatibility with non-sclang clients, but in this case it might make sense:

Pseq(list, repeats, offset)
Dseq(list, repeats)

… looks like just an oversight.

hjh

yeah, maybe :slight_smile: But Im currently not sure what part of my code it could replace then.

In theory, it might look like this:

maxOverlap = numChannels.collect { |chan|
	Demand.ar(triggers, reset,
		Dseq(arrayOfValues, inf, chan + stepOffset)
	)
};

But I’m not at all clear why the “false” rows are appearing, so this could be off base.

hjh

thanks :slight_smile: yeah, this would make it more easy to implement.

It seems that the stepOffset is working correctly but Dseries(0, 1) + chan % stepPerMeasure is causing the problem.

here polled with .dpoll(\index)

index: 0 block offset: 1 <---- stepsPerMeasure is 5 + 1
index: 1 block offset: 1
index: 2 block offset: 1
index: 3 block offset: 1
index: 4 block offset: 1
index: 1 block offset: 29 <---- stepsPerMeasure is 5 + 1
index: 2 block offset: 29
index: 3 block offset: 29
index: 4 block offset: 29
index: 5 block offset: 29
index: 2 block offset: 39 <---- stepsPerMeasure is 5 + 1
index: 3 block offset: 39
index: 4 block offset: 39
index: 5 block offset: 39
index: 0 block offset: 39
index: 3 block offset: 58 <---- stepsPerMeasure is 5 + 1
index: 4 block offset: 58
index: 5 block offset: 58
index: 0 block offset: 58
index: 1 block offset: 58
index: 4 block offset: 13 <---- stepsPerMeasure is 5 + 1
index: 5 block offset: 13
index: 0 block offset: 13
index: 1 block offset: 13
index: 2 block offset: 13
index: 5 block offset: 22 <---- stepsPerMeasure is 5 + 1
index: 0 block offset: 22
index: 1 block offset: 22
index: 2 block offset: 22
index: 3 block offset: 22
index: 6 block offset: 51 <---- stepsPerMeasure changes to 6 + 1
index: 0 block offset: 51
index: 1 block offset: 51
index: 2 block offset: 51
index: 3 block offset: 51
index: 0 block offset: 15 <--- stepsPerMeasure is 6 + 1
index: 1 block offset: 15
index: 2 block offset: 15
index: 3 block offset: 15
index: 4 block offset: 15

But I think the behaviour you could expect from this implementation is correct. When stepsPerMeasure has advanced its sequence from 5 to 6 causing stepsPerMeasure to be 6 + 1 the counting goes up to index 6 for the first item at offset 51 and wraps around to 0 at offset 15 But i guess you would additionally have to reset the counting when advancing the sequence, so it starts from 0 again right?

if I use the measureTrigger to reset Demand block 51 restarts properly with 0 but the two first blocks at every measure are repeated:

maxOverlap = numChannels.collect{ |chan|
	Demand.ar(triggers, measureTrigger, Dswitch1(arrayOfValues.flat, (Dseries(0, 1) + chan % eventsPerMeasure).dpoll(\index) + stepOffset));
}.sum;
index: 0 block offset: 1 <---- stepsPerMeasure is 5 + 1, starts at 0
index: 1 block offset: 1
index: 2 block offset: 1
index: 3 block offset: 1
index: 4 block offset: 1
index: 0 block offset: 29 <---- stepsPerMeasure is 5 + 1, also starts at 0
index: 1 block offset: 29
index: 2 block offset: 29
index: 3 block offset: 29
index: 4 block offset: 29
index: 1 block offset: 39
index: 2 block offset: 39
index: 3 block offset: 39
index: 4 block offset: 39
index: 5 block offset: 39
index: 2 block offset: 58
index: 3 block offset: 58
index: 4 block offset: 58
index: 5 block offset: 58
index: 0 block offset: 58
index: 3 block offset: 13
index: 4 block offset: 13
index: 5 block offset: 13
index: 0 block offset: 13
index: 1 block offset: 13
index: 4 block offset: 22
index: 5 block offset: 22
index: 0 block offset: 22
index: 1 block offset: 22
index: 2 block offset: 22
index: 0 block offset: 51 <---- stepsPerMeasure is 6 + 1, restarts correctly at 0
index: 1 block offset: 51
index: 2 block offset: 51
index: 3 block offset: 51
index: 4 block offset: 51
index: 0 block offset: 15 <---- stepsPerMeasure is 6 + 1, also starts at 0
index: 1 block offset: 15
index: 2 block offset: 15
index: 3 block offset: 15
index: 4 block offset: 15
index: 1 block offset: 25
index: 2 block offset: 25
index: 3 block offset: 25
index: 4 block offset: 25
index: 5 block offset: 25
index: 2 block offset: 44
index: 3 block offset: 44
index: 4 block offset: 44
index: 5 block offset: 44
index: 6 block offset: 44

Maybe this could be the case, because im using this rampToTrig function to get a trigger from the measure phasor, which is not creating an initial trigger. I guess this could explain the doubling of the first two blocks but not the doubling for the two blocks after the counter has been reset.

var rampToTrig = { |phase|
	var history = Delay1.ar(phase);
	var delta = (phase - history);
	var sum = (phase + history);
	var trig = (delta / sum).abs > 0.5;
	Trig1.ar(trig, SampleDur.ir);
};

EDIT: I think thats not the problem. All the triggers are synchronized carfeully, If the first trigger would start at 0 then the first block offset would be 0 instead of 1 or? I have tested it with HPZ1.ar(phase) < 0 + Impulse.ar(0) instead and its true then the first block starts at 0 but you still get the doubling of index 0 so the rampToTrig function is not the problem.

index: 0 block offset: 0
index: 1 block offset: 0
index: 2 block offset: 0
index: 3 block offset: 0
index: 4 block offset: 0
index: 0 block offset: 29
index: 1 block offset: 29
index: 2 block offset: 29
index: 3 block offset: 29
index: 4 block offset: 29

I think there is some weirdness going on with Pulsedivider which i use to distribute the event triggers round robin across the channels.

If I use the event trigger instead of the multichannel event triggers from PulseDivider and reset the counter with the measure trigger to calculate maxOverlap:

maxOverlap = numChannels.collect{ |chan|
	Demand.ar(eventTrigger, measureTrigger, Dswitch1(arrayOfValues.flat, Dseries(0, 1, inf) + chan % eventsPerMeasure + stepOffset));
}.sum;

you correctly get:

index: 0 block offset: 1 <---- stepsPerMeasure is 5 + 1, starts correctly at 0
index: 1 block offset: 1
index: 2 block offset: 1
index: 3 block offset: 1
index: 4 block offset: 1
index: 1 block offset: 29 <---- stepsPerMeasure is 5 + 1, next index is also correct
index: 2 block offset: 29
index: 3 block offset: 29
index: 4 block offset: 29
index: 5 block offset: 29
index: 2 block offset: 39 <---- stepsPerMeasure is 5 + 1
index: 3 block offset: 39
index: 4 block offset: 39
index: 5 block offset: 39
index: 0 block offset: 39
index: 3 block offset: 58 <---- stepsPerMeasure is 5 + 1
index: 4 block offset: 58
index: 5 block offset: 58
index: 0 block offset: 58
index: 1 block offset: 58
index: 4 block offset: 13 <---- stepsPerMeasure is 5 + 1
index: 5 block offset: 13
index: 0 block offset: 13
index: 1 block offset: 13
index: 2 block offset: 13
index: 5 block offset: 22 <---- stepsPerMeasure is 5 + 1
index: 0 block offset: 22
index: 1 block offset: 22
index: 2 block offset: 22
index: 3 block offset: 22
index: 0 block offset: 51 <---- stepsPerMeasure is 6 + 1, restarts correctly at 0
index: 1 block offset: 51
index: 2 block offset: 51
index: 3 block offset: 51
index: 4 block offset: 51
index: 1 block offset: 15 <---- stepsPerMeasure is 6 + 1, next index is also correct
index: 2 block offset: 15
index: 3 block offset: 15
index: 4 block offset: 15
index: 5 block offset: 15
index: 2 block offset: 25
index: 3 block offset: 25
index: 4 block offset: 25
index: 5 block offset: 25
index: 6 block offset: 25

But using the event trigger instead of the multichannel event triggers from PulseDivider to calculate maxOverlap leads to phase distortion:

grafik

Lets assume this issue could be fixed, then its not taking into account that maxOverlap has to be calculated per channel and not per measure. see the plot below, where its clear that you cant just rotate the sequence of events per measure to calculate maxOverlap per channel because events from the first measure already have to know about events from the seconds measure:

this is way beyond my math / programming knowledge. In the meantime you could use maxOverlap = currentEventDuration + (numChannels - 1) which ensures the phase wont get distorted with a bit of overlap capabilities but you cant use the full possible overlap available with numChannels if currentEventDuration > 1.

This shows the limitations of the round robin method. I guess the “overdubbing the future” approach from this thread could solve that. I will try to come up with a single sample writing and reading solution in gen~ where you could plugin an arbitrary signal for multichannel expansion and export that to SC. But this will take some time.