Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 12 of 12

Thread: zero crossing audio object for frequency detection

  1. #1

    zero crossing audio object for frequency detection

    Hello,

    I want to mesure as fast as possible the frequency of a sine.
    The audio library already provides objects for pitch tracking, but they have some latency due to buffering.
    Since my signal is almost a pure sine, I would like to use a "zero crossing" method to estimate frequency.

    I use a teensy 3.6 with audio shield.
    I have already created very basic custom audio objects, but I can't figure out how to start for this one. For the moment, I was just able to create an object that indicates when the audio signal is either positive or negative.

    If someone could give me some directions an ideas, that would help me a lot.
    Emmanuel

    Code:
    #include "zero_pitch.h"
    
    void zero_pitch::update(void) {
      int16_t *p, *end;
      audio_block_t *block;
    
    
      block = receiveReadOnly(0); //block reçoit l'adresse du paquet audio canal 0
      if (block == NULL) return;
      p = block->data;     
      end = p + AUDIO_BLOCK_SAMPLES; 
      while (p < end) {
        
        int16_t s = *p; 
        if (s < 0 && alternance == 1) {
          alternance = 0;
        }
        if (s > 0 && alternance == 0) {
          alternance = 1;
        }
        p++;
      }
      transmit(block);
      release(block);
    }
    
    bool zero_pitch::getAlternance() {
      return alternance;
    }

  2. #2
    Senior Member
    Join Date
    Jul 2020
    Posts
    350
    Well you could time successive crossings, take the reciprocal to get a frequency estimate, and then digitally filter the result to smooth variations.

    You'd be able to get _much_ better results if you interpolate between samples to estimate zero-crossing time to a fraction of a sample period.

    If the waveform isn't symmetric you'd want to time between consecutive rising crossings, as the crossings won't be even in time.

  3. #3
    Hi,
    Thank you for your suggestions. Here is a first attempt :
    Code:
    #include "zero_pitch.h"
    
    void zero_pitch::update(void) {
      int16_t *p;
      audio_block_t *block;
      block = receiveReadOnly(0);
      if (block == NULL) return;
      p = block->data;
    
    
      for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
        int16_t s = *p;
    
        if (s >= 0 && upward == 0) {
          upward = 1;                         //audio signal is going up
          period = i - prev_i;                //how many sample since last 0 crossing
    
          if (period > 0) {
            float freq = AUDIO_SAMPLE_RATE_EXACT / period;
            Serial.println(freq);  //for debugging
          }
          prev_i = i;
        }
    
        
        if (s < 0 && upward == 1) {
          upward = 0;                          //audio signal is going up
        }
        p++;
      }
      transmit(block);
      release(block);
    }
    My code is working but it is very poor and I am really not proud of it :-(
    At least, it's fast.
    It is working with a block of 128 samples. I need 256 samples blocks but I can't figure out the way to use 2 blocks in my code.
    The way I track the zero crossings is also very weak.

    A little help would be very appreciated...
    Emmanuel

  4. #4
    Senior Member
    Join Date
    Jul 2020
    Posts
    350
    Why are you retransmitting the block? Wouldn't this make more sense either as a destination only?

    To handle multiple blocks you need to store references to the blocks locally and remember to release them
    when you finished using them. For 2 blocks you store the previous block only which is easiest:

    Get the current block.
    process the previous block and current block,
    release the previous block, store ref to current block as prev block for next time.

    Any NULL block pointers are treated as all zero.

  5. #5
    You're right, I don't need to transmit the block. I will change that.

    I have improved my code and it's working well now. Basically, here is my method :
    - I first initialise a sample counter
    - I track the zero crossings and read my sample counter at every crossing
    - simple maths give me period and frequency

    This is basically what you suggested first.
    Doing this way, I don't need larger audio blocks for low frequency.

    Emmanuel

  6. #6
    Junior Member
    Join Date
    Aug 2020
    Posts
    14
    Are you trying to measure an external signal? If so, for minimum latency you might consider using an external comparator like an LM393 and connect it's output to a digital input. You can use a potentiometer as a voltage divider for the comparison voltage then AC couple the input signal via a capacitor to the other comparator input. This will give you the ability to set a threshold for some noise immunity if you want to detect presence/absence of signal. But if you don't care then 2 same value resistors (e.g. 47k) can be used a half voltage divider for a straightforward zero crossing detector. Ideally you'd want to use 2 comparators (conveniently, you get 2 per LM393 8-pin package) where one has an adjustable offset for signal detection and the other a straight zero crossing detector. Then 2 pins to Teensy.

    Quote Originally Posted by emmanuel63 View Post
    Hello,

    I want to mesure as fast as possible the frequency of a sine.
    The audio library already provides objects for pitch tracking, but they have some latency due to buffering.
    Since my signal is almost a pure sine, I would like to use a "zero crossing" method to estimate frequency.

    I use a teensy 3.6 with audio shield.
    I have already created very basic custom audio objects, but I can't figure out how to start for this one. For the moment, I was just able to create an object that indicates when the audio signal is either positive or negative.

    If someone could give me some directions an ideas, that would help me a lot.
    Emmanuel

    Code:
    #include "zero_pitch.h"
    
    void zero_pitch::update(void) {
      int16_t *p, *end;
      audio_block_t *block;
    
    
      block = receiveReadOnly(0); //block reçoit l'adresse du paquet audio canal 0
      if (block == NULL) return;
      p = block->data;     
      end = p + AUDIO_BLOCK_SAMPLES; 
      while (p < end) {
        
        int16_t s = *p; 
        if (s < 0 && alternance == 1) {
          alternance = 0;
        }
        if (s > 0 && alternance == 0) {
          alternance = 1;
        }
        p++;
      }
      transmit(block);
      release(block);
    }
    
    bool zero_pitch::getAlternance() {
      return alternance;
    }

  7. #7
    Thank you.
    You are right, an external comparator would probably do the job.
    I need to filter the audio signal before measuring frequency. I need to "slice" the signal into 4 bands, from 200 hz to 2000 hz approximatively. Low pass filter with high slope are required. Would you have a suggestion to that ?
    Emmanuel

  8. #8
    Junior Member
    Join Date
    Aug 2020
    Posts
    14
    Quote Originally Posted by emmanuel63 View Post
    Thank you.
    You are right, an external comparator would probably do the job.
    I need to filter the audio signal before measuring frequency. I need to "slice" the signal into 4 bands, from 200 hz to 2000 hz approximatively. Low pass filter with high slope are required. Would you have a suggestion to that ?
    Emmanuel
    Do you mean band pass filters (at least for the intermediate ranges)? What are you trying to measure? How separated do you expect the 4 frequencies you are trying to measure are? It could be very difficult or easy depending on what it is.

  9. #9
    Hi,
    I use 4 low pass filters. All the filters are fed with the audio input signal.
    first filter : fc = 300
    second : fc = 500
    third : 800
    forth : 1200 (for example).

    The output of the filters is routed to a pitch tracking sketch. This sketch is fast but requires strong pre-filtering (to remove harmonics). I found that 4 filters was enough to cover the range of audio signal. I use 4 biquad low pass filters. The concept is working good and I think I can achieve close to real-time pitch tracking.
    Have a look here to see what I am working on :
    https://youtu.be/N79Hcg-cOfI


    I would like to try now an external approach with dedicated filtering circuits. I need to build a low pass filter with a hard slope.

    Emmanuel

  10. #10
    Junior Member
    Join Date
    Aug 2020
    Posts
    14
    That looks really cool :-) I'm doing a guitar synth project - polyphonic tracking. But all in the digital domain. So far I'm getting 10-80Hz latency depending on the note. You're a much better guitarist than me!

    I'm not good enough with analogue design to dare to give you any advice with respect to designing audio high order filters. There's a lot that comes into it - choice of op-amps and passive components. But there seem to be a lot of knowledge out in the interwebs.

  11. #11
    Polyphonic tracking... This is very challenging.
    I had quite good result with the AccurateFFTInterpolator object. Here is a link :
    https://forum.pjrc.com/threads/36358...ncy-Estimation

    Even for monophonic pitch tracking, this object works very well.

    For my project, I am focused on the flute. It's easier because the natural pitch range of the flute is high (from C4). So I can achieve low latency.

    Thanks for your ideas. I will keep this thread active if I have success.
    Emmanuel

  12. #12
    Junior Member
    Join Date
    Aug 2020
    Posts
    14
    What I find particularly amusing is you're turning into a flute into another instrument and my personal challenge was to turn a guitar into a flute! Here's Bohemian Rhapsody as played by a bank of 32 flutes...

    http://fluffysoft.com.au/Bohemian_Rhapsody.mp3

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •