What the SuperCollider closest implementation of zerox~ Max/MSP object might look like? What would be the difference between my code below and actual zerox~?

How to convert ZeroCrossings output to a number of zero-crossings per signal vector?

Not sure if these are UGens, but they donâ€™t need to be. Trig will give you a trigger on each cross from negative to non-negative, so if you put the signal and its inverse through Trigs, you can get all the zero crossings:

//trig on zero crossings
(
{
var a;
a = SinOsc.ar(SinOsc.kr(1, 0, 20,30), 0, 0.1);
b = Trig1.ar(a, 1/s.sampleRate)+Trig1.ar(a.neg, 1/s.sampleRate);
[a, b]
}.play;
)
//count the pulses
(
{
var a;
a = SinOsc.ar(SinOsc.kr(1, 0, 20,30), 0, 0.1);
b = Trig1.ar(a, 1/s.sampleRate)+Trig1.ar(a.neg, 1/s.sampleRate);
i = Impulse.ar(1);
c = PulseCount.ar(b, Delay1.ar(i)); //delay the trigger by one sample so that polls the sum before it resets
Poll.ar(i, c);
nil
}.play;
)

When a nonpositive to positive transition occurs at the input, Trig1 outputs 1 for the specified duration, otherwise outputs 0.

ZeroCrossing

Outputs a frequency based upon the distance between interceptions of the X axis. The X intercepts are determined via linear interpolation so this gives better than just integer wavelength resolution. This is a very crude pitch follower, but can be useful in some situations.

Hopefully that clears it.
ZeroCrossing might not change regularly, in fact, as its designed as a pitch tracker (albeit, a crude one) it should update only when it thinks the frequency has changed - if the signal is periodic in the short term, it shouldnâ€™t change too often. In short, ZeroCrossing is not the tool to use if you want a signal when an input crosses zero - admittedly, it has quite a poor name.
Trig1 updates when ever the value crosses from a negative number to a positiveâ€¦ what isnâ€™t necessarily obvious is it will tread -0 and +0 differently. Nevertheless, Trig1 is the correct tool.

The issue with Trig is the fragility of s.sampleRate.reciprocal and the two sides of the plus â€¦ sample rate might change if you use multiple servers, or you might type it wrong, there could be some odd rounding error, or you could copy the two expressions either side of the plus wrong.

Sorry if that came off as condensing - certainly didnâ€™t mean to be -, itâ€™s just the reason why itâ€™s different is explained there. If you put in a sine wave with a steady frequency, it should change once, and then never again as zero crossing outputs a frequency calculated by using the zero crossing.

The definition of a trigger is a transition from non-positive to positive. If the Trig1 output occurs one sample after this transition, then itâ€™s violating the definition and that should be fixed.

But a/ it isnâ€™t breaking the definition and b/ that isnâ€™t the cause of a â€śone-sample delayâ€ť anyway. (There isnâ€™t a delay; letâ€™s not start folklore going here.)

(
// signal divides nicely into the sample rate
p = {
var sig = SinOsc.ar(SampleRate.ir/10);
var trig = Trig1.ar(sig, 1/s.sampleRate);
[sig, trig]
}.plot(20/s.sampleRate)
)
// hit 'm' to see sample by sample values
p.value[0].size
p.value[0][8..12]
-> [ -0.95105642080307, -0.58778524398804, -2.3399479687214e-08, 0.58778518438339, 0.95105642080307 ]

Sample 10 is not a zero crossing! Sample 9 is negative and so is sample 10.

Itâ€™s risky to read a graph and tell yourself â€śthe trigger should be happening thereâ€ť because graphs donâ€™t have enough precision (even with 1920 vertical pixels, you canâ€™t see a difference on the order of 10e-08).