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

portada.jpeg

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

  1. Arduino UNO R4 Minima (used in this project)
  2. Any compatible Arduino board can also be used (UNO, Nano, Mega, etc.)

Microphone module

  1. MAX9814 microphone module (used in this project)
  2. KY-038 module (also compatible)

Resistors

  1. 5 × 330 Ω resistors
  2. 220 Ω resistors can also be used as an alternative

Jumper wires

  1. Male-to-male wires (for breadboard connections)

Breadboard

USB cable (for programming and power)

Wiring the Microphone

conexion_mic_arduino.png

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

conexión_led_arduino.png

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:

  1. A high value indicates the sound is resonating close to A = 440 Hz.
  2. 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