Resetting start count with PulseDivider?

Dear List,

is there a way to “reset” the start count of PulseDivider?
If i understand it right this parameter is non-modulatable, as it relates to the start of the trigger count.
Would there be a way to alter the count with it running? I Imagine something like the reset parameter of Demand Ugens…

Thanks for any advice!
Jan

I thought I had a solution with Demand, but I think I just found a bug instead: edit: that’s probably an issue with some local changes in Poll, maybe not reproducible in the main branches.

(
a = {
	var trig = Impulse.kr(0);
	var demand = Demand.kr(trig, 0, Dseq([100, 101], inf));
	demand.poll(trig);
	Silent.ar(1);
}.play;
)

UGen(OutputProxy): 0  // not 100!!??!?!?!?

a.release;

Oh, but I just thought of a workaround:

(
a = { |division = 4, start = 0, t_reset = 0|
	var trig = Impulse.kr(2);
	var count = Demand.kr(trig, t_reset, Dseries(0, 1, inf));
	// 1. "lazy equality" (Object helpfile)
	// 2. Trig1 is just to force a single (kr) sample
	// instead of a gate until the next trigger
	var div = Trig1.kr(((count + start + 1) % division) |==| 0, ControlDur.ir);
	[count, div].poll(trig);
	Silent.ar(1)
}.play;
)

a.set(\t_reset, 1);

a.release;

That’s based on analyzing PulseDivider’s behavior: with div == 4, it produces a trigger on the 4th, 8th etc. input triggers. (This is different, for instance, from modular clock dividers, which would output a trigger on the 1st, 5th, 9th etc.)

hjh

Thank you James for the workaround!

I was wanting to provide an array for the start parameter, but it seems that the lazy equality method doesnt let it expand?
(switched to .ar in the example)

(

a = { | t_reset = 0|

	var trig = Impulse.ar(20);
	
	var start = (1..8);
	
	var division = 8;

	var count = Demand.ar(trig, t_reset, Dseries(0, 1, inf));

	// 1. "lazy equality" (Object helpfile)

	// 2. Trig1 is just to force a single (kr) sample

	// instead of a gate until the next trigger

	var div = Trig1.ar(((count + start + 1) % division) |==| 0, SampleDur.ir).poll;

	div.poll(trig);
	
	Silent.ar(1)

}.play

)

the goal is to be able to switch between trigger streams with different starting arrays on each reset, which PulseDivider didn’t seem to allow!

You’re right… That’s a case that I missed before.

It’s a very tricky problem actually… if we assume that arrays should “compose” |==| into an array of results, then if(arrayA |==| arrayB) will fail. But as you found, if we treat it as a regular object and produce a single result, then your case doesn’t work. There might not be a good way to solve it.

In the meantime, you can write the full form BinaryOpUGen('==', yourArrayOfUGens, 0) (edit: corrected typos in the code expression) and this will multichannel-expand.

hjh

Hmm, ok, then it’s not as straightforward as i initially expected!

I do still try to figure if there’s a possible strategy using PulseCount as that does have a reset input…

With BinaryOpUgen.kr you mean making a Pseudo Ugen with the lazy equality?

All the information you need for that is already in this thread.

hjh

Thank you for your suggestion with BinaryOpUgen, it seems to be working well, aside from one oddity: if the division is set to 1 there won’t be any trigger outputted. Shouldn’t it be expected to return the incoming rate?

(

a = { | t_reset = 0|

	var trig = Impulse.ar(20);
	
	var start = (1..8);
	
	var division = MouseX.kr(1,8).round(1).poll;

	var count = Demand.ar(trig, t_reset, Dseries(0, 1, inf));

	var div = Trig1.ar(BinaryOpUGen('==', (count+start+1)%division, 0), SampleDur.ir);

	div.sum*0.05;

}.play
1 Like

If division == 1, and count and start are always integers, then this value is continuously 0. Then the == operation is continuously 1. A trigger is defined as a transition from non-positive to positive. “Continuously 1” is continuously positive, thus no transitions, thus only one initial trigger.

hjh

i see, thank you james!

But on second thought, trig * BinaryOpUGen('==', (count+start+1)%division, 0) would not be continuously 1, so you should get separate triggers.

hjh

1 Like

following up on this example i’m trying to dynamically reshuffle the start positions of triggers (for an envelope), which seems to cause discontinuities because of too fast retriggering. (with a linear and steady start position for the triggers there’s no problem with clicks)
would it be possible to keep envelopes intact and and still dynamically reshuffle the start positions of triggers?

({
	var n=8;
	
	var freq=80;
	
	var btrig=Impulse.ar(freq);
	
	var division = n;
	
	var start = TIRand.ar(0,n-1,PulseDivider.ar(btrig,n)!8);   //dynamically reshuffling start position of triggers causes clicks in envelope
	
//	var start = (1..n);                                       //linear sequence triger offset
	
	var count = Demand.ar(btrig,0, Dseries(0, 1, inf));
	
	var trig=Trig1.ar(BinaryOpUGen('==', ((count+start+1)%(division)), 0), SampleDur.ir);
	
	var init= PulseCount.ar(trig)>0;
	
	init*SinOsc.ar(0,Sweep.ar(trig,freq/n).clip(0,1)*1pi);
	
}.plot(1))

i think you need a continuous phase.
Ive once tried to convert this into a multi channel random trigger / phase with no luck. maybe we can work on this together.

(
var randomTrigPhase = { |triggerRate = 10, randomness = 1|
	var minDuration = (2 ** randomness) / triggerRate;
	var maxDuration = (2 ** (-1 * randomness)) / triggerRate;
	var demand = Dunique(minDuration * ((maxDuration / minDuration) ** Dwhite(0, 1)));
	var duration = Duty.ar(demand, DC.ar(0), demand);
	var phase = Phasor.ar(DC.ar(0), (1 / duration) / SampleRate.ir, 0, 1);
	(phase: phase, trigger: HPZ1.ar(phase) < 0);
};

{
	var rate = 100;
	randomTrigPhase.(rate, 1).phase;
}.plot(0.1);
)

hi @dietcv,
thanks for chiming in! a continuous phase in what sense you mean?

i think in your example Sweep is receiving a trigger while going from 0 to 1 causing the discontinuity in the phase because the duration between one trigger and the next is unknown.

with this example you get at least a continiuous phase with random triggers to drive the hanning window (evaluate it several times). but its not multichannel with distributed phases / windows accross channels:

(
var randomTrigPhase = { |triggerRate = 10, randomness = 1|
	var minDuration = (2 ** randomness) / triggerRate;
	var maxDuration = (2 ** (-1 * randomness)) / triggerRate;
	var demand = Dunique(minDuration * ((maxDuration / minDuration) ** Dwhite(0, 1)));
	var duration = Duty.ar(demand, DC.ar(0), demand);
	var phase = Phasor.ar(DC.ar(0), (1 / duration) / SampleRate.ir, 0, 1);
	(phase: phase, trigger: HPZ1.ar(phase) < 0);
};

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

{
	var rate = 100;
	var phase = randomTrigPhase.(rate, 1).phase;
	hanningWindow.(phase);
}.plot(0.1);
)

im not sure if this is helping it just reminded me of some issue with multichannel random triggers and continiuous phases i have been trying to solve.

im not yet sure if i’m able to transfer this to my usage case, but what is avoiding a multichannel expansion?

with my example the phase is calculated first and then you can get the triggers by HPZ1 which solves the unknown duration issue.
When using Pulsedivider and Sweep to distribute the triggers / phases round robin across the channels how i would like to (to disable the randomness and mask the triggers by demand sequences of 0s and 1s), its the other way around first the triggers are calculated and then plugged into Sweep to get the phases. I just have no idea how to go about that. but when solved this could be used for your use case with Pulsedivider as well i guess to get random multichannel triggers and continiuous phases.

ok, i see. i unfortunately don’t have an immediate suggestion for this also, but it sounds like it could pave a way for many usages surely!

alternatively for my example im pondering a compromise (too avoid too many calculations of phase e.g.) with a gate that blocks sweep from receiving new triggers until the envelope is closed, but this is still an unimplemented, abstract thought.
ideally of course, a smartly set up sweep just neatly triggers and closes envelopes, no matter what random sequence is fed;)

yes i think all the custom made granular stuff with better abilities for modulation per grain. especially FM/PM or AM per grain. i have been either using FM/PM with a stateless index window per grain or a frequency window per grain.
right now:
by using synchronous triggers by Impulse / Pulsedivider / Sweep you get continiuous grains.
by using synchronous triggers by Impulse / Pulsedivider / Sweep masked by demand sequences with 0s and 1s you get semi synchronous / asynchronous grains
by using asynchronous triggers and Pulsedivider you get a discontinuity in the phase.

i think Sweep just starts right away not caring of the first trigger so you have to make sure the first trigger has arrived with PulseCount to start Sweep. Additionally every Sweep gets interrupted by a new trigger not caring if it has reached its end destination or not which is causing the discontinuity.

Yes, i was wondering though if possibly with Gate.ar one could engineer a way in which retriggers only occur under the condition of Sweeps phase being 0, by actively ignoring retriggers while an envelope is open/ non-zero.

This was what i was envisioning “gating” the sweep input. While it avoids retriggering in the middle of an envelope, it effectively skips triggers and so basically creates a much more discontinuous trigger stream. The randomization does work, but overall not a satisfying solution to the problem unfortunately!

({
	var n=8;
	
	var freq=80;
	
	var btrig=Impulse.ar(freq);
	
	var division = n;
	
	var start = TIRand.ar(0,n-1,PulseDivider.ar(btrig,n)!8);   //dynamically reshuffling start position of triggers causes clicks in envelope
	
	var linstart = (1..n);                                       //linear sequence triger offset
	
	var count = Demand.ar(btrig,0, Dseries(0, 1, inf));
	
	var trig=Trig1.ar(BinaryOpUGen('==', ((count+start+1)%(division)), 0), SampleDur.ir);
	var lintrig=Trig1.ar(BinaryOpUGen('==', ((count+linstart+1)%(division)), 0), SampleDur.ir);
	
	var init= PulseCount.ar(trig)>0;
	
	init*SinOsc.ar(0,Sweep.ar(trig*lintrig,freq/n).clip(0,1)*1pi);
	
}.plot(1))