Pitch detection

Status
Not open for further replies.

RobinLi

Active member
Hello,
in my new project I have to detect the fundamental frequency of a Bassdrum using the mic pins of the audio shield. I am able to calculate the FFT but the Bandwidth to large to detect a exact tone. Is it possible to to that with the notefreq analyzer? Do I first have to save the record on a SD card?

Any advice is welcome!
 
Hello,
in my new project I have to detect the fundamental frequency of a Bassdrum using the mic pins of the audio shield. I am able to calculate the FFT but the Bandwidth to large to detect a exact tone. Is it possible to to that with the notefreq analyzer? Do I first have to save the record on a SD card?

Any advice is welcome!

I have not used notefreq(), but I do know there are quite a few algorithms out there that do it; it mainly depends on your needs. As WMXZ said, there is a nice link to the discussion with almost every source (from YIN to AMDF, to full autocorrelation algorithms) to do what you are asking for.

If you are interested, I have implemented my own algorithm on teensy using the Average Magnitude Difference Function in conjunction with the Harmonic Product Spectrum and Convolution techniques to reduce harmonic approximation errors. I have an instructable up that I continue to modify from time to time. The teensy sketch is not up, but the matlab code is with very clear notes on what each portion of the script does.

Its pretty calculation intensive, but I am working to reduce that.

http://www.instructables.com/id/Arduino-Pitch-Detection-Algorithm-AMDF/
 
Thanks for your help. I will read the thread and the other links and write, if I have more questions. But I think its exactly what I want.
 
Hey, I read the Thread and the Guitar Tuner Project. Is there any other example code for the noteFreq() function that is as simple as the sample code for the FFT function? I have to insert the function in my program and just want to know how it works.
 
Maybe I have to take a longer look at it and have to try it when I have my new teensy. Could you tell me what kind of data the array in the cpp files are?
 
Hello, its me again.
I was looking for an other way to detect the fundamental frequency of my Signal an made a program with MATLAB. Its an Autocorrelation which detects the minimum (red square) and multiplies the time difference with to and calculates the frequency. It works pretty good as you can see in the comparison. But with the Teensy its not as simple. I think in this case I need the SD card to save the audio file before I can work with it? Has anyone a simple autocorrelation program?
Bildschirmfoto 2016-02-24 um 10.46.02.png
 
Hello, its me again.
I was looking for an other way to detect the fundamental frequency of my Signal an made a program with MATLAB. Its an Autocorrelation which detects the minimum (red square) and multiplies the time difference with to and calculates the frequency. It works pretty good as you can see in the comparison. But with the Teensy its not as simple. I think in this case I need the SD card to save the audio file before I can work with it? Has anyone a simple autocorrelation program?
View attachment 6463

As you use Matlab:
I would not only use xcorr, but would program it explicitly (you know: sum of product of timeseries with delayed timeseries). It should be a simple loop.
Compare result with xcorr. Convert to C program and run on Teensy.
After all Teensy allows you quick tests of C- algorithms.
There are some design criteria to make it efficient, but playing with small test programs allows you to make your decisions easily.
 
The plot above works with xcorr but my Programm without doesn't work. I used this formula:

http://www.itl.nist.gov/div898/handbook/eda/section3/eda35c.htm

y = [0,-1,0,1,0,-1,0,1,0,-1,0,1,0,-1,0,1,];
N = length(y);
p = 0;
q = 0;
r = 0;
bary = mean(y);

for k = 1:N-1
%---------------Ch------------------
for i=1:N-k
p(k)=(y(i)-bary)*(y(i+k)-bary);
end
p(k) = sum(p);
%---------------C0-------------------
for i=1:N
q(k)=(y(i)-bary)*(y(i)-bary);
end
q(k) = sum(q);

%----------------------------------------
r(k+1)=p/q;
end
stem(r);
axis([0 10 -2 2])
 
if you try this
Code:
y = [0,-1,0,1,0,-1,0,1,0,-1,0,1,0,-1,0,1,];
 N = length(y);
 r = zeros(N,1);
 bary = mean(y);

 for k = 1:N-1
     %---------------Ch------------------ 
     p = 0;
     for i=1:N-k
         p=p+(y(i)-bary)*(y(i+k)-bary);
     end
     %---------------C0-------------------
     q=0;
     for i=1:N
         q=q+(y(i)-bary)*(y(i)-bary); 
     end
     %----------------------------------------
     r(k)=p/q;
 end
 figure(1)
 hold off
 plot((1-N:N-1)',xcorr((y-bary))./sum((y-bary).^2))
 hold on
 stem(r,'color','r');
% axis([0 10 -2 2])
then it works

note the differences.

there are a couple of errors in your matlab code
e.g. the three lines cannot generate the results you wanted
Code:
 p(k) = sum(p);
 q(k) = sum(q);
 r(k+1)=p/q;
 
Hello, its me again.
I was looking for an other way to detect the fundamental frequency of my Signal an made a program with MATLAB. Its an Autocorrelation which detects the minimum (red square) and multiplies the time difference with to and calculates the frequency. It works pretty good as you can see in the comparison. But with the Teensy its not as simple. I think in this case I need the SD card to save the audio file before I can work with it? Has anyone a simple autocorrelation program?
View attachment 6463

From your example, it doesn't seem as though you have enough time to do this determination reliably and within the constraints of the perceivable human reaction time. From my research and what I can recall from the reading I did, it takes the human brain at the very least 5-6 periods to determine the "frequency" of the quasi-harmonic tone. Though, long in this respect isn't that long at all...

If you assume a 200hz signal you want to detect, 5 periods is still 25ms. Any less and the human ear can't determine it accurately anyway. I think if you use this assumption you'll get better results with your autocorrelation algorithm and reduce the rigorous requirements to detect a tone within a single period as your image depicts.

I think in this case I need the SD card to save the audio file before I can work with it? Has anyone a simple autocorrelation program?

I understand your troubles. I tried using the ADC object, but the ADC library gets trampled by the AUDIO libraries priority. In this case I recorded sample sets and waited until I got the number of memory blocks I needed to do my AMDF, similar to autocorrelation.

Here is a sample of what I did:

Code:
//**************************************************************************************************
///////////  AVERAGE MAGNITUDE DIFFERNCE FUNCTION w/ ENVELOPE /////////////
//**************************************************************************************************
void amdf_result(){

  //////CHECK BIN COUNT
  //////If enough bins are available process the amdf
  //////
  if (amdf_samples.available() >= num_of_blocks){


    //////SETUP
    //////Setup all the variables for AMDF
    //////
    byte buff[num_of_blocks*256];
    int16_t samples[num_of_blocks*128];
    int power_calc = 0;
    int dc_sum = 0;
    int amdf[max_amdf];
    int temp_result = 0;
    int max_sample = 0;


    //////COPY
    //////Fetch DMA blocks from the audio library and copy into a buffer
    //////Also, form all the 16bit signed numbers from the buffer bytes
    //////Additionally, collect samples to sum up the power to validate output
    //////
    for(int i = 0; i < num_of_blocks;i++){
      int index_shift = i*256;
      memcpy(buff + index_shift, amdf_samples.readBuffer(), 256);
      amdf_samples.freeBuffer();
    }
    for(int i = 0; i < num_of_blocks * 128; i++){
      samples[i] = buff[i*2] | (buff[i*2+1] << 8);  
      dc_sum += samples[i];
      samples[i] = samples[i] - dc_offset;
      int abs_sample = abs(samples[i]);
      power_calc += abs_sample;
      if(abs_sample > max_sample) max_sample = abs_sample;
    }

    
    //////UPDATE AVERAGE POWER
    //////Update the power array by taking the last 4 averages
    //////
    power_calc = sqrt(power_calc) - power_noise;
    if(power_calc < 0 ) power_calc = 0;
    input_power_array[input_power_array_pointer] = power_calc;
    input_power_array_pointer++;
    if(input_power_array_pointer > 3){ input_power_array_pointer = 0; }
    input_power = ( input_power_array[0] + input_power_array[1] + 
                    input_power_array[2] + input_power_array[3] ) / 4;


    //////UPDATE DC OFFSET
    //////Update the DC offset because AUDIO library does something weird with DC offset
    //////
    dc_offset_array[dc_offset_array_pointer] = dc_sum / ( num_of_blocks * 128);
    dc_sum = 0;
    dc_offset = ( dc_offset_array[0] + dc_offset_array[1] +
                  dc_offset_array[2] + dc_offset_array[3] +
                  dc_offset_array[4] + dc_offset_array[5] +
                  dc_offset_array[6] + dc_offset_array[7] )/8; 
    dc_offset_array_pointer++;
    if(dc_offset_array_pointer > 7){ dc_offset_array_pointer = 0; } 


    //////AMDF
    //////average magnitude difference function operates on a fixed window
    //////size between the min and max requested values of delay. 
    //////
    for(int i = 0; i< max_amdf; i++){ amdf[i] = 0; }
    for(int point = min_amdf; point < max_amdf; point++){
      temp_result = 0;
      for(int i = 0; i < amdf_window_size; i++){
        temp_result += abs(samples[i] - samples[ i + point ]);
      }
      amdf[point] = temp_result;
    }

  
    //////FIND MIN AND MAX
    //////used for inverting function later in algorithm
    //////
    int temp_max = 0;
    int temp_min = 1000000;
    for(int i = min_amdf; i < max_amdf; i++){
      if(amdf[i] > temp_max){ temp_max = amdf[i]; }
      if(amdf[i] < temp_min){ temp_min = amdf[i]; }
    }
        
    
    //////NEGLECT OUT OF RANGE
    //////floor any values that are not in the requested range
    //////
    for(int i = 0; i < min_amdf+2; i++){ amdf[i] = temp_max; }
  
  
    //////INVERT
    //////Invert the result array, allow high correlations to pass
    //////
    temp_max = temp_max - temp_min;
    int strong_correlation = (int)((float)temp_max * 0.85);
    for(int i = 0 ; i < max_amdf; i++){
      amdf[i] = amdf[i] - temp_min;
      amdf[i] = temp_max - amdf[i];
      if(amdf[i] < strong_correlation){ amdf[i] = 0; }
    }

  
    ////////////////////////////////////////////////////////////////////////////////////////////////
    //////   DIAGNOSTIC   DIAGNOSTIC   DIAGNOSTIC   DIAGNOSTIC   DIAGNOSTIC   DIAGNOSTIC   /////////
    ////////////////////////////////////////////////////////////////////////////////////////////////
    for(int i = 0; i<max_amdf; i++){
      output_amdf[i] = amdf[i];
    }

  
    //////ENVELOPE
    //////linear scale the AMDF spectra to preferrentially choose higher frequencies
    //////
    int reduction = temp_max / 2 / max_amdf;
    for(int i = 0; i<max_amdf; i++){
      if(amdf[i] != 0){
          amdf[i] = amdf[i] - i*reduction;
      }
      if(amdf[i] < 0) amdf[i] = 0;
    }      


    //////OUTPUT GUESS
    //////Find the max of the resulting enveloped spectrum 
    //////
    temp_max = 0;
    int temp_peak_index = 0;
    for(int i = 0; i < max_amdf; i++){
      if(amdf[i] > temp_max){
        temp_max = amdf[i];
        temp_peak_index = i; }
    }

      
    ////Power Picking
    ////If current calculated power is above limit, allow it to contribute to output
    ////
    if(power_calc > power_input_limit){
    
      //////AVERAGE GUESSES
      //////Pass peak value to the averaging value output
      //////
      peak_index[peak_index_pointer] = temp_peak_index;
      peak_current = temp_peak_index;
      peak_average = ( peak_index[0] + peak_index[1] +
                       peak_index[2] + peak_index[3] +
                       peak_index[4] + peak_index[5] +
                       peak_index[6] + peak_index[7] ) / 8;
      peak_index_pointer++;
      if(peak_index_pointer > 7){ peak_index_pointer = 0; }


    }else{
      //Update for too low of an input to prevent spurious results
      peak_current = 0;     
    }

    
  }//end amdf_samples
   
}// end amdf envelope algorithm

And you'll need to setup the AUDIO object to start collecting samples

Code:
  AudioMemory(20);
  amdf_samples.begin();

And remember that if you dont delete the memory blocks often it will overflow and kill the audio library
 
Last edited:
Okay, thanks. The MATLAB Code works but its very slow with a signal sampled with 44100Hz. (Maybe the Teensy is faster!?) Unfortunately the audio shield is not able to record with a lower sample rate so I will try the next days to record with the ADC Input.
I know, that the signal is not very long and I need the program to synthesize a signal with the same frequency or half but longer to determine it.
 
Josechow, your code is a bit to hard for me at the moment. I'm a beginner in program but I keep at it. I will try to save it in the buffer and record with 44.1kHz sampling frequency and sample it down by taking every 40 sample or something like that.
 
... record with 44.1kHz sampling frequency and sample it down by taking every 40 sample or something like that.

Before you throw away samples, you *must* low-pass filter the signal to only the bandwidth your final data will use. If you skip this important step, you will get terrible noise+distortion from the higher frequencies that were present. Many people often wish to skip the filtering step, because it's difficult and just discarding samples is so very easy. This filtering requirement is pretty basic Nyquist sampling theory. Ignoring the need to filter, only because it's hard and computationally intensive, is a wrong path *many* people have tried and failed. Don't fool yourself into thinking the filtering isn't needed, if your original signal has any higher frequencies that won't be able to fit into the lower bandwidth after you've discarded data.
 
Oh yes, I should know this.
I need it for reduce the computing time of the autocorrelation and buffer size. But I think a good LP needs also much computing time. I think I should first try it with 44.1k when I get my audio shield. The signal is not longer than 0.5 sec.
 
Okay, I tried the "Recorder" example and recorded something on the SD card. What can I do to get the samples of the recorded file? I'm a bit confused what queue, buffer and SD is storing and how they work exactly.
 
Before you throw away samples, you *must* low-pass filter the signal to only the bandwidth your final data will use. If you skip this important step, you will get terrible noise+distortion from the higher frequencies that were present. Many people often wish to skip the filtering step, because it's difficult and just discarding samples is so very easy. This filtering requirement is pretty basic Nyquist sampling theory. Ignoring the need to filter, only because it's hard and computationally intensive, is a wrong path *many* people have tried and failed. Don't fool yourself into thinking the filtering isn't needed, if your original signal has any higher frequencies that won't be able to fit into the lower bandwidth after you've discarded data.

Oops... Forgot to mention that step...
 
RobinLi: What is your requirement of the detecting speed? I have used the notefreq block with great success down to 30 Hz. But then it uses 24 blocks of audio samples (24*128samples) to be able to detect it. That is almost 70ms. If you can live with that it is the absolute easiest route. What is the lowest frequency you want to detect?
You can reduce the number of blocks you want to use by modifying this line in the analyze_notefreq.h file:
#define AUDIO_GUITARTUNER_BLOCKS 24

That will then of course limit the lowest frequency it is able to detect.

You said that you want to detect a bass drum. If you want a fast response to put out a signal at half the frequency or something like that, I would recommend to first detect the frequency(that is quite stable on a drum), and then use that frequency later on and just use a peek signal detect block to trigger an output.
 
Last edited:
Okay, I will try this. But first I want to edit the Recorder example. What I need is: Press buttonRecord=> record for two seconds, press buttonDetect => Detect the note Frequency. Unfortunately I have a problem with the queue. The if condition i commented does not meet, but I don't know why. If that works I try to implement the noteFrequency object with 24 blocks :)
Who can help me with the queue problem?

Code:
#include <Bounce.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioInputI2S                 LineIn;
AudioRecordQueue              queue1;
AudioPlaySdRaw                playRaw;
AudioConnection               patchCord1(LineIn,0,queue1,0);
AudioControlSGTL5000          sgtl5000_1;

Bounce buttonRecord = Bounce(0,8);
Bounce buttonDetect = Bounce(1,8);

const int myInput = AUDIO_INPUT_LINEIN;

int mode = 0;           // 0=Stop, 1=Recording, 2=Detecting

File frec;

void setup()
{
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  AudioMemory(60);

  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);

  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10)))
  {
    while(1)
    {
      Serial.println("Unable to access the SC card");
      delay(500);
    }
  }
}

void loop()
{
  buttonRecord.update();
  buttonDetect.update();
  if (buttonRecord.fallingEdge())
  {
    Serial.println("Record Button Press");
    if (mode == 0) startRecording();
  }
  if (buttonDetect.fallingEdge())
  {
    Serial.println("Detect Button Press");
    if (mode == 0) Detecting();
  }
  if (mode == 1) 
  {
    continueRecording();
  }
}
void startRecording()
{
  Serial.println("startRecording");
  if (SD.exists("RECORD.RAW"))
  {
    SD.remove("RECORD.RAW");
  }
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  if (frec)
  {
    queue1.begin();
    mode = 1;
  }
}

void continueRecording()
{
  Serial.println("Continue Recording");
  if(queue1.available() >= 2)                 // !!!!!!!!!!!!!!!!!!!!
  {
    //Serial.println("test");
    byte buffer[512];
    memcpy(buffer,queue1.readBuffer(),256);
    queue1.freeBuffer();
    memcpy(buffer+256,queue1.readBuffer(),256);
    queue1.freeBuffer();
    elapsedMicros usec = 0;
    frec.write(buffer,512);
    Serial.print("SD write, us=");
    Serial.println(usec);

    // Stop Recording
    delay(2000);
    Serial.println("StopRecording");
    queue1.end();

    while(queue1.available() > 0)
    {
      frec.write((byte*)queue1.readBuffer(),256);
      queue1.freeBuffer();
    }
    frec.close();
  }
  mode = 0;
}
void Detecting()
{
}
 
The Recorder works, but I have now problems to calculate the note frequency. I found no example where the analyzer reads from a raw file on the SD card.
This is my code but the note frequency is never available. Important is the detecting function at the end. I want to play the recorded file (it works) und detect the frequency. Any ideas how I can do that? Sorry for my stupid questions.

Code:
#include <Bounce.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioInputI2S                 LineIn;
AudioOutputI2S                LineOut;
AudioRecordQueue              queue1;
AudioPlaySdRaw                playRaw;
AudioAnalyzeNoteFrequency     notefreq;
AudioConnection               patchCord1(LineIn,0,queue1,0);
AudioConnection               patchCord2(playRaw, notefreq);
AudioConnection               patchCord3(playRaw, 0, LineOut, 0);
AudioConnection               patchCord4(playRaw, 0, LineOut, 1);
AudioControlSGTL5000          sgtl5000_1;

Bounce buttonRecord = Bounce(0,8);
Bounce buttonDetect = Bounce(1,8);
Bounce buttonStop   = Bounce(2,8);

const int myInput = AUDIO_INPUT_LINEIN;

int mode = 0;           // 0=Stop, 1=Recording, 2=Detecting

File frec;

void setup()
{
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  AudioMemory(60);
  notefreq.begin(.15);

  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.9);

  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10)))
  {
    while(1)
    {
      Serial.println("Unable to access the SC card");
      delay(500);
    }
  }
}

void loop()
{
  buttonRecord.update();
  buttonDetect.update();
  buttonStop.update();
  if (buttonRecord.fallingEdge())
  {
    Serial.println("Record Button Press");
    if (mode == 0) startRecording();
  }
  if (buttonDetect.fallingEdge())
  {
    Serial.println("Detect Button Press");
    if (mode == 0) Detecting();
  }
  if (buttonStop.fallingEdge())
  {
    Serial.println("Stop Button Press");
    if (mode == 1) stopRecording();
  }
  if (mode == 1) 
  {
    continueRecording();
  }
  
}
void startRecording()
{
  Serial.println("startRecording");
  if (SD.exists("RECORD.RAW"))
  {
    SD.remove("RECORD.RAW");
  }
  frec = SD.open("RECORD.RAW", FILE_WRITE);
  if (frec)
  {
    queue1.begin();
    mode = 1;
  }
}

void continueRecording()
{
  if(queue1.available() >= 2)
  {
    byte buffer[512];
    memcpy(buffer,queue1.readBuffer(),256);
    queue1.freeBuffer();
    memcpy(buffer+256,queue1.readBuffer(),256);
    queue1.freeBuffer();
    elapsedMicros usec = 0;
    frec.write(buffer,512);
  }
}

void stopRecording() 
{
  Serial.println("stopRecording");
  queue1.end();
  if (mode == 1) 
  {
    while (queue1.available() > 0) 
    {
      frec.write((byte*)queue1.readBuffer(), 256);
      queue1.freeBuffer();
    }
    frec.close();
  }
  mode = 0;
}
void Detecting()
{
  Serial.println("Detecting");
  playRaw.play("RECORD.RAW");
  if (notefreq.available())
  {
    Serial.println("test");
  }
  if(!playRaw.isPlaying())
  {
    playRaw.stop();
    mode = 0;
  }
}
 
Your code can't possibly work.

You're calling the Detecting() function only when the button changes. In that function, you start playing the data, and then you immediately call notefreq.available() only once, before the audio library has taken time to play the file and before the notefreq object has had an opportunity to analyze the sound. At that moment, there can't possibly be any analysis results available!
 
Oh yes of course, thanks! So I have to put it in a loop. Try this when my solve my hardware problem. Recording doesn't work anymore.
 
Status
Not open for further replies.
Back
Top