Introduction: Arduino Guitar Note Detector
This instructable will explain step-by-step how to build a device that detects which note is played on an acoustic guitar. The device accomplishes this task by using a microphone to read in the sound wave, audio amplifier and DC offset circuits to adjust the signal, an algorithm to determine the frequency, and a seven segment display and LEDs to display the note. This instructable was developed while referencing the following Instructables: Arduino Audio Input, Arduino Frequency Detection, and Arduino Guitar Tuner.
Parts List:
Amazon
- Arduino Uno & USB Cable
- Part #: LYSB01KFD3F0I-CMPTRACCS
- Jumper wires
- Part #: 32JWKZN
- Breadboard
- Part #: EBOOT-BREADBOARD-01
Radio Shack
- 5mm Green LED
- Catalog #: 2760022
- ¼-Inch Mono Inline Audio Jack
- Catalog #: 2740340
- Heavy-Duty 9V Snap Connectors (2)
- Catalog #: 2700324
- Uni-Directional Dynamic Microphone
- Catalog #: 3303038
- 9V Batteries (2)
- Catalog #: 2302211
Digi-Key
- J-FET Amplifier 2 Circuit
- Part #: 296-1780-5-ND
- 10k Ohm Gang Linear Panel Mount Potentiometer
- Part #: 987-1301-ND
- 100 Ohm ±5% 0.25W Through Hole Resistor (3)
- Part #: CF14JT100RTR-ND
- 47 Ohm ±5% 0.25W Through Hole Resistor (3)
- Part #: CF14JT47R0TR-ND
- 390 Ohm ±5% 0.25W Through Hole Resistor (1)
- Part #: CF14JT390RTR-ND
- 100k Ohm ±5% 0.25W Through Hole Resistor (3)
- Part #: CF14JT100KCT-ND
- 10µF 16V Aluminum Capacitor
- Part #: P5134-ND
- 0.047µF -20%, +80% 25V Ceramic Capacitor
- Part #: P4307-ND
Step 1: Step 1: Audio Input Circuit
Our design for the audio input circuit is based on the circuit design from the Arduino Audio Input Instructable. Our circuit diagram is shown above. This part of the circuit allows audio to be sampled and processed by the Arduino. The audio signal from the microphone goes into the circuit through an op amp circuit and DC offset circuit and then into the Arduino.
Preparing the Audio Jack: In our circuit, we used the ¼-inch mono audio jack. Connect a wire to the ground pin of the mono jack, which is usually the larger pin on the jack. Then connect a wire to the one of the two signal pins of the mono jack, which are the smaller pins. In our own circuit, we used electrical tape, but you can solder the wires for a better connection.
Building the Circuit: Refer to the circuit diagram above while constructing the circuit. The first step in the circuit is the amplifier. It increases the amplitude of the signal from around ±200 mV to ±2.5 V. It also protects the audio source from the rest of the circuit. The 9 V batteries power the amplifier. Wire the +V and -V to the op amp. Wire the signal from the mono jack to the non-inverting input and connect the ground pin of the jack to the 0 V reference on your voltage supply (the junction between the two 9 V batteries in series). Wire a 100 kOhm resistor between the output and inverting input of the op amp. We used a 10 kOhm potentiometer as a variable resistor to adjust the gain of the op amp. The gain is the amplitude of the output voltage divided by the amplitude of the input voltage. Wire the potentiometer between the inverting input and the 0 V reference. You can adjust the potentiometer to adjust the gain of the amplifier based on the sensitivity of the microphone while keeping it in an acceptable range for the Arduino, which is 0 to 5 V.
DC Offset: The +2.5 V DC offset causes the audio signal to oscillate around 2.5 V so that it stays within the acceptable range (0 - 5 V) for the Arduino’s analog inputs. The DC offset circuit has two main components: a voltage divider and a capacitor. Our voltage divider is made from two 100k resistors wired in series from the Arduino’s 5 V supply to ground. Because the resistors have the same resistance, the voltage at the junction between them equals 2.5 V. This 2.5 V node is tied to the output of the amplifier with a 10 μF capacitor. This causes the voltage at the 2.5 V node to center around 2.5 V. Connect the negative lead of the 10 μF to the output from the op amp. Connect the other side of the capacitor to the node between two 100k resistors in series between 5V and ground. Add the 47 nF capacitor from 2.5 V to ground. Now the output goes into an analog input pin A0 on the Arduino.
Step 2: Step 2: 7 Segment Display
Use a seven segment display and four LEDs to represent each of the following notes of a guitar: E2, A2, D3, G3, B3 and E4. Each segment and LED will be powered by the digital outputs from the Arduino. The digital pins 2-8 will be mapped to segments a, b, c, d, e, f and g of the seven segment display. The digital pin 10 will be used to power the seven segment. The digital pins 11-13 will provide the logic to the LEDs. The seven segment display will display the letter of the note and the LEDs will light up to represent the octave. Due to the limitations of the seven segment display, the notes will be presented in the following way:
E2: “E” with two LEDs lit
A2: “A” with two LEDs lit
D3: “d” with three LEDs lit
G3: “g” with three LEDs lit
B3: “b” with three LEDs lit
E4: “E” with four LEDs lit
Refer to the code in the ‘Programing’ section and ‘Appendix’ to program the LEDs and seven segment display. Refer to the circuit diagram above to wire the seven segment display and LEDs.
Step 3: Step 3: Programming
The code consists of two sections. The first section, written by Amanda Ghassaei, receives the audio input via the Arduino’s Analog to Digital Converter.
From void setup();
cli();//disable interrupts //set up continuous sampling of analog pin 0 at 38.5kHz //clear ADCSRA and ADCSRB registers ADCSRA = 0; ADCSRB = 0; ADMUX |= (1 << REFS0); //set reference voltage ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz ADCSRA |= (1 << ADATE); //enable auto trigger ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete ADCSRA |= (1 << ADEN); //enable ADC ADCSRA |= (1 << ADSC); //start ADC measurements sei();//enable interrupts
In addition, this section deals with frequency detection. It does so by internally forming a graph. It then processes this graph, detecting the frequency of the oscillation formed.
ISR(ADC_vect) {//when new ADC value ready
prevData = newData;//store previous value newData = ADCH;//get value from A0 if (prevData < 127 && newData >=127){//if increasing and crossing midpoint newSlope = newData - prevData;//calculate slope if (abs(newSlope-maxSlope) 9){ reset(); } } } else if (newSlope>maxSlope){//if new slope is much larger than max slope maxSlope = newSlope; time = 0;//reset clock noMatch = 0; index = 0;//reset index } else{//slope not steep enough noMatch++;//increment no match counter if (noMatch>9){ reset(); } } } time++;//increment timer at rate of 38.5kHz ampTimer++;//increment amplitude timer if (abs(127-ADCH)>maxAmp){ maxAmp = abs(127-ADCH); } if (ampTimer==1000){ ampTimer = 0; checkMaxAmp = maxAmp; maxAmp = 0; } }void reset(){//clear out some variables index = 0;//reset index noMatch = 0;//reset match counter maxSlope = 0;//reset slope }
The second section decides what to do upon receiving the frequency, and was written by the Diracakteers. In this section, the frequency from above was used to determine the note by comparing the frequency to the frequency of the desired note. Once, the frequency is determined, the code then drives the seven segment display and LEDs appropriately.
void frequencyCheck(){
if(frequency>70&&frequency<90){ // Displays E on the seven segment display and turns on two LEDs digitalWrite(2,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); } else if(frequency>100&&frequency<120){ // Displays A on the seven segment display and turns on two LEDs digitalWrite(2,HIGH); digitalWrite(3,HIGH); digitalWrite(4,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); } else if(frequency>135&&frequency<155){ // Displays D on the seven segment display and turns on three LEDs digitalWrite(3,HIGH); digitalWrite(4,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); } else if(frequency>186&&frequency<205){ // Displays G on the seven segment display and turns on three LEDs digitalWrite(2,HIGH); digitalWrite(3,HIGH); digitalWrite(4,HIGH); digitalWrite(5,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); } else if(frequency>235&&frequency<255){ // Displays B on the seven segment display and turns on three LEDs digitalWrite(4,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); } else if(frequency>320&&frequency<340){ // Displays E on the seven segment display and turns on four LEDs digitalWrite(2,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); digitalWrite(13,HIGH); } else{ digitalWrite(8,HIGH); } } // void allOff(){ // turn off each segment of the seven segment display and all of the LEDs digitalWrite(2,LOW); digitalWrite(3,LOW); digitalWrite(4,LOW); digitalWrite(5,LOW); digitalWrite(6,LOW); digitalWrite(7,LOW); digitalWrite(8,LOW); digitalWrite(9,LOW); digitalWrite(10,LOW); digitalWrite(11,LOW); digitalWrite(12,LOW); digitalWrite(13,LOW); }
The code can be viewed in full in the Appendix.
Step 4: Step 4: Testing
Test your pitch detector in three phases. Complete Test 1 and Test 2 after you have constructed the audio input circuit and uploaded the code to your Arduino. Complete Test 3 after you have constructed the seven segment display and LED circuit.
1. Test that the microphone is reading any sound and that the code is printing the corresponding frequencies to the serial monitor in the Arduino programming environment. Use your voice as a sound source for this phase of testing by speaking into the microphone. In our project, we ran into some trouble when wiring the microphone. Make sure you are using the proper channels on the microphone.
2. Test that your device is detecting specific frequencies properly. Hold the microphone 4 - 6 inches from the acoustic guitar strings and check that the displayed frequency in the serial monitor matches the expected frequency by playing each note.
Above are sample tests from determining what ampThreshold value to use. If you are running into problems detecting certain frequencies, try changing some of the threshold values. Two tests were recorded with the threshold value of 20 and one test was recorded with the Threshold value of 25. As you can see from the diagrams above, the circuit struggled to detect notes at higher frequencies. To eliminate some of the static, make sure you are testing in a quiet room with little to no surrounding noise.
3. Test that the seven segment display and associated LEDs are displaying the correct note. Repeat Test 2, but this time look at the seven segment display for feedback, as opposed to the serial monitor. The project can be pretty touchy though, as certain notes (especially E4) can be noisy or hard to pick up.
Step 5: Conclusion, Sources, and Appendix
Our group is comprised of five University of Kentucky students studying electrical and computer engineering. We developed this project for a Signals and Systems (EE 421) course. Refer to our WordPress website, Diracakteers' Response to Pitch Detection, which includes a blog of our progress while developing this device and a description of pitch versus frequency.
Sources:
Arduino Audio Input by Amanda Ghassaei (amandaghassaei)
Arduino Frequency Detection by Amanda Ghassaei (amandaghassaei)
Arduino Guitar Tuner by nikoala3
Appendix:
<p>//Modified and used by Dirackteers<br>// //generalized wave freq detection with 38.5kHz sampling rate and interrupts //by Amanda Ghassaei //https://www.instructables.com/id/Arduino-Frequency-Detection/ //Sept 2012</p><p>/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * *</p><p>/data storage variables byte newData = 0; byte prevData = 0; unsigned int time = 0;//keeps time and sends values to store in timer[] occasionally int timer[10];//storage for timing of events int slope[10];//storage for slope of events unsigned int totalTimer;//used to calculate period unsigned int period;//storage for period of wave byte index = 0;//current storage index float frequency;//storage for frequency calculations int maxSlope = 0;//used to calculate max slope as trigger point int newSlope;//storage for incoming slope data</p><p>//variables for decided whether you have a match byte noMatch = 0;//counts how many non-matches you've received to reset variables if it's been too long byte slopeTol = 3;//slope tolerance- adjust this if you need int timerTol = 10;//timer tolerance- adjust this if you need</p><p>//variables for amp detection unsigned int ampTimer = 0; byte maxAmp = 0; byte checkMaxAmp; byte ampThreshold = 30;//raise if you have a very noisy signal</p><p>void setup(){ Serial.begin(9600);</p><p> pinMode(2,OUTPUT); // segment a pinMode(3,OUTPUT); // segment b pinMode(4,OUTPUT); // segment c pinMode(5,OUTPUT); // segment d pinMode(6,OUTPUT); // segment e pinMode(7,OUTPUT); // segment f pinMode(8,OUTPUT); // segment g pinMode(9,OUTPUT); // decimal point of the seven segment display pinMode(10,OUTPUT); // common pinMode(11,OUTPUT); // LED 2 pinMode(12,OUTPUT); // LED 3 pinMode(13,OUTPUT); // LED 4 /* BEGIN AMANDA’s CODE */ cli();//disable interrupts //set up continuous sampling of analog pin 0 at 38.5kHz //clear ADCSRA and ADCSRB registers ADCSRA = 0; ADCSRB = 0; ADMUX |= (1 << REFS0); //set reference voltage ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz ADCSRA |= (1 << ADATE); //enable auto trigger ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete ADCSRA |= (1 << ADEN); //enable ADC ADCSRA |= (1 << ADSC); //start ADC measurements sei();//enable interrupts }</p><p>ISR(ADC_vect) {//when new ADC value ready prevData = newData;//store previous value newData = ADCH;//get value from A0 if (prevData < 127 && newData >=127){//if increasing and crossing midpoint newSlope = newData - prevData;//calculate slope if (abs(newSlope-maxSlope) 9){ reset(); } } } else if (newSlope>maxSlope){//if new slope is much larger than max slope maxSlope = newSlope; time = 0;//reset clock noMatch = 0; index = 0;//reset index } else{//slope not steep enough noMatch++;//increment no match counter if (noMatch>9){ reset(); } } } time++;//increment timer at rate of 38.5kHz ampTimer++;//increment amplitude timer if (abs(127-ADCH)>maxAmp){ maxAmp = abs(127-ADCH); } if (ampTimer==1000){ ampTimer = 0; checkMaxAmp = maxAmp; maxAmp = 0; } }</p><p>void reset(){//clear out some variables index = 0;//reset index noMatch = 0;//reset match counter maxSlope = 0;//reset slope }</p><p>/* END AMANDA’s CODE */</p><p>void frequencyCheck(){ if(frequency>70&&frequency<90){ // Displays E on the seven segment display and turns on two LEDs digitalWrite(2,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); } else if(frequency>100&&frequency<120){ // Displays A on the seven segment display and turns on two LEDs digitalWrite(2,HIGH); digitalWrite(3,HIGH); digitalWrite(4,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); } else if(frequency>135&&frequency<155){ // Displays D on the seven segment display and turns on three LEDs digitalWrite(3,HIGH); digitalWrite(4,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); } else if(frequency>186&&frequency<205){ // Displays G on the seven segment display and turns on three LEDs digitalWrite(2,HIGH); digitalWrite(3,HIGH); digitalWrite(4,HIGH); digitalWrite(5,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); } else if(frequency>235&&frequency<255){ // Displays B on the seven segment display and turns on three LEDs digitalWrite(4,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); } else if(frequency>320&&frequency<340){ // Displays E on the seven segment display and turns on four LEDs digitalWrite(2,HIGH); digitalWrite(5,HIGH); digitalWrite(6,HIGH); digitalWrite(7,HIGH); digitalWrite(8,HIGH); digitalWrite(10,LOW); digitalWrite(11,HIGH); digitalWrite(12,HIGH); digitalWrite(13,HIGH); } else{ digitalWrite(8,HIGH); } } // void allOff(){ // turn off each segment of the seven segment display and all of the LEDs digitalWrite(2,LOW); digitalWrite(3,LOW); digitalWrite(4,LOW); digitalWrite(5,LOW); digitalWrite(6,LOW); digitalWrite(7,LOW); digitalWrite(8,LOW); digitalWrite(9,LOW); digitalWrite(10,LOW); digitalWrite(11,LOW); digitalWrite(12,LOW); digitalWrite(13,LOW); } void loop(){</p><p> allOff(); if (checkMaxAmp>ampThreshold){ frequency = 38462/float(period); //calculate frequency by using the timer rate over period Serial.print(frequency); // print results to the serial display Serial.println(" hz"); } frequencyCheck(); delay(1000);// One second delay }</p>