Back in 2008, we organized a family gift exchange for Christmas, and one of the items on my brother-in-law's want list was a chromatic guitar tuner for his band Guajira. Seeing this, I began thinking about how one would go about building such a device, and, thinking it would make a cool Christmas vacation project, I decided to try my hand at it. After a bit of research, I started designing on December 19th; after much programming, building and testing later, I finished with moments to spare on December 24th.
Here is the video I posted shortly thereafter:
Over time the video has elicited many technical questions from viewers, questions which I couldn't answer in full detail until now for reasons unnamed. I'll try to provide a bit more detail than usual in this writeup, but keep in mind this project is almost two years old so the details are getting hazy. :P Also, since I no longer have the device in my possession, having given it away at the gift exchange, I'm unable to take new pictures of it, and I'm stuck with cruddy webcam pics.
The Tuner
As seen in the video, the device has a few functions. It accepts analog input from a standard 1/4" TRS jack, performs monophonic pitch detection on it, and either displays tuning information or outputs MIDI signals based on the detected pitch. Tuning can be adjusted by moving A up or down a few hertz from 440. There is a bypass switch, a backlight switch for the LCD and a contrast ratio pot; the whole thing runs on either DC power or a 9V battery.
You can get the code here.
If the above link does not work (it's hosted on Dropbox, which appears to be blocked in many places), try this link instead and use File/Save (Google Drive).
The Electronics
First off, I am far from being proficient with hardware design; my experience is quite limited, and I'm much more comfortable with digital than analog. I didn't have an oscilloscope when I built the tuner, so I did a lot of fiddling with component values using just a multimeter and a few speakers to listen for noise. (I've since acquired and assembled a DPScope, they're excellent.) As such, the design process was very much empirical, and I had fun swapping parts to see what worked best without the worry of frying a few pennies' worth of electronics.
Also, missing from these schematics is an Arduino. I used a Real Bare-Bones Board from Modern Device, which I recommend; cheap, tiny, easy to assemble. Signals named A5, D4, D5, D6 etc. in the following schematics connect to the pins with the same name on the ATmega168.
There were a few LED connections and simple pushbuttons, whose circuitry I didn't outline here. If you're wondering how to connect such devices to an Arduino, I would suggest the Arduino Playground as a starting point - it's a great wiki.
The Power Stage
POWERSW is the system's power switch, which does its job by connecting the system ground to the power jack's ground pin (pin 3 on DCEXT).
In the case where no external jack is connected, DCEXT's pin 2 is connected to pin 3; the LM317T remains unused, and the 9V battery directly feeds VCC and GND.
In the case where an external power jack is connected, DCEXT's pin 2 remains floating, which disconnects the battery ground from the system. The LM317T turns on and regulates outside power; voltage is adjusted to 9V using VADJ, as per the 317's datasheet. CFILTPOWER is there to smooth out ripple, acting as a low-pass filter. I had a 100uF lying around so I used that. (The LM317 datasheet recommends 10uF or more.)
What I learned:
Well, it doesn't get much simpler than this: a dual-pole, dual-throw switch that either routes the signal from the input jack to the circuit or right back out, like a 2-way multiplexer.
What I learned:
What I learned:
Notes on the Circuit as a Whole
Here is the video I posted shortly thereafter:
Over time the video has elicited many technical questions from viewers, questions which I couldn't answer in full detail until now for reasons unnamed. I'll try to provide a bit more detail than usual in this writeup, but keep in mind this project is almost two years old so the details are getting hazy. :P Also, since I no longer have the device in my possession, having given it away at the gift exchange, I'm unable to take new pictures of it, and I'm stuck with cruddy webcam pics.
The Tuner
As seen in the video, the device has a few functions. It accepts analog input from a standard 1/4" TRS jack, performs monophonic pitch detection on it, and either displays tuning information or outputs MIDI signals based on the detected pitch. Tuning can be adjusted by moving A up or down a few hertz from 440. There is a bypass switch, a backlight switch for the LCD and a contrast ratio pot; the whole thing runs on either DC power or a 9V battery.
You can get the code here.
If the above link does not work (it's hosted on Dropbox, which appears to be blocked in many places), try this link instead and use File/Save (Google Drive).
The Electronics
First off, I am far from being proficient with hardware design; my experience is quite limited, and I'm much more comfortable with digital than analog. I didn't have an oscilloscope when I built the tuner, so I did a lot of fiddling with component values using just a multimeter and a few speakers to listen for noise. (I've since acquired and assembled a DPScope, they're excellent.) As such, the design process was very much empirical, and I had fun swapping parts to see what worked best without the worry of frying a few pennies' worth of electronics.
Also, missing from these schematics is an Arduino. I used a Real Bare-Bones Board from Modern Device, which I recommend; cheap, tiny, easy to assemble. Signals named A5, D4, D5, D6 etc. in the following schematics connect to the pins with the same name on the ATmega168.
There were a few LED connections and simple pushbuttons, whose circuitry I didn't outline here. If you're wondering how to connect such devices to an Arduino, I would suggest the Arduino Playground as a starting point - it's a great wiki.
The Power Stage
The power stage, which provides regulated 9V power to the rest of the circuit.
POWERSW is the system's power switch, which does its job by connecting the system ground to the power jack's ground pin (pin 3 on DCEXT).
In the case where no external jack is connected, DCEXT's pin 2 is connected to pin 3; the LM317T remains unused, and the 9V battery directly feeds VCC and GND.
In the case where an external power jack is connected, DCEXT's pin 2 remains floating, which disconnects the battery ground from the system. The LM317T turns on and regulates outside power; voltage is adjusted to 9V using VADJ, as per the 317's datasheet. CFILTPOWER is there to smooth out ripple, acting as a low-pass filter. I had a 100uF lying around so I used that. (The LM317 datasheet recommends 10uF or more.)
What I learned:
- The battery leaks 6mA through R2 and VADJ, which is quite crappy.
- You mileage may vary when adding a filtering capacitor before the LM317T; I found it made no discernable difference in this case.
Input and bypass.
Well, it doesn't get much simpler than this: a dual-pole, dual-throw switch that either routes the signal from the input jack to the circuit or right back out, like a 2-way multiplexer.
What I learned:
- The foot switch was difficult to source, and once I found an appropriate one, it took up LOTS of space in the case - on the order of two cubic inches. I hadn't really planned for this.
Button wiring; not much to see here, just a simple pull-up resistor setup. As reader Geoff Steele pointed out, you can also simply use the ATmega's internal pull-up resistors.
LCD connections.
Once again, pretty standard stuff. The LCD I used had a backlight, hence BACKLIGHTSW. Contrast adjustment is done using RCONTRAST, on pin 3. Pins 4, 6, 11, 12, 13 and 14 form a 4-bit data connection to the Arduino, as described on the Arduino LiquidCrystal page.
What I learned:
What I learned:
- The cheap surplus MTC-16205D LCD I used required some extra "convincing" to boot up properly. I included the LiquidCrystal library directly in the PDE and modified the source to get it working.
The MIDI Stage
MIDI connector connections.
More standard fare; I recommend the ITP page about MIDI output from Arduino, it's great!
The Amplification Stage
The amplification stage, which takes the input from the guitar and feeds it to the Arduino. (See note below about INPUTCOUPLING capacitor, I think I had the wrong value marked down.)
This stage is the one I spent the most time trying to get right, and (I'm guessing) probably the portion of the circuit which is of most interest to the people trying to build something similar. I'm positive there are many grievous design errors in here, but in the end I got it working well enough.
Overall, what this circuit does is take the input, adjust its volume (VADJ), amplify it (the LM386), and convert it to a form which the Arduino can sample (R7/R8/D1/D2).
I began with the application entitled "Amplifier with Gain = 200" from the LM386 datasheet:
- Volume adjustment is done using VADJ1 as a voltage divider. I preferred this approach over adjustable amplification gain because the physical mapping of "angle of the volume potentiometer" to "amplification volume" was easier to control this way; judging by the LM386's typical applications, this seems to be an oft-favoured way of doing things.
- The LM386 is setup as a fixed gain amplifier, whose gain is adjusted to 200 using CFILT3, as per the datasheet.
- The output is fed through a low-pass filter built using CFILTAUDIO and R6; this rejects unwanted noise from the amplifier. Without it, you can hear AM radio faintly, and there is a lot of crackle in the amplified sound.
- An output coupling capacitor, OUTPUTCOUPLING, blocks any DC bias at the output of the amplifier, preventing it from leaving that stage and entering the following portion of the circuit. (If a speaker was connected directly to the output of the amplifier, DC would flow through the speaker's coils, which are delicate and may heat up. Here, instead of a speaker, we have an Arduino; more on this below.)
- I added R5 to set the minimum amplification level. R5 effectively create a "floor" for the voltage divider formed by R5 and VADJ1.
- I added the INPUTCOUPLING capacitor to fix a buzzing problem; I think I wound up using a bigger value than 0.011uF, however, since that's an unusually small value for a coupling capacitor, one which I think might have filtered the signal a bit too much. Much like the output coupling capacitor, this prevents DC from entering the op-amp (LM386) and being amplified, offsetting the output signal. I distinctly recall trying multiple values on for size here.
- Instead of connecting a speaker to OUTPUTCOUPLING, I connected an analog input on the Arduino so I could sample the signal.
To rectify this situation, we have to do two things. Firstly, we can add D1 and D2 as shunt diodes to clip the signal to 5V and 0V, respectively. In practice, the forward bias of the diodes allow the signal to exceed the allowed range slightly, but the excess shouldn't damage your Arduino. I used fast-switching 1N914 diodes, which I just realized have a forward bias voltage of 1V; this means the signal can still range from -1V to 6V, which is probably a bit much to be safe. Nevertheless, I've had no problems so far... knock on wood. :P
The second thing we must to is a bit trickier. The output of the LM386 centers around 0V, which is half-way between -9V and 9V. The Arduino, however, has a mid-range input of 2.5V, which is half-way between 0V and 5V. If the Arduino just samples the signal as clipped by the diodes, anything below 0V from the op-amp will get clipped and sampled as a 0 by the Arduino. This means that the Arduino would only see the top half of the waveform from the LM386, which would make it difficult for us to have an accurate "big picture" of the waveform for frequency analysis.
To fix this, we must add a DC bias to the signal, to "re-center" it around 2.5V. This is done with the help of R7 and R8, which form a sort of voltage divider. Since R7 and R8 have the same value, when no AC is coming through the coupling capacitor, the voltage at the Arduino pin is 2.5V. When the LM386 is busy amplifying a signal, the AC component of the amplifier's output makes it through OUTPUTCOUPLING, nudging the rest point of 2.5V up and down, if you will. (It is important to use large values for R7/R8 in order to raise the cutoff frequency of the high-pass filters they form with the OUTPUTCOUPLING capacitor.)
To fix this, we must add a DC bias to the signal, to "re-center" it around 2.5V. This is done with the help of R7 and R8, which form a sort of voltage divider. Since R7 and R8 have the same value, when no AC is coming through the coupling capacitor, the voltage at the Arduino pin is 2.5V. When the LM386 is busy amplifying a signal, the AC component of the amplifier's output makes it through OUTPUTCOUPLING, nudging the rest point of 2.5V up and down, if you will. (It is important to use large values for R7/R8 in order to raise the cutoff frequency of the high-pass filters they form with the OUTPUTCOUPLING capacitor.)
You may wonder what we do about scaling the range of the signal. The answer is: nothing, VADJ1 takes care of that for us. Since different guitar pickups have different signal levels, on the tuner I built, the user adjusts VAJD1 using a potentiometer. LEDs indicate amplified signal strength for calibration.
What I learned:
- Despite warnings from my E&CE241 teachers and lab instructors to the contrary, as long as you keep the datasheet warnings in mind and your finger not too far from the kill switch, it is perfectly fine to try various component values in your circuit. In fact, it's fun and instructive, and I plan to do more of it in the future.
Notes on the Circuit as a Whole
Seeing as how it's performing signal analysis, the tuner is fairly sensitive to power supply noise. About a year after the tuner was made, I took it in for a "service call". The unit sometimes no longer detected pitch properly; after some testing, I realized this only occurred if the LCD backlight was on and an external power adapter was used. It seems that when the LCD backlight is on, the extra load on the power supply stage increases the ripple which makes it through to the amplifier stage and Arduino supply (which, in turn, probably affects the reference voltage of the ATmega168's ADC to some degree, even though the Arduino has its own regulator). My hypothesis is that this is due to failure of some kind in the power supply filtering; possibly my recycled 100uF capacitor gave up the ghost or otherwise deteriorated; perhaps my soldering was suboptimal; perhaps there are gremlins in the box. In any case, this is very similar to what occurred on a breadboard when I accidentally knocked the 100uF capacitor out.
The Software
The software does a few things. I won't really discuss the pushbutton logic and so on here, since there is a lot of documentation available about that. The two things I get the most questions about are the pitch-detection algorithm and the method I used to output slim vertical bars on the LCD.
How to Perform Pitch Detection
I tried a few "heuristic" algorithms - counting zero crossings, analysing the first-order derivative, and so on - but these often depended on the shape of the signal, which gave erratic results. (Even a single vibrating string has multiple harmonics which make such approches difficult to work with.) I wound up settling on a home-grown variation (and simplification) of an algorithm called YIN, which you can find described here. Based on autocorrellation, this algorithm worked much better than all other approaches. However, as you might tell from the paper, it is relatively expensive in terms of computation and memory, at least as far as an ATmega168 goes. (Newer Arduinos now use the ATmega328; double the SRAM goes a long way, but you might be constrained by the speed at which you can process the data.) The challenge was to adapt YIN to make it work with a 16MHz CPU that has no FPU and a few hundred bytes of available RAM.
I won't explain the full details of YIN here, but I will explain the gist of it, to show how I adapted it for the Arduino. Autocorrellation-based approaches are actually reasonably simple to visualize. Given a quasi-periodic signal like that produced by a vibrating string, the idea is to take the original waveform:
And then, we create a virtual copy of the signal, shifting it over a bit:
Then, we compute some function of the area between the two resulting signals, shown in purple below:
The idea is to keep sliding the copy over until the area between the two resulting signals is minimized. Notice how the red copy keeps sliding over to the right, and how it gets closer and closer to "matching" the blue copy again.
The software does a few things. I won't really discuss the pushbutton logic and so on here, since there is a lot of documentation available about that. The two things I get the most questions about are the pitch-detection algorithm and the method I used to output slim vertical bars on the LCD.
How to Perform Pitch Detection
I tried a few "heuristic" algorithms - counting zero crossings, analysing the first-order derivative, and so on - but these often depended on the shape of the signal, which gave erratic results. (Even a single vibrating string has multiple harmonics which make such approches difficult to work with.) I wound up settling on a home-grown variation (and simplification) of an algorithm called YIN, which you can find described here. Based on autocorrellation, this algorithm worked much better than all other approaches. However, as you might tell from the paper, it is relatively expensive in terms of computation and memory, at least as far as an ATmega168 goes. (Newer Arduinos now use the ATmega328; double the SRAM goes a long way, but you might be constrained by the speed at which you can process the data.) The challenge was to adapt YIN to make it work with a 16MHz CPU that has no FPU and a few hundred bytes of available RAM.
I won't explain the full details of YIN here, but I will explain the gist of it, to show how I adapted it for the Arduino. Autocorrellation-based approaches are actually reasonably simple to visualize. Given a quasi-periodic signal like that produced by a vibrating string, the idea is to take the original waveform:
The basic sampled signal.
And then, we create a virtual copy of the signal, shifting it over a bit:
Copy of the original signal.
Then, we compute some function of the area between the two resulting signals, shown in purple below:
The idea is to keep sliding the copy over until the area between the two resulting signals is minimized. Notice how the red copy keeps sliding over to the right, and how it gets closer and closer to "matching" the blue copy again.
In practice, the steps are much smaller. I made them a bit larger above to demonstrate the idea. However, since we're getting quite close to the red waveform overlapping the blue one again, for the purposes of illustration, let's reduce the step size a bit. (Again, the actual algorithm tries many, many small steps.)
(Pretty close here!)
As you can see, in the third picture above, the two curves came pretty close to overlapping. That corresponds to the moment where the purple area - a function of the area between the curves - was minimized. Once you figure out the offset between the curves for which this area is minimized, you can calculate the corresponding pitch with the following simple equations:
So that's the general idea. But how do we make it run on Arduino? Put another way: what is it that's prohibitively expensive about YIN and prevents it from running on the ATmega168? In practice, I found the two constraints were the time spent computing the value of the difference function (related to the "size" of the purple area above), and keeping a tight reign on the memory requirements of the sampling buffer.
The first hurdle you run into is floating-point math support; simply, there is none on the ATmega168, so you have to give up on floating-point math in any performance-critical portions in the code. I used fixed-point math where I required fractional quantities in the code.
Next, as with most every decision when building gizmos, you have to start thinking about tradeoffs. The difference function used by true YIN works by iterating over all samples in a given window, subtracting the value of the red curve from that of the blue curve and squaring that difference, summing all values thus obtained. The first change I made was to give up on the squaring operation, which required a lot of expensive multiplications, and instead simply compute the absolute value of the difference at each sample:
where tau represents the offset at which the difference function is being evaluated.
In many ways, the revised version is related to the average magnitude difference function (AMDF) mentioned in the YIN paper, only without the averaging operation. Losing the squaring operation may introduce inaccuracies in our detection, but it keeps the general shape of the resulting function looking pretty close to the original while giving us huge improvements in executing speed, so it works out. (Critically, the minima and maxima of both equations occur for the same values of tau.)
That really helped speed up execution, but it was still too slow and memory-intensive. As it turns out, if you can reduce the size of your buffer, you also gain in computation time, because you have fewer samples to process - two birds with one stone! So the next step was trying to figure out a buffer size for which the computation was fast enough, while maintaining the required pitch detection precision.
By "maintaining the required pitch detection precision", I mean that as the wavelengths get shorter and shorter (i.e. as pitch gets higher), a period will take up fewer and fewer samples in our buffer. See the following exerpt from a small helper worksheet I made:
| Frequency (Hz) | Cpu Cycles Per Period | seconds per period | samples per period | |
| A | 55 | 290909.09 | 0.01818 | 174.83 |
| A# | 58.27 | 274581.62 | 0.01716 | 165.01 |
| B | 61.74 | 259170.54 | 0.01620 | 155.75 |
| C | 65.41 | 244624.41 | 0.01529 | 147.01 |
| C# | 69.3 | 230894.7 | 0.01443 | 138.76 |
| D | 73.42 | 217935.57 | 0.01362 | 130.97 |
| D# | 77.78 | 205703.79 | 0.01286 | 123.62 |
| E | 82.41 | 194158.52 | 0.01213 | 116.68 |
| F | 87.31 | 183261.24 | 0.01145 | 110.13 |
| F# | 92.5 | 172975.58 | 0.01081 | 103.95 |
| G | 98 | 163267.21 | 0.01020 | 98.12 |
| G# | 103.83 | 154103.72 | 0.00963 | 92.61 |
| A | 110 | 145454.55 | 0.00909 | 87.41 |
| A# | 116.54 | 137290.81 | 0.00858 | 82.51 |
| B | 123.47 | 129585.27 | 0.00810 | 77.88 |
| C | 130.81 | 122312.21 | 0.00764 | 73.5 |
| C# | 138.59 | 115447.35 | 0.00722 | 69.38 |
| D | 146.83 | 108967.79 | 0.00681 | 65.49 |
| D# | 155.56 | 102851.9 | 0.00643 | 61.81 |
| E | 164.81 | 97079.26 | 0.00607 | 58.34 |
| F | 174.61 | 91630.62 | 0.00573 | 55.07 |
| F# | 185 | 86487.79 | 0.00541 | 51.98 |
| G | 196 | 81633.6 | 0.00510 | 49.06 |
| G# | 207.65 | 77051.86 | 0.00482 | 46.31 |
| A | 220 | 72727.27 | 0.00455 | 43.71 |
| A# | 233.08 | 68645.4 | 0.00429 | 41.25 |
| B | 246.94 | 64792.63 | 0.00405 | 38.94 |
| C | 261.63 | 61156.1 | 0.00382 | 36.75 |
| C# | 277.18 | 57723.67 | 0.00361 | 34.69 |
| D | 293.66 | 54483.89 | 0.00341 | 32.74 |
| D# | 311.13 | 51425.95 | 0.00321 | 30.91 |
| E | 329.63 | 48539.63 | 0.00303 | 29.17 |
| F | 349.23 | 45815.31 | 0.00286 | 27.53 |
| F# | 369.99 | 43243.9 | 0.00270 | 25.99 |
| G | 392 | 40816.8 | 0.00255 | 24.53 |
| G# | 415.3 | 38525.93 | 0.00241 | 23.15 |
| A | 440 | 36363.64 | 0.00227 | 21.85 |
| Prescaler | 7 | |||
| ADC Divider | 128 | |||
| Clk/sample | 13 | |||
| F_CPU | 16000000 | |||
| SampleRate | 9615.38 |
See at the end there, around the 440Hz mark, rounding up the 21.85 gives 22, and rounding down the 23.15 gives 23 - that means that using integer buffer offsets (integer values of tau) in the formula above, it would become difficult to differentiate consecutive semitones starting around that A.
One way to increase the precision would be to reduce the prescaler value on the ADC and sample twice as often, which would mean reducing the speed of the code by half and growing the memory by a factor of two. But what is the exact buffer size we're talking about here? What would be the minimal buffer size required to work with the YIN and the frequencies we want to analyze? Well, we need a minimum of two full periods being sampled, in order to be able to copy the first period over the second. Looking at the above table, if we set our minimal frequency to 55 Hz, 175 samples are required to sample a full period, so the minimum size of the buffer would be 350 samples.
The above table shows that these settings are sufficient for coarse precision, around 100 cent of a semitone at the highest frequencies. This is what the code uses for perform pitch detection when in MIDI mode, because it is important to reduce latency (and thus minimize computation). However, 100 cent is hardly sufficient to perform precise tuning of guitar strings, so we must dive further.
I use another trick from the YIN paper: sub-sample interpolation. The idea is simple. In practice, the offset at which the difference function is minimized will never be an integer value; it will be a fractional value, meaning that you have to shift the red copy of the waveform over by a fractional number of samples for the two copies to line up perfectly. Unfortunately, we only sampled the signal at specific intervals, so in order to be able to "resample" the signal at fractional offsets, we reconstruct an approximation of the original, true, analog signal by interpolating between the samples we took. Where YIN uses quadratic (second-order) interpolation, however, I was limited to linear (first-order) interpolation by the processing power. Nevertheless, this provides greatly-enhanced precision.
In the end, to get everything integrated and running, I wrote a small set of classes that allowed me to profile single statements as run on the actual hardware. I also used avr-objdump to disassemble the code produced by the compiler and understand where the inefficiencies were. (There was no need to hand-assemble code; the compiler does a fine job as long as you use the correct datatypes.) I fiddled around with the sampling precision (the ADC on an ATmega168 can sample up to 10 bits of precision), the minimal detection frequency, and the interpolation step size a great deal until I settled on 8-bit samples, a minimum frequency of 60 Hz, and a prescaler value of 7, for a final buffer size of 321 bytes. The sub-sampling code uses fixed-point math with a 5-bit fraction, and it solves up to the least significant bit when finding the minimum in the difference function; thus, the tuner has a theoretical accuracy of 1/32th of a sample, or approximately 2.4 cents of a semitone at 440Hz (3.8 cents at 880Hz).
(The code applies a small amount of low-pass filtering to the final computed pitch value in order to smooth out the display on the LCD.)
The LCD Driver
Rendering the thin vertical bars on the LCD is actually not very complex. Most LCD drivers have a small area of memory called CGRAM, short for Character Generator Random Access Memory, which allows you to create a few custom characters for display. Since the display matrix of the LCD I used was 5x7, I merely created five custom characters, each which a different column of dark pixels. Turning on a given column of pixels then becomes the simple matter of writing the correct custom character to that location on the screen.
Scary Pictures
As mentioned above, about a year after hacking this thing together, I took it in to replace the battery. By this point, my digital camera was repaired, so I snapped a few glorious pictures of the complete mess that it is.
.png)

.png)
.png)
.png)
.png)



















cool project, im trying to do something similar using the Arduino uno which has the ATmega328...im trying to find the fundamental frequency of each invidivual string. Im new to programming and after reading the YIN method im a little confused with the math. Can you share the code you used to implement the YIN method? It would be a great help, thanks. -jay
ReplyDeleteHey Jay,
ReplyDeleteThe code is actually linked in the post above, it's not very visible I guess, hehe:
http://dl.dropbox.com/u/2330215/TunerCode.zip
Enjoy!
great work. hey i am trying to build a guitar tuner using Arduino Uno with ATmega328. but my approach is different, i am using a zero crossing algorithm. do u have any idea on how to set the sampling frequency of the arduino uno ADC. thanx... shawn
ReplyDeleteHi Shawn!
ReplyDeleteI'm not sure if the Arduino library exposes a function to do so, but you can always go "straight to the metal" yourself, since the Arduino exposes the atmel system headers. You can find the documentation for the atmega328 chip here:
http://www.atmel.com/dyn/resources/prod_documents/doc8271.pdf
Setting up the conversion frequency is usually a matter of twiddling the ADPS0, ADPS1 and ADPS2 bits in the ADCSRA register. There is an example of this in the tuner code linked on this page, in Tuner::Start(). Note that for very high sampling frequencies, if you're doing a lot of work, you might have to do a bit of math to make sure your CPU can keep up with the ADC.
Note that before settling on YIN, I tried various simpler approaches - zero crossing, peak counting, combining this with slope analysis, etc. and though they /sometimes/ worked for specific notes, they were still nowhere near as reliable as YIN, because of the harmonic content present in guitar strings. They would work for very simple waveforms with very attenuated or no harmonics, however.
Best of luck, and have fun!
super,
ReplyDeletei build a guitar2midi converter using your yin implantation from here posted tuner code. i get ~42ms latency. this is ok enough for me.
i try several other methods from zero detection to complete fft. but the yin method looks like the fastest and also most error resistant method.
blah blah blah, i only want to say thx for this impressive suggestion, because i was nearly on the point to give up.
Awesome, glad you managed to make use of it! Thanks for the feedback!
ReplyDeletehi,
ReplyDeletei post this yin based guitar2midi converter project on the arduino forum Exhibition/Gallery. here is the forums link. http://arduino.cc/forum/index.php/topic,82621.msg620845.html#msg620845
hopefully this is ok for you, because 95% from my converter code are simply a copy from your code. i only remove all things needed from tuner and lcd display and change some things in the midi note on/off logic.
Great project! :D Thanks for letting me know! Glad you got some use out of it!
ReplyDeletehello!! please read the blog on the last part
DeleteThis project description was really helpful, thanks! I'm working on a variation of this as a project, using separate string pickups to do polyphonic midi. Did a lot of poking online to find YIN, and then your implementation. I probably need more memory and cycles to do 4-6 strings in parallel, but I'm going to start with the pickup implementation and testing it w/your code. Thanks again!
ReplyDeleteWow, that sounds awesome! Let me know what you come up with! Best of luck!
ReplyDeleteI've more or less created the identical amplifier with some minor tweaks, and am powering it, as well as a universal tuner pick up via vcc on the arduino, but I am trying to use the pulsein function for frequency measurement, not YIN. So long as I take a large enough sample, things thus far, seem to work okay. Fairly uninvasive code as it does not use atmega pin 5 timer counter or libraries. Is there anything fundamentally unsound to what I am doing? Why aren't more people using pulsein samples, and calculating frequency that way? I imagine outside the audible range this method lacks accuracy. But for an audio/instrumental application it seems fine, no?
ReplyDeleteHello gbcben! Hmm, I have not worked with the pulsein function, but from the documentation it seems it is a variation on the zero-crossings method. I've tried a few, and indeed it is possible to get somewhat workable results (as noted above), but the problem comes in when you have harmonics in your signal... because of the harmonics, the number and interval of zero crossings will vary depending on many factors like your amplifier bias and gain, and so on. With a steep low-pass filter you might be able to get rid of many of the offending higher-order harmonics and make your input signal into more of a sine wave, but that would restrain the range over which your tuner (or pitch detector) would work (unless you applied the low-pass in software and perhaps came up with some kind of adaptive strategy to raise the cutoff frequency of your filter until you let a minimum amount of energy through, perhaps). In any case, even with a few variations on that theme, I found I was still too far from the really accurate results I wanted; for my purposes, YIN was the perfect fit since it did not depend on any amplifier or filter parameters, or the waveform shape. (This last quality allows it to work with any instrument which has a pitch; I use my voice as an example in the video, but it could be anything else.) So... to answer your question, there is absolutely nothing unsound about what you are doing, and if it suits your purposes and needs, then by all means, proceed! I simply found that YIN provided that extra step in accuracy and robustness that I required.
DeleteThanks Raptor, the harmonics became a problem. I am getting the higher frequency harmonics on problem pitches. Which is unfortunate, because the rest of the time I get frequency accurate to small fractions of a hertz. I'll be tweaking my circuit, and utilizing a YIN approach.
DeleteIf someone were looking for a simple tuner, where you simply get frequencies, and had a preset that you were tuning for, then did some division for scaling, then you could do a very very light weight and accurate tuner with my approach. But if you need the actual frequency of the instrument(not a scale of it) then YIN seems to be the best approach.
For the sake of anyone doing similar things, I'll update this comment thread after I make adjustments.
Many thanks for sharing your experience! I'm sorry to hear it didn't work out in the end, but at least it sounds as though you identified a subset of input cases where the approach makes sense..!
Deletei want to do ths project almost exactly except i would like the tuner to control lights not an lcd screen can you help me with the programming ? which part to to take out ? this should actually be much simpler coding i assume im just not sure what is the lcd screen and what is need for the rest of the code operation
ReplyDeleteThanks for your interest! Actually, do you mean something like this?
Deletehttp://www.youtube.com/watch?v=q_wdKhV-b6w&feature=plcp&context=C4cfe68bVDvjVQa1PpcFOO4oxlKhYhAqDW1HXTjQYUBzhONq__Klw%3D
I've been looking for an excuse to write up that project as well, let me know if that interests you!
As far as the code goes, for the tuner, you basically only need the Tuner class and the few functions it relies on. However, you can strip the m_lcd member and any code that uses it, along with anything relating to glyphs or the LiquidCrystal class. I think you should be able to set ENABLE_LCD to 0 and have it work, but I have not tested that recently - there may be a few minor compilation nags to fix.
That's a fairly summary description of what should be needed; let me know if you need more detail!
how can i make a simple arduino program that recognizes a frequency and does something. say for each note a different led turns on......
ReplyDeleteplease help as in your code there's probably something similar.
thank you in advance !
Indeed! You should be able to use the code pretty much as is. I would use TunerMode::Midi (by setting Tuner.m_mode to that value), which will reduce latency if you only need note detection. I also suggest you have a quick look at RenderWideGlyphForNote(), which does something like the following:
Deletevoid RenderWideGlyphForNote(int note)
{
if (note >= 0)
{
int nameIndex = GetNoteNameIndex(note);
...
You could easily use nameIndex to light up a specific LED instead. nameIndex will correspond to one of 12 notes in the scale: A, A#, B, C, etc.
This is very cool,
ReplyDeleteI'm just curious, what resistance values did you use for R5 and R6? And pin 7 is just left unconnected right?
Hey Tyler,
DeleteThanks for the feedback! I *believe* the values indicated on the schematic are valid - 910 ohms for R5, and 10 ohms for R6 - but since I no longer have the physical gizmo by me, I can't be 100% sure. I readily admit that with the rest of the circuitry in place (op-amp/output stage), the complexity of circuit analysis required to verify the cutoff frequency of the resulting filter vastly exceeds my current back-of-the-envelope calculation abilities, especially since R6 is not placed in a "classical" LPF setup. And yes I believe I left pin 7 floating in my design, though looking at the application notes again I see that I could have used it with a bypass capacitor (i.e. just connect the pin to ground through a capacitor) to isolate the amp from power supply noise. Ah, hindsight :D
Ok awesome! Thanks a lot. A friend and I are trying to build this (a simpler version I must say) and this page has been beyond helpful, you did a great job. One other thing that we are struggling with though, is the code. We've been trying to use a modified version of (https://github.com/JorenSix/Pidato) that code but It doesn't seem to read properly. We have tried to go through yours and just filter out the MIDI and LCD parts but neither of us know a whole lot about programming the arudino and yours was rather involved :P
DeleteThanks once more for the kind words! As far as filtering out the MIDI and LCD code... as noted (and buried) somewhere in the comments above, I think you should be able to get rid of most of the LCD stuff by #defining ENABLE_LCD to 0 instead of 1, but I admit I haven't tested that compilation path lately. And as for MIDI, you can just comment out everything having to do with Serial - the serial port is only used for MIDI output. That will actually free up the port for you to print debug output to, say, a PC, if you want to modify and debug the code anyhow. Hope this helps a bit..!
DeleteHi, Would love to try this out but the dropbox link above is coming up as a 404. Is it still hosted there or has it been moved? Thanks
ReplyDeleteDarn, that's bizarre? I just tried the public link (http://dl.dropbox.com/u/2330215/blog/TunerCode.zip) from two distinct IPs, and everything seems to work fine. You might be able to get it to work over https as well: https://dl.dropbox.com/u/2330215/blog/TunerCode.zip I know this has worked in the past, but there may be some kind of new technical hurdle. If it still doesn't work, let me know and I'll try to find an alternative.
DeleteThanks for your interest!
Awesome - the public link did the job.
DeleteThanks !
Hello Please I need your help im trying to only read the musical tones and active a relay for each individual note.
ReplyDeletemy email is cgdemoya@gmail.com
I need to receive thought an audio jack any sound and activate a relay for each note. I prefer to activate it only for 8 basic notes
ReplyDeletei do not need LCD or anything like that to visualize. The only thing I have to do is if Im hearing the soud " for elise" in its first 20 seconds ( could be done from an audio jack or small microphone) I need to activate 1 relay for each note heared and only have it activated during the current note .If im hearing LA.. I have to activate the LA relay and have it activated until I hear another note.. If im hearing more than one note for example LA and SOL..then relays for LA and SOL must be activated please help me out Thanks
ReplyDeleteHello Constantino! Thanks for your interest. This project _might_ be a good starting point for you, but I'm not sure, because it will fall short on two aspects:
Delete-it is probably not quite fast enough to pick out individual notes in a song like "Für Elise", at least if the song is played at regular speed
-due to the pitch detection approach used, it will not detect multiple notes at the same time. It will only report the lowest note played, and even then it will probably have trouble doing so when the input signal is polyphonic.
If these limitations are not acceptable for you, you might have better luck with an approach based on the Fast Fourier Transform (FFT), which you can research by googling "arduino FFT". The FFT will allow you to analyze the signal on a frequency spectrum basis, and you can attempt to pick out any peaks to detect played notes.
If you still wish to being from this project, I would start by downloading the code linked on this page, setting ENABLE_LCD to 0 and using the MIDI mode code as a starting point. Best of luck!
Hi,
ReplyDeleteI have found an implementation on YIN witten for the arduino, it's available on github (https://github.com/JorenSix/Pidato/tree/master/libraries/Yin), which I thought may be of use to some people. I was wondering if you (or anyone else) knew of a way to calculate the speed this code may run on the Arduino without actually running it on an arduino. The reason I ask is because I am designing a guitar tuner for a project, but do not have time to build and test it
Hi Andy,
DeleteOof... that is a difficult thing to estimate. From a cursory glance at the code, one can see it uses floating-point math; since the Arduino does not have a floating-point unit, it is safe to assume that the algorithm as written on github would run prohibitively slowly on Arduino. Moreover, depending on the actual Atmega chip you plan on using, the memory requirements can quickly get out of hand, even for a single-precision sample buffer.
On Arduino/ATMega*8, moving to fixed-point math improves performance by orders of magnitude and cuts down on memory requirements. However, in order to reduce the YIN latency while keeping memory use under control, I had to spend some time looking at the assembler output from the compiler, tweaking and profiling individual statements of code to get them running just barely fast enough for things to be practical. (I'm sure some headroom remains, if anybody feels inclined to carry on the effort. :-) )
If you are locked into using an Arduino for your project, then my humble suggestion would be to either use the code I provide on this page as a starting point or go down the FFT route. However, if this is something "on paper", perhaps nothing prevents you from changing your design to be centered around a DSP chip with a floating-point unit, an FPGA, or even simply just a much faster general-purpose microcontroller. That would get the number-crunching limitations out of the way and perhaps allow you to concentrate on the tuner-specific parts of the project.
Best of luck, and thanks for taking the time to comment!
Hi, congratulations about this!
ReplyDeleteI'm learning so much reading this topic. Can you please check the code link?? i'm trying to download it to get more perspective to the project, understanding it from inside.
I'm already on a project based on yours and many others from the internet. Right now i'm stuck in the very begining, i don't know if my preamp is properly working.
I made some little programs trying to test it but were helpless. Do you know any solution to test the preamp without building all the rest of the electronics?
Hey there Zangatre!
DeleteThanks! Hmm, I just checked the link and it seems to work for me. The original link is on dropbox though, that seems to be blocked in a few locations... I added another to Google Drive, let me know if that works.
The best solution to test the preamp would probably be to borrow an oscilloscope just to see what the output looks like, but if you don't have access to a 'scope perhaps the next best thing would be to temporarily connect a speaker or some cheap headphones and use your ears to determine whether the sound seems "OK". That's what I did, since I didn't have an oscilloscope at the time I built this. Ideally, you'd add a small coupling capacitor before the speaker to make sure you don't send a DC signal through the coils, but if you're just connecting a speaker for a short amount of time and the coils don't have time to heat up to much you might be OK. Your mileage may vary. :) An old PC speaker would do just fine!
Best of luck!
Hi! thanks for the answer!
DeleteThe oscilloscope is stucked in my wishlist im afraid hehe.
We (my brother and me) have builded the power stage and the preamp electronics but we cannot make it works fine.
On a simple arduino code we got the analog read at the serial monitor, but it is always 1023 (máx read, it doesn't matter about the guitar (lol). This is the beggining of the project and it is the third preamp we've build so... I'm starting to think that we are doing something else wrong.
It there a chance that you still have the eagle files of the schematics??
By the way... I finally downloaded the code by the https dropbox link.
Thanks a lot for the support!
Hello again!
DeleteI'm glad you managed to get your hands on the code! :)
As for your circuit, wow you certainly have a lot of patience if you've built three preamps! I'm sure that will come in handy when trying to diagnose the issue. A couple of things come to mind that you could try without a 'scope. Hopefully one of these will set you on the right track:
-with no input signal, have you tried measuring the DC bias of the input to your Arduino? The resistor network (R7 and R8 in the diagrams above) should give a bias of around 2.5V for a supply voltage of 5V. That's kind of "neutral ground" if you will; for zero input signal, the arduino should read 512 (for a 10-bit reading, which is what you seem to imply from the 1023 in your above post).
-the DC bias on the output pin of the LM386 should also be one-half your supply voltage, so 4.5V if you're using a 9V supply. That means pin 5 of the LM386 should read 4.5V without any input connected. In case you're trying to follow the above diagram, it's important to note that what's on the left of the OUTPUTCOUPLING capacitor in the above schematics uses VCC=9V as a supply voltage, whereas the resistor network on the right of the OUTPUTCOUPLING capacitor uses 5V from the Arduino's voltage regulator as supply.
-have you verified that you are able to read other analog sources from the Arduino pin you are trying to use? Perhaps verify that you are able to read values other than 1023 in software by using a simple potentiometer to vary the voltage supplied to the ADC? Alternatively, perhaps trying using a different ADC pin? It is common for single pins to get "fried" while the rest of the microcontroller works fine.
-if your multimeter has an AC mode, you might have some success trying to measure AC at various points in the circuit. Make sure the input signal (probably millivolts) is making it to the amplifier, then through the amplifier, then to the Arduino input. And use the DC mode to check that the signal remains centered about the expected value.
I still do have the EAGLE schematics, but they are of no real functional use... I only used them to create the drawings above, I did not create them with sufficient rigor to allow them to be used in simulation, layout or any such. If you're still interested, let me know.
That's sort of what comes to mind for now! If that doesn't help, let me know and I'm sure we can come up with more paths to explore :)
Best of luck! Keep me posted!
Hi
DeleteShould the VCC at the MIDI port be 5V also, or is it the 9V VCC used elsewhere?
Thanks
Oooohoho... good find... It should be 5V. If you use 9V, your mileage may vary. In practice there is an optoisolator at the MIDI In end; essentially the TX line (pin 5) on the MIDI output port will be shorted to pin 4 when it's active, so depending on the voltage drop from the diodes within the optoisolator and the sink capacity of whatever you have connected to the TX line, you may or may not burn something out at 9V. So if you want to stay safe... stay with 5V :-) Thanks for pointing that out!
DeleteSimilarly VCC should also be 5V for the switches on D9, D10 & D11.
ReplyDeleteIs there a reason not to use the internal pull-up resistors on the Arduino or ATmega for those push button pull-ups?
I'm pretty close to getting to the board stage on this. Has anyone got any feedback on the value used for the input coupling cap on the amp? I was tempted to use 0.1uF as I have before with this component, but not sure the implications that might cause.
Thanks, Geoff
Hey there Geoff,
ReplyDeleteWow, after all this time, I finally get around to taking a look at this. Sorry for the delay, and a huge thanks for your questions and comments!
Indeed, the VCC 9V vs. 5V was quite unclear on these diagrams! I've updated the diagrams with what I hope clearer labelling.
There is no reason not to use the internal pull-up resistors - in fact, believe it or not, I was unaware of them when I put this project together. I've added a note to this effect - thanks again!
As far as the input coupling cap goes, unfortunately, I have no news to share. :(
Thanks for your interest!
Thanks for the clarification. I've been off on other projects - there's always plenty of those on the go. I've just updated the schematic so next trick is the board layout. After all this I'll have to find out where to send you one when they're done.
ReplyDeleteCheers!
Geoff
I totally understand, I always have many things in flight as well. :)
DeleteHaha I'm quite curious to see what you'll come up with! Keep me posted, and have fun hacking! :P