Wanted: a plugin to find synchronous zero crossings

Need a plugin to highlight where are both stereo waveforms at very zero right at the same time. It’s possible to find manually even in small selections but too long.

Select > Region > Ends to Zero Crossings function is inaccurate for me as it just suggests points with least clicks.

I don’t want to use normalize/repair/fading effects as mentioned here , because they all change the waveform.

Versions: Audacity 2.4.2, Windows 8 x64

I don’t think that exists but it should be possible with Nyquist.

Note that exact-zero samples are rare so you’d be looking for places where a positive sample is followed by a negative sample or vice-versa.

What should the plug-in do if left and right do not have a zero crossing at the same time?


First Track000.png

This code, when run in the Nyquist Prompt (https://manual.audacityteam.org/man/nyquist_prompt.html) will create a label at the first zero crossing within the selected audio. If there are no zero crossings common to both channels, an error message is shown.

Note that this code can only be used on stereo tracks. On a mono track it will silently fail.
Note that if you select a long track and there are no zero crossings common to both tracks, it will be very slow - I’d strongly recommend that you only use it on short selections.

If you want to convert this into an installable plug-in, see: http://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference

(defun find-zero (sig)
  (let ((sign0  (plusp (snd-fetch (aref sig 0)))) ; left
        (sign1  (plusp (snd-fetch (aref sig 1)))) ; right
        zx0 ; zero crossing flag (left)
        zx1 ; zero crossing flag (right)
        (count 0.5))
    (do ((val0 (snd-fetch (aref sig 0)) (snd-fetch (aref sig 0)))
         (val1 (snd-fetch (aref sig 1)) (snd-fetch (aref sig 1))))
        ((not val0) nil)
      (setf newsign0 (plusp val0))
      (setf newsign1 (plusp val1))
      (setf zx0 (not (or (and (not sign0) (not newsign0))
                         (and sign0 newsign0))))
      (setf zx1 (not (or (and (not sign1) (not newsign1))
                         (and sign1 newsign1))))
        ((and zx0  zx1)
          (return-from find-zero count))
          (setf sign0 newsign0)
          (setf sign1 newsign1)
          (incf count))))))

(setf z (find-zero *track*))
(if z
    (list (list (/ z *sound-srate*) "Z"))
    "Zero crossing not found")

Thank you, but since an attack part of a sound is more short and visible than a release part, I always split waveforms just before the next sound to prevent ripping a note apart :laughing: So it’s more useful to detect crossing just before the split line, therefore the last possible in selection. What needs to be changed in your code?

Note that if you select a long track and there are no zero crossings common to both tracks, it will be very slow - I’d strongly recommend that you only use it on short selections.

I usually deal with a single bar of music per crossing and less. I guess it’s not long.

If I were charging for this job, the price would now have doubled :laughing:

Instead of “(return-from find-zero count)” when a zero crossing is found, it needs to set a “result” variable to the current “count”. The loop will then process all samples in the selection (which will be slower because it can’t terminate early). When all samples have been processed, return “result” or “nil”.
(The boolean logic will probably also require some modification as we don’t want to carry the flags beyond the current sample)

Do you want to try modifying it yourself?

I do, but I can’t. Since I’m not a programmer, just understanding of what you wrote is harder than doing things as before. I wonder how nobody else needs it 20 years later.

I wasn’t a programmer either, but I needed modified versions of some plug-ins, so I used the documentation and had a go. I was fortunate to have help when I needed it (that was on a mailing list, as this forum had only just started). As with anything, the more you do, the more you learn :wink:

Try this version. I’ve also added some comments to the code that explain some of the less obvious parts:
(a “comment” is text that is ignored by Nyquist and is for the benefit of anyone reading the code. Comments begin with a semicolon “;”)

If you have any questions about this code, feel free to ask.

(defun find-zero (sig)
  (let ((prev0  (plusp (snd-fetch (aref sig 0)))) ; is left positive
        (prev1  (plusp (snd-fetch (aref sig 1)))) ; is right positive
        new0  ;new sample (left)
        new1  ;new sample (right)
        zx0 ; zero crossing flag (left)
        zx1); zero crossing flag (right) 
    (do ((val0 (snd-fetch (aref sig 0)) (snd-fetch (aref sig 0)))
         (val1 (snd-fetch (aref sig 1)) (snd-fetch (aref sig 1)))
         (count 0.5 (1+ count)))  ; rslt flag will be placed between previous and current samples
        ((not val0) rslt)
      (setf new0 (plusp val0))  ; is left still positive
      (setf new1 (plusp val1))  ; is right still positive
      ;; Previous and current sign both positive, or both "not positive".
      ;; When both are the same sign, we have not crossed zero.
      (setf zx0 (or (and prev0 new0)
                    (and (not prev0) (not new0))))
      (setf zx1 (or (and prev1 new1)
                    (and (not prev1) (not new1))))
      (when (and (not zx0) (not zx1))  ; left and right have crossed zero
        (setf rslt count))
      ;; Update previous sample flags
      (setf prev0 new0)
      (setf prev1 new1))))

(setf z (find-zero *track*))
(if z
    (list (list (/ z *sound-srate*) "Z"))
    "Zero crossing not found")

I can only speak for myself:
When I’m doing a lot of detailed editing, I’m usually working with mono tracks, so this is a non-issue.
When working with stereo tracks, I’m usually only making a few edits, so it’s no great difficulty to simply zoom in close (Ctrl + Mouse wheel) and select an edit point manually.

Ok, thank you again. This code is ready to be converted, right? If it doesn’t work properly for free, I’ll return where I came from. It’s faster than reading manuals, and I don’t really edit a lot of songs in Audacity. Once I learned the very basics of php and javascript though. I have no great desire to walk this path again, because coding isn’t my vocation, the music is.

The reason I can’t work with mono is improving others’ stereo tracks by soft-remixing them according to my feeling of beauty. Or making instrumental versions using original recordings of that songs, or something else.

Does it do what you want it to do?

Same here :wink:

Looks like no.
What it does
What I need

That image shows the waveform in both tracks “crossing zero”. That is what a “zero crossing point” is.

If you only want to find places where samples are exactly zero in both tracks, that will hardly ever occur in “real-world” audio. Other than “generated” audio and “absolute silence”, sample values are hardly ever exactly at zero.

So the crossing is anywhere between two nearest dots? Or just where the split line placed? If second than it’s hard to guess it manually, with no ability to see the waveform at current scale. That’s why I’ve never used this method.

It occurs in every “normal” song many times. You just haven’t tried to find it.
My 2nd picture is not photoshop, it’s a real 3rd party rock song, I can send you .aup if you want.

Not “exactly” zero.

For simplicity, try this code on a mono track. It adds a label each time a sample is exactly zero.

(setf labels ())
(do ((val (snd-fetch *track*) (snd-fetch *track*))
     (count 0 (1+ count)))
    ((not val) labels)
  (when (= val 0)
    (push (list (/ count *sound-srate*) "") labels)))

Also, if you zoom in vertically as far as possible, in most cases you will be able to see that samples are not “exactly” zero.

It occurs in every “normal” song many times. You just haven’t tried to find it.

There is NOTHING between digital samples.* The original analog or reconstructed analog will have a true-zero value twice per cycle.

In digital a “zero crossing” is just-before or just-after a polarity (sign) change. (We can’t get in-between the digital samples.)

The sample times are essentially random (uncorrelated with the audio) so with 16 bits the odds of hitting zero are about 1 in 65,535. At a sample rate of 44.1kHz you’d expect a true-zero value about every 1.5 seconds on average (assuming mono files and random distribution). Actually the odds are a little better than that because the sample values are skewed toward lower values, and you double your odds with stereo. Or if there is “digital silence” you’ll have a bunch of zeros in a row. “Artificially” generated tones could have much better odds depending on the relationship between frequency and sample rate. At 24-bits the odds of zero are about 1 in 16 million (again assuming true randomness and mono). With floating point (or files from MP3) the odds of an exact-zero are nearly zero.

The odds of a visual zero are a LOT higher because Audacity (and your video monitor) doesn’t have as much pixel resolution as the audio file.

I don’t know the odds of finding left & right zero crossings that match (digitally) but you’ll probably have a good feel for it soon!


  • If this doesn’t make sense take a look at [u]this little tutorial[/u] that shows how audio is digitized (quantized in both time & amplitude) and then converted back to analog when played back.

and for the default 32-bit float format, much, much, much less likely.

Works only when silent before the songs starts. Doesn’t show zeros in song selections.

Now I got it more or less. Thank you for the explanation.

Perfectionism plus ignorance :slight_smile:

That’s because it is only adding labels when the sample is exactly zero.
This line:

(when (= val 0)

Try changing that to:

(when (< (abs val) 0.000001)