Ddup values not in phase?

Noticed some oddities with Ddup, not sure too what extent this qualifies as a bug or not.
Thought I’d post it here before I start obsessively poking around in the source.

Essentially I’m trying to write a demand-rate .fold; figured this should be easy enough, since basically (as per Wavefolder):

folded_output = wave(input)

give or take some gain and period adjustment.

This works fine when wave = sin; the output of feeding it a Dseries is a quantized sine wave,
as was to be expected:

{Demand.ar(Impulse.ar(2000), 0, sin(Dseries(0, 4pi/200, 200)))}.plot(0.1)

not so much when I try it with a triangle wave, which is what the .ar fold presumably does:

(
var tri = {|x| 2 * abs(x - floor(x + 0.5))}; // should fold into [0, 1]

{Demand.ar(Impulse.ar(2000), 0, tri.(Dseries(0, 1/200, 200)))}.plot(0.1)
)

Here the tri function eats through the demand at twice the rate, because the x variable is called twice in the tri function.
Ok fine: I’ll put a Ddup around the input and that should do it:

(
var tridup = {|d| var x = Ddup(2, d); 2 * abs(x - floor(x + 0.5))}; // should fold into [0, 1]

{Demand.ar(Impulse.ar(2000), 0, tridup.(Dseries(0, 1/200, 200)))}.plot(0.1)
)

Well almost. There’s this little weirdness around the peak that results from the two x’s not actually being in phase.

(
var tripoll = {|d| var x = Ddup(2, d); (2 * abs(x.dpoll("absx") - floor(x.dpoll("floorx") + 0.5))).dpoll("outx")}; // should fold into [0, 1]

{Demand.ar(Impulse.ar(2000), 0, tripoll.(Dseries(0, 1/200, 200)))}.plot(0.1)
)

Now the expected pattern of polled values should be that the floorx and absx that come between two outx’s should be of the same value.
But… it turns out they’re not in phase because of something that happens at the beginning, where 3 floorx and 2 absx are polled before we get the first outx.

Relevant poll output:
floorx: 0 block offset: 0
absx: 0 block offset: 0
floorx: 0.005 block offset: 0
absx: 0.005 block offset: 0
floorx: 0.01 block offset: 0
outx: 0.005 block offset: 0
absx: 0.01 block offset: 24
floorx: 0.015 block offset: 24
... so now we're no longer in phase
outx: 0.01 block offset: 24
absx: 0.015 block offset: 48
floorx: 0.02 block offset: 48
outx: 0.015 block offset: 48
... this becomes visible in the plot around the peak
outx: 0.96 block offset: 40
absx: 0.485 block offset: 0
floorx: 0.49 block offset: 0
outx: 0.97 block offset: 0
absx: 0.49 block offset: 24
floorx: 0.495 block offset: 24
... leap form 0.98 output to...
outx: 0.98 block offset: 24
absx: 0.495 block offset: 48
floorx: 0.5 block offset: 48
... 1.01 output (should be 0.99)
outx: 1.01 block offset: 48
absx: 0.5 block offset: 8
floorx: 0.505 block offset: 8
outx: 1 block offset: 8
absx: 0.505 block offset: 32
floorx: 0.51 block offset: 32
outx: 0.99 block offset: 32
absx: 0.51 block offset: 56
floorx: 0.515 block offset: 56
outx: 0.98 block offset: 56

So here’s what I either don’t understand or is a bug:
Why don’t all the polled DUGens get called the same number of times? Presumably it’s not an issue with Dpoll, because the plots confirm the values reported by it.

Any ideas? (I’ve attempted to use Dunique for this, but it seems that works only with respect to a Demand.ar, and not within Demand chains…)

EDIT:
I replaced floor by trunc and at least now the two values are “in phase”, even though the very first pair is still somehow swallowed. I guess it has to do with how the usual AbstractFunction methods work when dealing with DUGens…
Otoh I didn’t expect trunc to do what it turns out to be doing… I thought it would truncate toward zero, but it seems to be downward always. The more you know…

I copied the examples twice and wanted to help, but was not really sure where the problem is. Was thinking that i like to use Dseries with normalized increments like Dseries(0, 1) but dont know if thats related to the problem. I was not sure if its about Ddup actually.

Thanks… sorry, I’ve probably phrased this confusingly.

What I would like is, if I have some function that calls an argument twice in the calculation, like the triangle waveform:

tri = { | x | 2 * abs(x/100 - floor(x/100 + 0.5)) }

and I feed that function with a Dseries, that both the x’s in the function body get the same value for each evaluation of the function.
But that currently does not seem to be the case, so

tri.(Ddup(2, Dseries(0, 1, 200)))

ends up being evaluated as, say,

2 * abs(1/100 - floor(2/100 + 0.5))
2 * abs(2/100 - floor(3/100 + 0.5))
2 * abs(3/100 - floor(4/100 + 0.5))
// etc.
// or generally
2 * abs(x[n-1] - floor(x[n] + 0.5))
// when it should be
2 * abs(x[n] - floor(x[n] + 0.5))

which leads to a shape that isn’t exactly triangular. I hope this makes more sense.

Interestingly, floor is a unary op while trunc is a binary op. The fact that the behavior is different between them suggests that there’s some inconsistency in their initialization.

Part of UGens’ initialization process is to calculate their first value and save it at its output, to serve as an input for its descendants that will initialize later. The Ddup must be getting queried at this time. It’s strange though that the initialization isn’t the same between the two operators, and that it isn’t always an even number of polls.

I haven’t had time to look at it but I suspect this is where to look, at least.

hjh