Automatic Guitar Tuner
To spare myself from the 5 minute process of tuning my guitar, I've spent 15 hours developing a device to do it for me! Using a microphone, esp32 microcontroller, dc motor and driver, and a power supply, this project will listen to your guitar and adjust the tuning accordingly with relatively high accuracy.
My original intention for this project was to design a mechanism that would allow all 6 strings to be tuned, but due to issues with my design this project will only tune 2 strings at a time. It's possible to add more motors, but I didn't have the hardware on hand for this solution.
Supplies
Note: The exact motors I used are no longer manufactured and will have to be substituted for a similar geared motor. I was not able to find a motor with the same shaft dimensions.
Materials:
- ESP32c3 (or other microcontroller)
- MAX9814 Electret Microphone devboard
- TT Motor All-Metal Gearbox x2
- L298N H-Bridge Motor Driver
- Mini breadboard
- Jumper wires
- 9V power supply
- 3d printed tuning peg adapter x2
Tuning Peg Adapter
In order to attach the motor to your guitar, you will have to create an adapter. If you have access to a 3D printer, it's the easiest way to do this. I designed the part in Onshape and printed it in PLA. The exact dimensions of the part will depend on your guitar and the motor you use.
Set Up Microphone
I suggest using a MAX9814 devboard, which you can find on Amazon, Adafruit, or Aliexpress. Purchasing this microphone solved all my problems, I plugged it in and it worked first try. You will have to solder on the pin headers before you can use it. You can find information online on how to solder the pin header.
There are other options for microphones, but this is by far the simplest option. From my own experience, building your own audio amplifier is not worth the effort (I couldn't get mine to work). Also, the commonly available sound pressure sensors, which have a potentiometer and both analog and digital output, didn't have a high enough signal to noise ratio for the analog output to be useful in my testing.
Upload Code
If you've never used an esp32 board with the Arduino IDE, there are several steps you need to go through to set it up. I recommend using this tutorial for the basic steps:
https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/
Once you've set up the IDE, you'll need to install the arduinoFFT library. Click the library icon on the left sidebar, and search for "arduinoFFT", then click install.
Once you've installed the library, create a new sketch and paste in the the code.
#include "arduinoFFT.h"
#define SAMPLES 4096
#define SAMPLING_FREQUENCY 4096
#define MOTORCW 7
#define MOTORCCW 8
#define MOTOR2CW 9
#define MOTOR2CCW 10
unsigned int samplingPeriod;
unsigned long microSeconds;
double vReal[SAMPLES];
double vImag[SAMPLES];
double stringFrequencies[6] = {330, 247, 196, 147, 110, 82};
double stringTolerances[6] = {0.5, 0.5, 0.5, 0.5, 0.5, 0.5};
double freqOffset = 0.6;
double stringMSPH[6] = {100, 100, 100, 100, 100, 100};
int stringIndex = 1;
bool doneTuning = false;
int serialCommand = 0;
double serialValue = 0;
int lastDiff = 9999;
void setup()
{
pinMode(2, INPUT);
pinMode(MOTORCW, OUTPUT);
pinMode(MOTORCCW, OUTPUT);
pinMode(MOTOR2CW, OUTPUT);
pinMode(MOTOR2CCW, OUTPUT);
analogWrite(MOTORCW, 0);
analogWrite(MOTORCCW, 0);
analogWrite(MOTOR2CW, 0);
analogWrite(MOTOR2CCW, 0);
Serial.begin(115200);
samplingPeriod = round(1000000*(1.0/SAMPLING_FREQUENCY));
}
void loop()
{
Serial.println("Starting sample");
for(int i=0; i<SAMPLES; i++)
{
microSeconds = micros();
vReal[i] = analogRead(2);
vImag[i] = 0;
while(micros() < (microSeconds + samplingPeriod))
{
}
}
Serial.println("Finished sampling");
analogWrite(MOTORCW, 0);
analogWrite(MOTORCCW, 0);
analogWrite(MOTOR2CW, 0);
analogWrite(MOTOR2CCW, 0);
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQUENCY);
FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(FFT_FORWARD);
FFT.ComplexToMagnitude();
double peak = FFT.MajorPeak();
Serial.println(peak);
double divided = peak / round(peak/stringFrequencies[stringIndex]);
double z = divided - stringFrequencies[stringIndex];
Serial.println(z);
double tolerance = stringTolerances[stringIndex];
double tunePeriod = stringMSPH[stringIndex];
int motorpincw = (stringIndex % 2 == 1) ? MOTORCW : MOTOR2CW;
int motorpinccw = (stringIndex % 2 == 1) ? MOTORCCW : MOTOR2CCW;
if (z<15 && z>-15 && lastDiff<15 && lastDiff>-15 && !doneTuning) {
if (z > tolerance) {
analogWrite(motorpincw, 128);
delay(tunePeriod * fabs(z));
analogWrite(motorpincw, 0);
} else if (z < (0 - tolerance)) {
analogWrite(motorpinccw, 128);
delay(tunePeriod * fabs(z));
analogWrite(motorpinccw, 0);
} else {
doneTuning = true;
}
}
lastDiff = z;
if (Serial.available() > 0) {
char receivedChar = Serial.read();
if (isAlpha(receivedChar)) {
serialCommand = receivedChar;
String serialValueString = Serial.readStringUntil('\n');
serialValue = serialValueString.toDouble();
}
}
if (serialCommand != 0) {
if (serialCommand == 's' && serialValue > 0 && serialValue < 7) {
stringIndex = (int)(serialValue - 1);
doneTuning = false;
Serial.print("Changed string to ");
Serial.print(serialValue);
Serial.print(" (");
Serial.print(stringFrequencies[stringIndex]);
Serial.println("Hz)");
} else if (serialCommand == 't' && serialValue > 0 && serialValue < 500) {
stringTolerances[stringIndex] = serialValue;
Serial.print("Changed string tolerance to +/- ");
Serial.print(stringTolerances[stringIndex]);
Serial.println("Hz");
} else if (serialCommand == 'p' && serialValue > 0 && serialValue < 1000) {
stringMSPH[stringIndex] = serialValue;
Serial.print("Changed string MSPH to ");
Serial.print(stringMSPH[stringIndex]);
Serial.println("ms");
} else {
Serial.println("Invalid command");
}
serialCommand = 0;
}
}
Assemble Parts
You will need to create the circuit as pictured. If the pin numbers on your microcontroller are different, you will need to change the code accordingly.
- Ensure the group of 3 screw terminals on the motor driver is facing left.
- The + voltage, ground, and 5-volt screw terminals are on the top, middle, and bottom, respectively.
- Connect the positive wire of a 9-volt power supply to the + screw terminal on the motor driver.
- Connect the negative wire of the 9-volt power supply and a jumper wire to the ground screw terminal on the motor driver. The other end of this jumper wire should connect to the 'G' pin on your microcontroller.
- Connect the 5-volt terminal on the motor driver and the '5V' pin on your ESP32 microcontroller.
- Connect all 4 input pins of the motor driver to pins 7-10 on the ESP32.
- Make sure both of the jumpers (next to the input pins) are present. If a jumper is missing, the motor on that side will not work.
- Connect each motor to one of the pairs of screw terminals on the top or bottom of the motor driver.
- Connect the 'Vdd' pin on the microphone to the '3.3' pin on the ESP32.
- Connect the 'GND' pin on the microphone to the 'G' pin on the ESP32.
- Connect the 'Out' pin on the microphone to pin 2 on the ESP32.
If the motors behave unexpectedly (wrong motor turning or turning in the wrong direction), adjust the pin numbers in the code. Switching the order of the pins for a motor will invert the direction.
Once the circuit is done, attach both motors to each other so they can stay in place on the guitar and install the tuning peg adaptors. Then place the motors on the guitar. If the microphone is too far from the guitar it will perform unreliably. Mine worked as expected when attached to a guitar stand which was touching the guitar.
Test the Project
Plug it in and test it! Be careful when first using it, since it's possible the motors will be strong enough to break a string. I was lucky enough to not find out. The user interface is done over serial. By default, the tuner will attempt to tune the high E string (string 1) when plugged in, and will disable itself once the frequency is within the desired range. Sending 's1' - 's6' over the serial monitor will change the string being tuned.