I think this is due to single sample and rounding issues concerning the sample rate, because if we take whole integers of the sample rate (respectively block size), the effect is not observable.
(
{ // audio rate
var foo = Impulse.ar(SampleRate.ir/4);
var bar = Impulse.ar(SampleRate.ir/6);
[
foo,
bar,
foo * bar,
Impulse.ar(SampleRate.ir/12),
]
}.plot(bounds: Rect(200, 50, 1300, 800))
)
and
(
{ // control rate
var foo = Impulse.kr(s.options.blockSize/4);
var bar = Impulse.kr(s.options.blockSize/6);
[
foo,
bar,
foo * bar,
Impulse.kr(s.options.blockSize/12),
]
}.plot(1.0, bounds: Rect(200, 50, 1300, 800))
)
If we turn the trigger into a gate which is open for two samples, the code work as expected as well
(
{ // audio rate
var foo = Impulse.ar(1200);
var bar = Impulse.ar(800);
[
foo,
bar,
Trig.ar(foo, (SampleRate.ir/2).reciprocal) * Trig.ar(bar, (SampleRate.ir/2).reciprocal),
Impulse.ar(400),
]
}.plot(bounds: Rect(200, 50, 1300, 800))
)
However, 48000 is a multiple of 1200 and 800, so I am not quite sure where this problem comes from - maybe it is due to numerical instability of floats and some DSP code decides whether the scheduled sample is due by checking if the diff is >=0.0. If we get <0.0 due to numerical instability, a sample may be triggered too early or too late. Since Impulse relies on a phasor internally, this could be the cause of the problem here as summation of floats is prone to inaccuracy.
Hope this helps a bit, I am also not expert in these rounding issues
edit: if you look closely you can see that the spikes of Impulse.ar(1200)
and Impulse.ar(800)
are also not alinging in the plot.