Inconsistent phasor output values when changing the rate

Ive been trying to read a certain set of samples in the middle of a buffer normally and reversed alternatively. A trigger reverses the reading direction.

(
{
	var rate, phase, trigger;
	
	trigger = Impulse.kr(1);
	rate = Demand.kr(trigger, 0, Dseq([1, -1], inf));
	phase = Phasor.ar(0, rate, 44100, 44100*3);
	
}.plot(10)
)

The plot looks normal, also polling phase shows normal results.


But then i increase the trigger rate to 3 hz, and these random spikes appear. Polling also show the spikes. For almost all rates higher than 1 it spikes.

But for certain impulse rates like 5, there are no spikes.

Why is this?

Edit: I understand now that if an impulse is a little bit late it can dip under 44100 and wrap to the end position. Im still curious however why certain impulse rates get late and others dont.
Im also still trying to find a solution.

I would suggest to use Impulse.ar and Demand.ar.

The default control block size is 64. Impulse.kr’s triggers are quantized to 64-sample blocks.

This is exact if you are running at 48 kHz: 48000 / 64 = 750.

It is not exact at 44.1 kHz: 44100 / 64 = 689.0625.

At 44.1 kHz, then, you could use Impulse.kr if it’s OK for the pulses to be “close enough.” If you need precision, then you really should use ar.

hjh

I still get the spikes when i use audiorate ugens. In the helpfiles about demand ugens, it says something about ‘demand rate’. Maybe it has something to do with that?

Edit: it has nothing to do with the demand ugen.

(
{
	var rate, phase;

	rate = LFPulse.ar(2).range(-1, 1);
	phase = Phasor.ar(0, rate, 44100, 44100*3);

}.plot(5)
)


It is rather frustrating that you have something simple in mind and you think yeah i can do that in supercollider. And then you have to spend a lot of time to find a solution because of little quirks like this.

For some reason, when it gets to the bottom, it is folding around to 44100*3 for 1 sample.

Maybe just do it a different way:


(

{

var rate, phase, trigger;

phase = Phasor.ar(0, 1, 44100, 44100*3);

phase.fold(0,44100*2)

}.plot(10)

)

Sam

This is really annoying, but you have to do Phasor.ar(DC.ar(0), ...). I always forget that

Wow cool it works! Thank you very much. But if i may ask, do you know why this works?

We’ve all had to do that. And it isn’t any better in any other programming environment. (Some may have fewer inconsistencies than SC, but none is fully free of “gotchas.”) You could, for instance, try Max/MSP, where you’d find that 0.7 * 1 = 0 while 0.7 * 1. = 0.7 because Max is the only dadgummed programming environment I know that implicitly downcasts floats to ints in math operations unless both arguments are explicitly float :confounded:

It’s frustrating but it’s also a fact of life in the programming world (everywhere in the programming world).

Many UGens internally have different calculation functions for different input rates. A filter might have one function for audio-rate signal and audio-rate parameters, and a different one for audio-rate signal, control-rate parameters (where the latter would be more CPU efficient). Sometimes there are too many combinations, so only some of the possible functions are implemented.

I hadn’t checked the source code and I hoped that the audio rate “rate” would be recognized automatically – but it seems that Phasor chooses the audio-rate parameter version based on the rate of the first input. This makes it necessary to force ar for the 0 trigger … (which is not intuitive at all).

hjh

I forgot about this - but this is a straight-up bug. Comparable to the Max/MSP case, there’s no reasonable interpretation where you want ar arguments to be downgraded to kr for this kind of case.

Unfortunately this isn’t the only one – offhand I can also think of FOS, which requires all 3 coefficients to be audio rate, and if not, they’re all treated as kr. The code review for that would be a big job.

Then I suspect it would take some use of templates to fix it. To my knowledge, there’s not a great way to promote kr to ar within a function expecting an ar input – the block-size vector doesn’t exist. So you’d need separate functions for every possible combination. Let’s assume the same rate logic appears in SOS, with an input signal and 5 coefficients. That’s 2^5 = 32 functions, maybe plus one more for a kr signal input. Nobody is going to do that by hand.

Agreed that in principle it would be good to promote inputs rather than demote them, but absent a really cute c++ trick, it looks to me like a big job.

hjh

Yeah the trick to this is to use something like GitHub - jacquelinekay/petra: runtime to compile-time mappings to generate compile time versions of each variation and then switch between the process functions at runtime. I have some prototype examples of this, it honestly pretty straightforward and if anything you reduce the amount of code from multiple implementations into a single function - but its still tricky and of course takes time to implement :slight_smile:

Looking a bit at this problem, the much bigger effort is NOT rewriting existing ugen code but actually writing tests to verify you’ve gotten it right. :confused: