Arduino Wind Instrument Tuner 440Hz Using Goertzel Algorithm
by vegueromeralcarlos in Circuits > Arduino
16 Views, 0 Favorites, 0 Comments
Arduino Wind Instrument Tuner 440Hz Using Goertzel Algorithm
My name is Carlos Vegue Romeral, a Sound and Image Engineering student at the University of Málaga.
In this project, I built a digital tuner for wind instruments using an Arduino and the Goertzel algorithm.
The system is designed to detect a reference pitch of A = 440 Hz, helping musicians quickly verify whether their instrument is in tune.
Instead of using a full FFT, this project uses the Goertzel Transform, which is computationally efficient and ideal for real-time frequency detection on microcontrollers.
This project is suitable for students, makers, and audio enthusiasts who want to learn about digital signal processing and audio analysis using low-cost hardware.
Supplies
Arduino board
- Arduino UNO R4 Minima (used in this project)
- Any compatible Arduino board can also be used (UNO, Nano, Mega, etc.)
Microphone module
- MAX9814 microphone module (used in this project)
- KY-038 module (also compatible)
Resistors
- 5 × 330 Ω resistors
- 220 Ω resistors can also be used as an alternative
Jumper wires
- Male-to-male wires (for breadboard connections)
Breadboard
USB cable (for programming and power)
Wiring the Microphone
We begin by connecting the microphone module to the Arduino making three simple connections. First, connect the module's OUT pin to the Arduino's Analog Pin A0. (This pin is specifically designed to read the varying voltage that represents the audio signal.) Next, provide power by connecting the module's VCC pin to the Arduino's 3.3V output. (Using 3.3V instead of 5V is recommended as it typically results in a cleaner, less noisy signal for audio sensing.) Finally, establish a common electrical reference by connecting the module's GND pin to any of the Arduino's GND pins.
Wiring the LED
We will connect a status LED to the Arduino. This is a simple but essential circuit that requires a current-limiting resistor to prevent damage to the LED and the Arduino's output pin. Start by connecting the Digital Pin you assigned to the LED in your code to one leg of the current-limiting resistor. Connect the other leg of this resistor directly to the anode (the positive, longer leg) of the LED. Finally, complete the circuit by connecting the cathode (the negative, shorter leg) of the LED directly to the Arduino's GND.
First Test - Detecting 440Hz Only
In this first step, the objective is to verify that the Arduino is capable of detecting a 440 Hz tone using the Goertzel algorithm.
Additionally, this step allows us to understand how the Goertzel Transform works in practice, by measuring how strongly a specific frequency is present in the input signal.
Instead of identifying many frequencies, the algorithm is configured to "listen" only to A = 440 Hz and output a magnitude value that represents how much the input signal resonates at that frequency.
A high magnitude value means the input sound contains a strong 440 Hz component, while a low value means that frequency is not present.
const int ANALOG_INPUT = A0;
const int SAMPLE_RATE_HZ = 3000;
const int SAMPLE_PERIOD_US = 1000000 / SAMPLE_RATE_HZ;
const int F_HZ = 440;
const int N = 3000;
const int K = 440; // Frequency bin corresponding to 440 Hz
unsigned long tPrev;
struct goertzelFilter {
float s1, s2; // Internal filter states
int k, n;
float sinv, cosv, cosv2;
int nextIteration;
};
struct goertzelFilter filter;
void goertzelFilterReset(struct goertzelFilter &f) {
f.s1 = 0;
f.s2 = 0;
f.nextIteration = 0;
}
void goertzelFilterInit(struct goertzelFilter &f, int k, int n) {
f.k = k;
f.n = n;
f.cosv = cos(2 * PI * k / n); // Cosine coefficient for target frequency
f.sinv = sin(2 * PI * k / n); // Sine coefficient for target frequency
f.cosv2 = 2 * f.cosv;
goertzelFilterReset(f);
}
boolean goertzelFilterFinished(struct goertzelFilter &f) {
return (f.nextIteration == (f.n + 1));
}
void goertzelFilterRun(struct goertzelFilter &f, float input) {
// Goertzel difference equation (core algorithm)
float s = input + (f.cosv2 * f.s1) - f.s2;
f.s2 = f.s1;
f.s1 = s;
f.nextIteration++;
}
float goertzelFilterGetMagnitude(struct goertzelFilter &f) {
// Output magnitude: indicates how strong 440 Hz is in signal
float real = f.s1 - (f.cosv * f.s2);
float imag = f.sinv * f.s2;
return ((real * real) + (imag * imag)); // Squared magnitude
}
void setup() {
Serial.begin(9600);
tPrev = micros();
goertzelFilterInit(filter, K, N);
goertzelFilterReset(filter);
}
void loop() {
unsigned long t = micros();
if ((t - tPrev) >= SAMPLE_PERIOD_US) {
int v = analogRead(ANALOG_INPUT);
if (goertzelFilterFinished(filter)) {
float magnitude = goertzelFilterGetMagnitude(filter);
Serial.println(magnitude); // Print resonance at 440 Hz
goertzelFilterReset(filter);
}
else
// Normalize input signal around zero
goertzelFilterRun(filter, ((float) v - 512) / 512);
tPrev = t;
}
}
The output value shown in the Serial Monitor is the magnitude at 440 Hz.
This number tells how strongly the input signal contains that frequency:
- A high value indicates the sound is resonating close to A = 440 Hz.
- A low value means that frequency is weak or absent.
At this stage, the system does not yet act as a tuner — it only measures how present 440 Hz is in the signal.
Complete Tuner Using Multiple Goertzel Filters
In this step, the project becomes a complete digital tuner. Instead of detecting only one frequency, the system is now able to analyze several frequencies around A = 440 Hz simultaneously and determine which one is dominant in the input signal.
A group of Goertzel filters in paralell is used, one filter for each frequency. Each filter acts as a digital resonator that measures how strongly its target frequency is present in the sound captured by the microphone. After processing a full block of samples, the system compares the magnitude values obtained from all filters and selects the frequency with the highest result.
Based on this result, LEDs provide visual feedback to the musician. If the detected frequency is far below 440 Hz, a red LED indicates that the note is too low. As the frequency approaches the correct pitch, different LEDs are activated. When the frequency is between 439 Hz and 441 Hz, the green LED turns on, showing that the instrument is in tune. Frequencies above this range light up other LEDs indicating that the note is too high.
This tuning method is extremely efficient because it does not require computing a complete FFT. Instead, it focuses only on the frequencies of interest. This reduces computational load and makes the system suitable for real-time operation even on simple microcontrollers like Arduino.
Below is the full program that implements the tuner:
const int ANALOG_INPUT = A0;
const int SAMPLE_RATE_HZ = 3000;
const int SAMPLE_PERIOD_US = 1000000 / SAMPLE_RATE_HZ;
const int N = 3000;
const int NUM_FREQS = 7;
// Frequencies to detect
int frecuencias[NUM_FREQS] = {430, 435, 438, 440, 442, 445, 450};
// LED pins
const int LED_ROJO2 = 2; // Very low (<435 Hz)
const int LED_ROJO1 = 3; // Low (435-439 Hz)
const int LED_VERDE = 4; // In tune (439-441 Hz)
const int LED_ROJO3 = 5; // Sharp (441-445 Hz)
const int LED_ROJO4 = 6; // Very sharp (>445 Hz)
unsigned long tPrev;
struct goertzelFilter {
float s1, s2;
int k, n;
float sinv, cosv, cosv2;
int nextIteration;
};
// One filter per frequency
struct goertzelFilter filters[NUM_FREQS];
void goertzelFilterReset(struct goertzelFilter &f) {
f.s1 = 0;
f.s2 = 0;
f.nextIteration = 0;
}
void goertzelFilterInit(struct goertzelFilter &f, int k, int n) {
f.k = k;
f.n = n;
f.cosv = cos(2 * PI * k / n); // Target frequency coefficient
f.sinv = sin(2 * PI * k / n);
f.cosv2 = 2 * f.cosv;
goertzelFilterReset(f);
}
boolean goertzelFilterFinished(struct goertzelFilter &f) {
return (f.nextIteration == (f.n + 1));
}
void goertzelFilterRun(struct goertzelFilter &f, float input) {
float s = input + (f.cosv2 * f.s1) - f.s2; // Core Goertzel equation
f.s2 = f.s1;
f.s1 = s;
f.nextIteration++;
}
// Compute resonance strength
float goertzelFilterGetMagnitude(struct goertzelFilter &f) {
float real = f.s1 - (f.cosv * f.s2);
float imag = f.sinv * f.s2;
return ((real * real) + (imag * imag)); // Magnitude squared
}
void apagarTodosLEDs() {
digitalWrite(LED_ROJO2, LOW);
digitalWrite(LED_ROJO1, LOW);
digitalWrite(LED_VERDE, LOW);
digitalWrite(LED_ROJO3, LOW);
digitalWrite(LED_ROJO4, LOW);
}
// LED logic depending on detected frequency
void encenderLEDSegunFrecuencia(int freq) {
apagarTodosLEDs();
if (freq < 435) {
digitalWrite(LED_ROJO2, HIGH);
}
else if (freq >= 435 && freq < 439) {
digitalWrite(LED_ROJO1, HIGH);
}
else if (freq >= 439 && freq <= 441) {
digitalWrite(LED_VERDE, HIGH);
}
else if (freq > 441 && freq <= 445) {
digitalWrite(LED_ROJO3, HIGH);
}
else if (freq > 445) {
digitalWrite(LED_ROJO4, HIGH);
}
}
void setup() {
Serial.begin(9600);
// Configure LEDs as outputs
pinMode(LED_ROJO2, OUTPUT);
pinMode(LED_ROJO1, OUTPUT);
pinMode(LED_VERDE, OUTPUT);
pinMode(LED_ROJO3, OUTPUT);
pinMode(LED_ROJO4, OUTPUT);
apagarTodosLEDs();
tPrev = micros();
// Initialize all Goertzel filters
for(int i = 0; i < NUM_FREQS; i++) {
goertzelFilterInit(filters[i], frecuencias[i], N);
goertzelFilterReset(filters[i]);
}
}
void loop() {
unsigned long t = micros();
if ((t - tPrev) >= SAMPLE_PERIOD_US) {
int v = analogRead(ANALOG_INPUT);
float input = ((float)v - 512) / 512; // Center and normalize signal
// Run all filters in parallel
for(int i = 0; i < NUM_FREQS; i++) {
goertzelFilterRun(filters[i], input);
}
// When one filter finishes, all others have finished too
if (goertzelFilterFinished(filters[0])) {
float maxMagnitude = 0;
int detectedFreq = frecuencias[0];
// Find strongest frequency
for(int i = 0; i < NUM_FREQS; i++) {
float mag = goertzelFilterGetMagnitude(filters[i]);
if(mag > maxMagnitude) {
maxMagnitude = mag;
detectedFreq = frecuencias[i];
}
}
// Activate corresponding LED
encenderLEDSegunFrecuencia(detectedFreq);
// Optional serial output
Serial.print("Detected frequency: ");
Serial.print(detectedFreq);
Serial.println(" Hz");
// Reset all filters for next window
for(int i = 0; i < NUM_FREQS; i++) {
goertzelFilterReset(filters[i]);
}
}
tPrev = t;
}
}
This method provides stable detection and avoids false readings caused by noise or harmonics, since the system evaluates only the specific frequencies relevant to tuning.
Demostration-In Tune (440Hz)
This video shows the tuner detecting a correctly tuned note at A = 440 Hz. The green LED indicates that the instrument is in tune.
Downloads
Demostration-Low Note
Here the instrument is below 440 Hz, and the tuner correctly identifies the pitch as flat.
Downloads
Demostration-Sharp Note
In this example, the note is slightly above 440 Hz, and the tuner shows the sharp condition using the red LED
Downloads
Final Text
This project demonstrates how a simple microcontroller can be used to implement frequency detection for wind instruments. It provides a practical introduction to digital signal processing on embedded systems, showing how theoretical concepts like frequency analysis can be applied on low-cost hardware