Introduction: Reliable Frequency Detection Using DSP Techniques
Accurate Frequency Detection is important for many projects such as Guitar/Piano Tuners, Vibration Analyzers, Heartrate Monitors, MEMs Sensor Analysis and Laboratory Instruments.
There have been many fine examples of projects that try to solve this problem, for example: Arduino Frequency Detection by amandaghassaei and Arduino Frequency Counter Library.
But they all use Time Domain techniques; analyzing the signal for features such as : Zero-Crossings, Peak Detection, Slope Detection etc..
Take a look at the Waveforms shown. One of them is recorded from a Piano playing Middle-C (C4) . The other is from a Synthesizer Playing Middle-C (C4). Clearly any good Time Domain algorithm will work well with the Piano waveform. But the Synthesizer waveform will not be identifiable that way because its very strong harmonic content makes the fundamental frequency undiscernable . It looks impossible to Identify the Frequency of this signal.
It is possible.
Using the technique I'm going to show you it was measured to be 259.91Hz ... only 0.09Hz away from an Exact Middle C Frequency of 260Hz.
UPDATE:
I got a lot of questions about how to use this to make a Guitar Tuner. SoI spent some time writing a tutorial on how to make a Guitar Tuner using this method. You can find it on my blog:
http://www.akellyirl.com/arduino-guitar-tuner/
========
By the way. If you like this Instructable, you might also like to read My Blog covering various projects and Tutorials.
========
Step 1: You Will Need
I used an Arduino because it will make a great basis for building a Frequency Detector with Analogue Input such as a Guitar Tuner or Heartrate Monitor.
But the principles apply to any platform.
To demonstrate the principles I'm going to use pre-recorded sound files captured as an array in a .h file. So we won't be needing any circuit for the Arduino this time.
Step 2: Autocorrelation
The trick we use to identify the frequency of a noisy signal is well known in the Mathematical World of Digital Signal Processing (DSP), and is based on some pretty fancy maths. But the technique is not difficult to understand and better still it's super-easy to code. The core of it is just 3 lines of code.
What we need to do is to change the original signal into another one that highlights the periodicity of the original signal. So if it is indeed periodic, then that will stand out in the new signal and then we can measure that in the usual way using peak-detect or zero crossing detect.
What's the magic algorithm that does that?
It's Autocorrelation.
Imagine your signal is contained in a window or buffer. Now image you have an exact copy of that window or buffer with a time delay.
What Autocorrelation does is to measure the correlation (or similarity) between the signal and its delayed copy each time the copy is delayed by a sample period.
See the diagram. When the signal and the copy have no delay they are very similar (i.e. highly correlated) as shown in step 1, and therefore the autocorrelation value for delay = 0 is maximum.
Step 2 shows that when the copy is delayed significantly it doesn't look similar to the original in the overlapping area. Therefore the autocorrelation value for this delay is small.
Step 3 shows that when the copy is delayed even more the signal in the overlapping area is very similar to the original because the signal is periodic. Therefore the autocorrelation value for this delay shows a peak.
We can see that the distance in time between the maximum peak at the beginning and the first peak afterwards must be equal to the fundamental period of the waveform.
Now that we've emphasised the periodicity of the signal by Autocorrelation we just need to perform a Peak-Detect to measure the period.
Technically the "similarity" or correlation between the signal and its delayed copy is the sum of the product of the two signals.
For the technically minded all the details of Autocorrelation can be found here: http://en.wikipedia.org/wiki/Autocorrelation
Step 3: Autocorrleation Code
for(i=0; i < len; i++) { sum = 0; for(k=0; k < len-i; k++) sum += (rawData[k]-128)*(rawData[k+i]-128)/256; }The data is in the rawData[] array. We subtract 128 from each value because it's 8bit unsigned and we require signed values.
The sum value is the result of each autocorrelation calculation, i.e. each point of the function. In order to save memory we don't save the output to an array. We're going to work on the individual sum values to find the first peak and therefore calculate the period.
Sending the sum values out to be plotted, we get the Autocorrelation function shown. Comparing to the original signal (also shown) it is clear that there is periodicity in the original signal and this has been clearly highlighted by the Autocorrelation function.
Step 4: Peak Detect
// Peak Detect State Machine if (pd_state == 2 && (sum-sum_old) <=0) { period = i; pd_state = 3; } if (pd_state == 1 && (sum > thresh) && (sum-sum_old) > 0) pd_state = 2; if (!i && pd_state == 0) { thresh = sum * 0.5; pd_state = 1; }The state machine moves from one state to the next when an event occurs as follows:
STATE0 : Set thresh the threshold under which value we'll ignore the data : NEW STATE = 1
STATE1 : look for the signal being above the threshold AND the slope of the signal is positive : NEW STATE = 2
STATE2 : look for the slope of the signal is negative or zero. If so we've found the PEAK! : NEW STATE = 3
Step 5: Here's All of the Code
#include "C4.h" // Sample Frequency in Hz const float sample_freq = 22050; int len = sizeof(rawData); int i,k; long sum, sum_old; int thresh = 0; float freq_per = 0; byte pd_state = 0; void <b>setup</b>() { <b>Serial</b>.begin(115200); sum = 0; pd_state = 0; int period = 0; // Autocorrelation for(i=0; i < len; i++) { sum_old = sum; sum = 0; for(k=0; k < len-i; k++) sum += (rawData[k]-128)*(rawData[k+i]-128)/256; // Peak Detect State Machine if (pd_state == 2 && (sum-sum_old) <=0) { period = i; pd_state = 3; } if (pd_state == 1 && (sum > thresh) && (sum-sum_old) > 0) pd_state = 2; if (!i) { thresh = sum * 0.5; pd_state = 1; } } // Frequency identified in Hz freq_per = sample_freq/period; <b>Serial</b>.println(freq_per); } void <b>loop</b>() { // put your main code here, to run repeatedly: }
Step 6: Files
The C4.h File contains the buffer we're analysing.
You can fill that by reading a Block of Data from the ADC in the Arduino. Or you can generate the data from a program such as Audacity.
Here's a link to the Files including several .h files containing Audio samples you can try out.
The samples were taken from the extensive collection captured by the University of Iowa Electronic Music Studios.

Participated in the
Microcontroller Contest

Participated in the
Build My Lab Contest
22 Comments
Question 6 weeks ago
Did someone know how I can create an .h file from a Audio file because I can't figure out how to do it ? Thank
10 months ago
Hi, I don't understand why, when changing 8 bit unsigned to signed, you subtract 128. Can you give an example or perhaps elaborate more..?
Reply 10 months ago
8 bit unsigned numbers range from 0 to 255 in decimal or 0000 0000 to 1111 1111 in binary. In 8 bit signed, numbers range from -128 to 127 where -128 is 1000 0000 and 127 is 0111 1111 . So to convert from unsigned to signed you need to subtract 128 so 0 becomes -128 and 255 becomes 127. Hope that helps.
Question 1 year ago
I tried using this program for autocorrelation of audio signals in real time. Whenever i am feeding a input of greater than 4khz, i am unable to get a reliable output. Could you help meunderstand the reason why?
I used the same hardware used in this project for data aquisition
https://create.arduino.cc/projecthub/shajeeb/32-band-audio-spectrum-visualizer-analyzer-902f51
Question 3 years ago
Hi, the arduino can sample the ADC at only about 9kHz, which gives much less precise than with your examples. It has an accuracy of +-25Hz when measuring a signal of about 700Hz, since the wave period is only 13+-1 samples (9000Hz/700Hz). Is it feasible to improve this program so it takes into account not just one but all the peaks in the autocorrelation output for grater accuracy? (measuring and averaging the period of 10 consecutive waves is more accurate than just measuring one)
5 years ago
if you dont mind, would you explain to me about the autocorrelation algorithm in detail ? i want to use that algorithm in my project but i can understand it clearly
7 years ago
Akellyirl,
Thank you for posting this tutorial on beat detection. It is detailed and informative as well as much appreciated. I have been working for a few months now on getting a solid audio input into the arduino to use this code. the audio is oscillating on a 2.5v line from 0v to 5v with about 2% noise at the moment. So after accomplishing this I wanted to see if I could use this code to analyze audio inputs freq. I put rawData to A0 but get an error on the line "for(k=0; k<len-i;k++) sum+=(rawData[k]...". I attempted assigning raw data as an unsigned char and an int.
can this code be adapted to analyze audio coming in at 0 to 5v with a 2.5v midline?
Thank you for your time,
Rex
Reply 6 years ago
Hi Rex, did you ever figure this out? I'm trying to do the same thing right now.
Reply 6 years ago
No not with this set up. I got it working with the teensy board and audio sheild.
Reply 6 years ago
What you need to do is fill the rawData [] array with data from the ADC. You can use a for loop for that. You should also subtract the mean of the data so that it's centred at zero.
Reply 6 years ago
So is this done all at one time in the loop() function? I thought the ADC only returns one int value at a time. How do I fill an array of 970 values with one value from ADC?
Reply 6 years ago
I spent some time writing a tutorial on how to make a Guitar Tuner using this method. You can find it on my blog:
http://www.akellyirl.com/arduino-guitar-tuner/
Reply 7 years ago
This should work OK. I'd need more info on the error. Make sure the array is defined an unsigned char e.g. unsigned char rawData[970] = { };
Reply 6 years ago
akellyirl,
Could you please tell me how can I assign the input value of A0 to the array rawData?
Reply 6 years ago
I spent some time writing a tutorial on how to make a Guitar Tuner using this method. You can find it on my blog:
http://www.akellyirl.com/arduino-guitar-tuner/
Reply 6 years ago
Not sure how much programming knowledge I should assume you have. I'm no pro myself, but I've fiddled a bit. For loops are quite handy for meddling with values inside arrays, but it gets quite process-intensive for such large arrays and there's a lot of "index out of bounds" mistakes. For smaller ones you might do something like
for(int i=1; i < (size of your array); i++){ //note the 1 starting value
val[i-1] = val[i] //this is standard form, shifting all values down one seat
}//after the for loop, you put your reading in the last seat, so:
val[size-1] = analogRead(pin); //this is the last position in the array. The index of the last seat of an array size n is n-1.
With such massive arrays it might be clever to work out some kind of trailing, self-following method, where you don't have to shift everything around for each new assignment. So, you'd just write into the array starting at 0 and going to (size-1) and then start over, and each time you'd need to do some calculations from the numbers you'd go *from* your current position to the end, and then from the start and *to* your current position? Definitely possible with nested for loops, although I haven't worked out the details. Maybe there's some built-in way to do this more smoothly.
6 years ago
I spent some time writing a tutorial on how to make a Guitar Tuner using this method. You can find it on my blog:
http://www.akellyirl.com/arduino-guitar-tuner/
7 years ago
Have you tried to subtract original and shifted signals instead of multiplying them? This will be another method to detect pitch and probably would give better results.
9 years ago on Introduction
Nice. This is a good tool and your explanation is clear.
I suggest you think about how to use this in real world applications.
Maybe a microphone, and a digital display of the primary note and the 2ndary harmonics,
maybe to help people talk and sing in key ... :)
Reply 9 years ago on Introduction
Thanks.
This has been used in a real world application to measure Heart Rate using Pulse Oximetry methods. It works really well. Maybe I'll post the project sometime.