Components and supplies
Solderless Breadboard Full Size
Arduino UNO
USB-A to B Cable
Jumper wires (generic)
Computer Speaker, Pro-Sound 2000
DEVMO High Sensitivity Sound Detection Module for Arduino
Apps and platforms
Mini Piano LT
Project description
Code
Musical Note Detector
c_cpp
1/* 2 File/Sketch Name: MusicalNoteDetector 3 4 Version No.: v1.0 Created 7 June, 2020 5 6 Original Author: Clyde A. Lettsome, PhD, PE, MEM 7 8 Description: This code/sketch displays the approximate frequency as well as the musical note played on an electronic keyboard or piano app. For this project, the analog output from the 9 sound module detector is sent to the A0 analog input of the Arduino Uno. The analog signal is sampled and quantized (digitized). Autocorrelation, weighting and tuning code is used to 10 find fundamental frequency using the first 3 periods. The approximate fundamental frequency is then compared to frequencies in octaves 3, 4, and 5 range to determine the closest musical 11 note frequency. Finally the guessed note for the closest frequency is printed to the screen. 12 13 License: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License (GPL) version 3, or any later 14 version of your choice, as published by the Free Software Foundation. 15 16 Notes: Copyright (c) 2020 by C. A. Lettsome Services, LLC 17 For more information visit https://clydelettsome.com/blog/2020/06/07/my-weekend-project-musical-note-detector-using-an-arduino/ 18 19*/ 20#define SAMPLES 128 //Max 128 for Arduino Uno. 21#define SAMPLING_FREQUENCY 2048 //Fs = Based on Nyquist, must be 2 times the highest expected frequency. 22#define OFFSETSAMPLES 40 //used for calabrating purposes 23#define TUNER -3 //Adjust until C3 is 130.50 24 25float samplingPeriod; 26unsigned long microSeconds; 27 28int X[SAMPLES]; //create vector of size SAMPLES to hold real values 29float autoCorr[SAMPLES]; //create vector of size SAMPLES to hold imaginary values 30float storedNoteFreq[12] = {130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185, 196, 207.65, 220, 233.08, 246.94}; 31 32int sumOffSet = 0; 33int offSet[OFFSETSAMPLES]; //create offset vector 34int avgOffSet; //create offset vector 35 36int i, k, periodEnd, periodBegin, period, adjuster, noteLocation, octaveRange; 37float maxValue, minValue; 38long sum; 39int thresh = 0; 40int numOfCycles = 0; 41float signalFrequency, signalFrequency2, signalFrequency3, signalFrequencyGuess, total; 42byte state_machine = 0; 43int samplesPerPeriod = 0; 44 45 46void setup() 47{ 48 Serial.begin(115200); //115200 Baud rate for the Serial Monitor 49} 50 51void loop() 52{ 53 //***************************************************************** 54 //Calabration Section 55 //***************************************************************** 56 Serial.println("Calabrating. Please do not play any notes during calabration."); 57 for (i = 0; i < OFFSETSAMPLES; i++) 58 { 59 offSet[i] = analogRead(0); //Reads the value from analog pin 0 (A0), quantize it and save it as a real term. 60 //Serial.println(offSet[i]); //use this to adjust the sound detection module to approximately half or 512 when no sound is played. 61 sumOffSet = sumOffSet + offSet[i]; 62 } 63 samplesPerPeriod = 0; 64 maxValue = 0; 65 66 //***************************************************************** 67 //Prepare to accept input from A0 68 //***************************************************************** 69 avgOffSet = round(sumOffSet / OFFSETSAMPLES); 70 Serial.println("Counting down."); 71 delay(1000); //pause for 1 seconds 72 Serial.println("3"); 73 delay(1000); //pause for 1 seconds 74 Serial.println("2"); 75 delay(1000); //pause for 1 76 Serial.println("1"); 77 delay(1000); //pause for 1 seconds 78 Serial.println("Play your note!"); 79 delay(250); //pause for 1/4 second for reaction time 80 81 //***************************************************************** 82 //Collect SAMPLES samples from A0 with sample period of samplingPeriod 83 //***************************************************************** 84 samplingPeriod = 1.0 / SAMPLING_FREQUENCY; //Period in microseconds 85 for (i = 0; i < SAMPLES; i++) 86 { 87 microSeconds = micros(); //Returns the number of microseconds since the Arduino board began running the current script. 88 X[i] = analogRead(0); //Reads the value from analog pin 0 (A0), quantize it and save it as a real term. 89 90 /*remaining wait time between samples if necessary in seconds */ 91 while (micros() < (microSeconds + (samplingPeriod * 1000000))) 92 { 93 //do nothing just wait 94 } 95 } 96 97 //***************************************************************** 98 //Autocorrelation Function 99 //***************************************************************** 100 101 for (i = 0; i < SAMPLES; i++) //i=delay 102 { 103 sum = 0; 104 for (k = 0; k < SAMPLES - i; k++) //Match signal with delayed signal 105 { 106 sum = sum + (((X[k]) - avgOffSet) * ((X[k + i]) - avgOffSet)); //X[k] is the signal and X[k+i] is the delayed version 107 } 108 autoCorr[i] = sum / SAMPLES; 109 110 // First Peak Detect State Machine 111 if (state_machine==0 && i == 0) 112 { 113 thresh = autoCorr[i] * 0.5; 114 state_machine = 1; 115 } 116 else if (state_machine == 1 && i>0 && thresh < autoCorr[i] && (autoCorr[i]-autoCorr[i-1])>0) //state_machine=1, find 1 period for using first cycle 117 { 118 maxValue = autoCorr[i]; 119 120 } 121 else if (state_machine == 1&& i>0 && thresh < autoCorr[i-1] && maxValue == autoCorr[i-1] && (autoCorr[i]-autoCorr[i-1])<=0) 122 { 123 periodBegin = i-1; 124 state_machine = 2; 125 numOfCycles = 1; 126 samplesPerPeriod = (periodBegin - 0); 127 period = samplesPerPeriod; 128 adjuster = TUNER+(50.04 * exp(-0.102 * samplesPerPeriod)); 129 signalFrequency = ((SAMPLING_FREQUENCY) / (samplesPerPeriod))-adjuster; // f = fs/N 130 } 131 else if (state_machine == 2 && i>0 && thresh < autoCorr[i] && (autoCorr[i]-autoCorr[i-1])>0) //state_machine=2, find 2 periods for 1st and 2nd cycle 132 { 133 maxValue = autoCorr[i]; 134 } 135 else if (state_machine == 2&& i>0 && thresh < autoCorr[i-1] && maxValue == autoCorr[i-1] && (autoCorr[i]-autoCorr[i-1])<=0) 136 { 137 periodEnd = i-1; 138 state_machine = 3; 139 numOfCycles = 2; 140 samplesPerPeriod = (periodEnd - 0); 141 signalFrequency2 = ((numOfCycles*SAMPLING_FREQUENCY) / (samplesPerPeriod))-adjuster; // f = (2*fs)/(2*N) 142 maxValue = 0; 143 } 144 else if (state_machine == 3 && i>0 && thresh < autoCorr[i] && (autoCorr[i]-autoCorr[i-1])>0) //state_machine=3, find 3 periods for 1st, 2nd and 3rd cycle 145 { 146 maxValue = autoCorr[i]; 147 } 148 else if (state_machine == 3&& i>0 && thresh < autoCorr[i-1] && maxValue == autoCorr[i-1] && (autoCorr[i]-autoCorr[i-1])<=0) 149 { 150 periodEnd = i-1; 151 state_machine = 4; 152 numOfCycles = 3; 153 samplesPerPeriod = (periodEnd - 0); 154 signalFrequency3 = ((numOfCycles*SAMPLING_FREQUENCY) / (samplesPerPeriod))-adjuster; // f = (3*fs)/(3*N) 155 } 156 } 157 158 //***************************************************************** 159 //Result Analysis 160 //***************************************************************** 161 if (samplesPerPeriod == 0) 162 { 163 Serial.println("Hmm..... I am not sure. Are you trying to trick me?"); 164 } 165 else 166 { 167 //prepare the weighting function 168 total = 0; 169 if (signalFrequency !=0) 170 { 171 total = 1; 172 } 173 if(signalFrequency2 !=0) 174 { 175 total = total + 2; 176 } 177 if (signalFrequency3 !=0) 178 { 179 total = total + 3; 180 } 181 182 //calculate the frequency using the weighting function 183 signalFrequencyGuess = ((1/total) * signalFrequency) + ((2/total) * signalFrequency2) + ((3/total) * signalFrequency3); //find a weighted frequency 184 Serial.print("The note you played is approximately "); 185 Serial.print(signalFrequencyGuess); //Print the frequency guess. 186 Serial.println(" Hz."); 187 188 //find octave range based on the guess 189 octaveRange=3; 190 while (!(signalFrequencyGuess >= storedNoteFreq[0]-7 && signalFrequencyGuess <= storedNoteFreq[11]+7 )) 191 { 192 for(i = 0; i < 12; i++) 193 { 194 storedNoteFreq[i] = 2 * storedNoteFreq[i]; 195 } 196 octaveRange++; 197 } 198 199 //Find the closest note 200 minValue = 10000000; 201 noteLocation = 0; 202 for (i = 0; i < 12; i++) 203 { 204 if(minValue> abs(signalFrequencyGuess-storedNoteFreq[i])) 205 { 206 minValue = abs(signalFrequencyGuess-storedNoteFreq[i]); 207 noteLocation = i; 208 } 209 } 210 211 //Print the note 212 Serial.print("I think you played "); 213 if(noteLocation==0) 214 { 215 Serial.print("C"); 216 } 217 else if(noteLocation==1) 218 { 219 Serial.print("C#"); 220 } 221 else if(noteLocation==2) 222 { 223 Serial.print("D"); 224 } 225 else if(noteLocation==3) 226 { 227 Serial.print("D#"); 228 } 229 else if(noteLocation==4) 230 { 231 Serial.print("E"); 232 } 233 else if(noteLocation==5) 234 { 235 Serial.print("F"); 236 } 237 else if(noteLocation==6) 238 { 239 Serial.print("F#"); 240 } 241 else if(noteLocation==7) 242 { 243 Serial.print("G"); 244 } 245 else if(noteLocation==8) 246 { 247 Serial.print("G#"); 248 } 249 else if(noteLocation==9) 250 { 251 Serial.print("A"); 252 } 253 else if(noteLocation==10) 254 { 255 Serial.print("A#"); 256 } 257 else if(noteLocation==11) 258 { 259 Serial.print("B"); 260 } 261 Serial.println(octaveRange); 262 } 263 //***************************************************************** 264 //Stop here. Hit reset button on Arduino to restart 265 //***************************************************************** 266 while (1); 267} 268
Downloadable files
Musical Note Detector Schematic
Musical Note Detector Schematic
Musical Note Detector Schematic
Musical Note Detector Schematic
Comments
Only logged in users can leave comments