Tony Chai  
 
   
     
 
 

 

Guitar Tuner

Designed by Tony Chai and John Yi

 

This project entails creating a full-functioning automatic guitar tuner. It is capable of tuning guitar string pegs automatically to tune a guitar to a variety of tunings. This provides a remedy for two types of guitarists: one is for the professional, who may not be able to tune his strings by ear in a loud environment and wants his strings tuned instantly. The other is for the novice, who may not have the trained relative pitch necessary to tune the guitar himself. We also aimed to make this device affordable, so that the potential users would not be intimidated by the price point for being able to tune his guitar conveniently.

The tuner is manually attached to the target guitar peg. The guitarist will use a button to select which string he wants to tune, and then pluck the according string for the tuner to correct the guitar tuning. If the guitar is in tune, a green LED will light up indicating that it is in tune. If it is either sharp (too high) or flat (too low), the tuner will light up the corresponding LED and twist the guitar pegs accordingly until it is in tune.

 

 

High-Level Project Block Diagram

 

 

Mechanical Engineering

We are using a continuous servo motor to wind the guitar pegs. The DC motor provides enough torque to wind a tuning peg if all the power from the Arduino is used for it. Thus, while the motor is turning, the rest of the circuit is temporarily disabled because the code is only focused on turning the motor in the loop. If we had the entire circuit working simultaneously with the motor turning, the Arduino wouldn’t have enough power to turn the guitar pegs with the appropriate torque as the USB port provides only 5 volts.

A: Hole diameter for original servo piece: 3/8 inches
B: Length of attachment: 11/8 inches
C: Width of space for peg: 7/16 inches
D: Height of attachment inside: 5/8 inches
E: Thickness of attachment: 1/16 inches

 

 

Electrical Engineering

We are sending a direct signal from an electric guitar’s sound jack, amplifying it through an op-amp with a signal filter, and sending it to the ATmega128. The microcontroller is connected to a servo motor and will rotate the motor depending on the user selection using a button and 6 LEDs (each LED resembles its corresponding string). As you can see in our op-amp circuit, we have a gain of 57 [1 + (56k/1k)] to bring up the voltage to around 5V for the Arduino to read properly. We also added diodes as a fail-safe for the circuit.

Op-Amp:

 

 

Software (Programming)

We are translating the analog signal into a digital signal that will control the rate and direction of each motor using our Arduino board. Within our Arduino code, we are filtering out the overtones of each string using a Butterworth filter to isolate the fundamental frequency of the guitar signal. We count how many times the sinusoidal signal crosses a voltage threshold on the y-axis for a period of time on the x-axis in a function. We call this function with the Atmega’s internal timer to get a consistent period for sampling.

After the string input has been idle (meaning the cross counts have been below threshold for a period of time), we then take the average of a few values for cross counts that were within the boundaries of the string (eliminating further possibility of higher frequencies altering our averages) to check if the string is in tune. We found the “in tune” averages and stored them in the Arduino. The microprocessor compares the computed average from the string input to the “in tune” average for each string to determine if it is in pitch.

If the string is in tune, the Arduino will only light up the corresponding LED. If it is out of tune, the Arduino will not only light up the corresponding LED, it will send a PWM signal to the servo motor to turn it either direction depending on if the string is too high or low.

The code also considers the user input from the button, changing the variables for pitch detection depending on the selected string (and lighting up the corresponding display LED)

 

Code (Press + to expand code box)

Guitar Code [-]

/*
* Magic_Tune
*
* This program will receive an amplified analog signal, compare the pitch, and control the servo motor while displaying output LEDs
*/

//Libraries required for servo control and usage of the Atmega’s internal timer
#include
#include "WProgram.h"

//Global variables to be used
int x;
int y;
int lastx;
int lasty;
long timer;
int idle_timer;
int threshold;
int cross_count;
int in_tune;
int average_val;
int pitch_diff;

int upper_bound;
int lower_bound;
int avg_cross;
int avg_counter;
int avg_upper;
int avg_lower;
float a, b;

int timer_divide;
int divide_by;

int string_select;
int select_pin_val;

int analogPin = 0; // analog to digital pin for signal input

int led_high = 13; // this LED will show user if a string's pitch is too high
int led_ok = 12; // this LED will show user if a string's pitch is correct
int led_low = 11; // this LED will show user if a string's pitch is too low

// these pins will light up to show which string the Atmega is comparing values for
int led_e4 = 10;
int led_b3 = 9;
int led_g3 = 8;
int led_d3 = 7;
int led_a2 = 6;
int led_e2 = 5;

int button_pin = 3; // input pin for user using a button to switch strings
int servoPin = 2; // control pin for servo motor

void setup()
{
// Set up timer 1 to generate an interrupt every 1 microsecond
TCCR1A = 0x00;
TCCR1B = (_BV(WGM12) | _BV(CS12));
OCR1A = .071;
TIMSK1 = _BV(OCIE1A);

x = 0;
lastx = 0;
y = 0;
lasty = 0;

timer = 0;
cross_count = 0;
avg_cross = 0;
avg_counter = 0;
string_select = 0;

//Set the input and output pins
pinMode(button_pin, INPUT);
pinMode(servoPin, OUTPUT);
pinMode(led_high, OUTPUT);
pinMode(led_ok, OUTPUT);
pinMode(led_low, OUTPUT);
pinMode(led_e4, OUTPUT);
pinMode(led_b3, OUTPUT);
pinMode(led_g3, OUTPUT);
pinMode(led_d3, OUTPUT);
pinMode(led_a2, OUTPUT);
pinMode(led_e2, OUTPUT);

Serial.begin(9600); // Opens serial port, sets data rate to 9600 bps
}

// Nothing is done in the Arduino loop, since timing is off.
void loop()
{
}

// Timer function running every microsecond
ISR(TIMER1_COMPA_vect)
{
timer++;
idle_timer++;

// Read button press to determine which string is to be detected
if (timer % 100 == 0)
{
select_pin_val = digitalRead(button_pin);
if (select_pin_val == HIGH)
{
string_select = ((string_select + 1) % 6);
Serial.print("string: ");
Serial.println(string_select);
}
}


// Depending on which string is selected, the proper variables are set
switch (string_select)
{
case 0:
digitalWrite(led_e4, LOW); // sets the proper LED on, all else off
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, HIGH);
a = 0.045;
b = 0.9099;
threshold = 150;
upper_bound = 77;
lower_bound = 33;
avg_upper = 57;
in_tune = 55; // This is the “in tune” average of cross counts for the string.
avg_lower = 53;
timer_divide= 2000;
divide_by = 3;
break;
case 1:
digitalWrite(led_e4, LOW); // sets the proper LED on, all else off
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, HIGH);
digitalWrite(led_e2, LOW);
a = 0.0592;
b = 0.8816;
threshold = 150;
upper_bound = 88;
lower_bound = 44;
avg_upper = 67;
in_tune = 65; // This is the “in tune” average of cross counts for the string.
avg_lower = 63;
timer_divide = 2000;
divide_by = 3;
break;
case 2:
digitalWrite(led_e4, LOW); // sets the proper LED on, all else off
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, HIGH);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, LOW);
a = 0.0797;
b = 0.8406;
threshold = 150;
upper_bound = 117;
lower_bound = 63;
avg_upper = 97;
in_tune = 95; // This is the “in tune” average of cross counts for the string.
avg_lower = 93;
timer_divide = 2000;
divide_by = 3;
break;
case 3:
digitalWrite(led_e4, LOW); // sets the proper LED on, all else off
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, HIGH);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, LOW);
a = 0.0730;
b = 0.8541;
threshold = 130;
upper_bound = 50;
lower_bound = 15;
avg_upper = 29;
in_tune = 27; // This is the “in tune” average of cross counts for the string.
avg_lower = 26;
timer_divide = 500;
divide_by = 4;
break;
case 4:
digitalWrite(led_e4, LOW); // sets the proper LED on, all else off
digitalWrite(led_b3, HIGH);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, LOW);
a = 0.1270;
b = 0.7459;
threshold = 140;
upper_bound = 50;
lower_bound = 15;
avg_upper = 35;
in_tune = 34; // This is the “in tune” average of cross counts for the string.
avg_lower = 33;
timer_divide = 500;
divide_by = 4;
break;
case 5:
digitalWrite(led_e4, HIGH); // sets the proper LED on, all else off
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, LOW);
a = 0.1648;
b = 0.6705;
threshold = 150;
upper_bound = 60;
lower_bound = 20;
avg_upper = 47;
in_tune = 45; // This is the “in tune” average of cross counts for the string.
avg_lower = 43;
timer_divide = 500;
divide_by = 4;
break;
}

check_crossings();

// After the string input has been idle for a while, we take the average of a number of cross counts that were in bound.
if (idle_timer == 10000)
{
Serial.println("AVG AVG LOOK HERE AVG AVG");
average_val = avg_cross / divide_by;
Serial.println(average_val);

// If else statements for tuner lights
if ((average_val < avg_lower) && (average_val > 0))
{
// Turn off all string display lights to conserve power
digitalWrite(led_e4, LOW);
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, LOW);

// Sets the proper tuning LED on, all else off
digitalWrite(led_high, LOW);
digitalWrite(led_ok, LOW);
digitalWrite(led_low, HIGH);

pitch_diff = in_tune - average_val;
Serial.print("Pitch Difference Low: ");
Serial.println(pitch_diff);

// If the tuning is off by a questionably high amount, count it as an error in reading and do not turn the peg.
// Otherwise tune the peg for a period of time. This time depends on how far off the read average is.
if (pitch_diff < 20)
for(long i = 0; i < pitch_diff * 36000; i++)
{
digitalWrite(servoPin, HIGH); // start the pulse
delayMicroseconds(15); // pulse width
digitalWrite(servoPin, LOW); // stop the pulse
}
}

// Don't turn the peg if the guitar is in tune.
else if ((average_val >= avg_lower && average_val <= avg_upper) || (average_val == 0))
{
// Sets the proper tuning LED on, all else off
digitalWrite(led_high, LOW);
digitalWrite(led_ok, HIGH);
digitalWrite(led_low, LOW);
}

else if (average_val > avg_upper)
{
// Turn off all string display lights to conserve power
digitalWrite(led_e4, LOW);
digitalWrite(led_b3, LOW);
digitalWrite(led_g3, LOW);
digitalWrite(led_d3, LOW);
digitalWrite(led_a2, LOW);
digitalWrite(led_e2, LOW);

// Sets the proper tuning LED on, all else off
digitalWrite(led_high, HIGH);
digitalWrite(led_ok, LOW);
digitalWrite(led_low, LOW);

pitch_diff = average_val - in_tune;
Serial.print("Pitch Difference High: ");
Serial.println(pitch_diff);

// If the tuning is off by a questionably high amount, count it as an error in reading and do not turn the peg.
// Otherwise tune the peg for a period of time. This time depends on how far off the read average is.
if (pitch_diff < 20)
for(long i = 0; i < pitch_diff * 270000; i++)
{
digitalWrite(servoPin, HIGH); // start the pulse
delayMicroseconds(2); // pulse width
digitalWrite(servoPin, LOW); // stop the pulse
}
}

// Reset all variables used for pitch detection
cross_count = 0;
avg_cross = 0;
avg_counter = 0;
}

// We take the average of cross counts after the first value in bound. We dismiss the first value since it is usually inaccurate
// for finding a good average.
if (timer % timer_divide == 0){
if (cross_count > lower_bound && cross_count < upper_bound) {
if (avg_counter >= 1 && avg_counter < (divide_by + 1)) {
avg_cross = avg_cross + cross_count;
Serial.print("Runnin Avg cross sum: ");
Serial.println(avg_cross);
}
avg_counter++;
}
Serial.print("cross_count: ");
Serial.println(cross_count);
cross_count = 0;
}
}

void check_crossings()
{
lastx = x;
lasty = y;
x = analogRead(analogPin); // Read the input pin
y = a * x + a * lastx + b * lasty; // Apply Butterworth filter to eliminate high frequencies

// If the string crosses it's set threshold, add it to the count. If there are no crossings, the idle timer will begin to run.
if (lasty > threshold and y < threshold)
{
cross_count++;
idle_timer = 0;
}
}

 

 

Design Description


(1) Servo Motor - This motor uses the pitch detection code from the Arduino to wind the peg to its proper frequency.

(2) Servo Motor Attachment - This is a plastic piece that attaches to the servo motor by fitting onto a smaller plastic piece already installed on the servo motor and fits on the other side to a guitar peg, so that the servo motor can effectively tune the guitar string.

(3) Op-Amp- The op-amp amplifies the guitar signal to a readable signal (around 5V) for the Arduino to determine the pitch.

(4) Pitch detector (Arduino) - The Arduino reads in the amplified signal from the op-amp circuit, then counts how many times the signal crosses a certain threshold to determine the frequency. Depending on which string is selected, it will send a PWM signal to the servo motor to tune the peg to its proper pitch.

(5) Selector button - This will set the variables for the pitch detector, so that the user can choose which string he wants to tune. Each time the button is pressed, the next string will be chosen, shown in the LED Array.

(6) LED Array - These LED's provides information to the user to show which string is being tuned [6 LEDs], and as to whether it is being tuned up or down by the Arduino [3 LEDs].

 

 

Cost

Part Number of units Total Cost
Arduino Diecimila 1 $35
Servo Motor (Continuous) 1 $30
Servo Motor Attachment 1 <$1
Operational Amplifier (ECG891N) 1 <$1
LEDs 9 <$1
Pushbutton 1 <$1
USB Oscilloscope 1 $140
Capacitors (3.3uF) 1 <$1
Resistors (56k, 22k, 1k) 4 <$1
Cumulative Total Cost   ~$211

 

 

Conclusion

By the end of the project, we had created an automatic tuner that was mostly accurate for most strings and could tune a string by itself within one or two windings. We were also pleased to find that our two biggest hurdles (signal amplification and pitch detection) were solved with time to spare to allow us to focus on the other components (LEDs and servo control). We were also able to create the tuner using relatively inexpensive parts, as the tuner itself could be made for only around $70.