Wanted: Algorithm for a Guitar tuner - Page 1
Forum section: electronics / general

author: hamster_nz
author_rank: Super Contributor
timestamp: May 29, 2019, 10:37:35 pm
content: I've got everything I need for a DSP guitar tuner project.- a poorly tuned 12-string guitar, that has been under the bed for a few years unplayed- A Sipeed MAIX board (64-bit RISC-V, plenty of RAM and Flash, with floating point, I2S mic, 2" LCD and an I2S microphone)- A working RISC-V SDK kit on my laptopI've had a few attempts at various solutions, but nothing that will actually allow me to reliably tune a string.I've got raw samples from the Mic at 48,000S/s, and the mic has an AGC so that is fine.Most of my attempts have been around correlation of a few thousand sample with the desired frequency, an narrow bands either side, then showing that on the LCD. It of works, and you have to tune for a symmetric curve around the center target frequency.Last night I tried correlation only against target frequency, looking at the phase of that signal relative to the phase of the same correlation a fixed number of samples later (about 1/48th of a second), hoping to measure how much the phase has drifted due to the the string being detuned.That doesn't seem to be working.I assume that everybody will say "FFT!" but when working with 8192 samples @ 48000S/s you get ~6Hz wide bins, which is audibly out of tune.The fundamental frequency range for guitar strings are between about 200 Hz and 800 Hz, so I do have the option sample-rate conversion down to 3kS/s or so, but CPU cycles are supposed to be cheapThe range of indication is most likely +/- 10% of the target frequency, as strings can be about 1/3 of an octave apart.Any hint/ideas?Maybe a relatively bandpass around the target frequency, then count the period on the result?Any idea of keywords for the underlying math I can read up on?

author: T3sl4co1l
author_rank: Super Contributor
timestamp: May 30, 2019, 01:03:20 am
content: Right, 8192 samples isn't a lot. But, as you say, cycles are free, so, why not more?Note that you can't determine the frequency much better than the amount of time spent determining it. That is, if you spend about 1/6th of a second acquiring samples, expect around a 6Hz uncertainty.Can you do better? Sure. Obvious first step, add adjacent bins together and calculate the midpoint -- find the peak assuming it's somewhere smeared across a few bins. That gets you about as much as you need.Can you do better? Sure. But you will sacrifice some of the confidence for the most general method. These are probably fine for a fairly periodic signal as you'd expect from a guitar. You could calculate zero crossings (bad for nasty waveforms, mind) and count samples, including interpolating the sub-sample crossings, for example.And then of course you just need a PID loop controlling the servo driving the tuner, but that's practically trivial right?...Tim

author: KE5FX
author_rank: Super Contributor
timestamp: May 30, 2019, 01:14:29 am
content: Hint: The output of an FFT consists of two components at each bin, magnitude and _______

author: hamster_nz
author_rank: Super Contributor
timestamp: May 30, 2019, 01:56:16 am
content: Quote from: KE5FX on May 30, 2019, 01:14:29 amI think that this what I attempted last night:- Multiply the time-series samples with a sine and cosine of the target frequency and sum (i.e. almost the same as calculating the FFT bin of interest)- Resolve that back to a phase angle with atan2()- And then a short time later (say 1/10th of a second) calculate the same again.- Adjust the phase of one of them for the expected difference in phase if it was perfectly in tune. (e.g. at 48kS/s and with a string frequency of 209.33 Hz 4800 samples away should have a phase difference of 20.933 cycles, or -0.067*2*PI.- Calculate the difference in phase angles - it should be zero when perfectly in tune (but will also be 0 when out of tune by 10Hz)- Show in screenAs you will only get a +/- pi phase difference it limits things somewhat. If the "short time later" is 0.1s, you can only cleanly resolve at best +/- 5Hz, which is a bit too narrow as the notes either side are 221.77 Hz and 197.58 Hz.Sounds like it should work, but...(oh and 8192 samples is arbitrary, but picked so I don't have to overlap LCD and I2S transfers, and I want a reasonable frame rate / interactivity although 5 frames per sec is pretty sucky).

author: Tom45
author_rank: Frequent Contributor
timestamp: May 30, 2019, 02:40:47 am
content: How about running a microphone into a zero crossing detector. Then use the zero crossing detector to gate a reference oscillator which feeds a counter, and also to provide an interrupt on, say, the negative to positive zero crossing.Then on each interrupt read the counter and increment the corresponding histogram bin.

author: MasterT
author_rank: Frequent Contributor
timestamp: May 30, 2019, 02:55:05 am
content: The key word is autocorrelation.http://www.akellyirl.com/reliable-frequency-detection-using-dsp-techniques/I haven't tested it myself, but seems right.

author: RoGeorge
author_rank: Super Contributor
timestamp: May 30, 2019, 03:23:10 am
content: If you have a mobile phone, a tablet or a laptop, all you need to do is to download any free tuning guitar app/program and tune your guitar in minutes using the embedded microphone. No extra hardware required.Alternatively, there are dedicated devices, passives or actives upon preference. The electronic ones also can be used as a metronome, sometimes as a note generator, too.https://www.google.com/search?q=instrument+tuner

author: KE5FX
author_rank: Super Contributor
timestamp: May 30, 2019, 03:35:52 am
content: Quote from: hamster_nz on May 30, 2019, 01:56:16 amThat seems like the right basic idea but it may not be that simple. The general form would be a Hilbert transform that yields I and Q components at whatever the input frequency is, followed by mixing down to baseband with your sin/cos 'local oscillator.' That would definitely yield useful phase information that you could turn into a frequency difference. If you convolve the real input directly with the sin and cos LO, using only real multiplication operations, you won't have an analytic signal with an unambiguous one-sided spectrum... but then you don't get that from the FFT either, so it may not matter.One thing to watch for: even though you aren't doing a generalized DFT you still need to window your input data to make it phase-continuous at the boundaries. See (e.g.) DSP_dominant_tone() indsplib.cpp.

author: RoGeorge
author_rank: Super Contributor
timestamp: May 30, 2019, 05:01:11 am
content: LOL, so you don't want to play the guitar, you want to play with the DSP techniques, sorry for not reading in full the first post.The first idea that comes to mind (and I think the most simple) will be to implement a homodyne detection algorithm (AKA lock-in amplifier).https://en.wikipedia.org/wiki/Homodyne_detectionhttps://en.wikipedia.org/wiki/Lock-in_amplifierContinuously multiply your samples with the reference frequency, then integrate. I didn't put it on paper, but it should work even if the reference is not sinusoidal, so you can multiply with a square wave instead of a reference sinus. To avoid multiplication, you can use a square wave of value +/-1.Even simpler, you can use a reference square wave of 0/1, which in the end will be to continuously count your samples, and add only those overlapping with the 1 semi-alternance of the reference square wave. So, all you have to do is to pick which samples to add, and which samples to skip in a running window adder. You will need a second reference square wave which is 90 degree shifted (quadrature) and do a running adder for it, too. The guitar string is tuned on the reference frequency when (adder1*adder1 + adder2*adder2) is maxim.The math of the lock-in amplifier is nicely explained inbetween minute 4:00 and 8:00. Should work, too, with a square 0/1 instead of a sinus reference, so you get rid of most of the multiplications and use addition instead.Now, the only problem is that the amplitude of the string is not constant, so we can turn it into a saturated square wave too, like we did with the reference frequency. In the end we will just add (AKA integrate) the signum of the sampled signal, but we will add only those samples where the reference signal is 1, and discard those where the value of the reference oscillator is zero.The guitar string is tuned when sigma1*sigma1+sigma2*sigma2 is maxim, where sigma1, sigma2 are something like this:Code:If reference == 1 thensigma1 = sum(signum(sample))elsediscard sample;If reference_quadrature == 1 thensigma2 = sum(signum(sample))elsediscard sample;Didn't test it, thought, so I hope I'm not assuming anything fundamentally wrong here.

author: jeremy
author_rank: Super Contributor
timestamp: May 30, 2019, 05:13:27 am
content: You could try just zero padding your input data. It won’t fix your uncertainty problem, but it will handle the interpolation for you assuming FFTs are cheap.

author: hamster_nz
author_rank: Super Contributor
timestamp: May 30, 2019, 10:50:11 am
content: Success!It actually turns out that the I2S sample rate was 44100, not 48000, and I had a stupid coding error - in DSP code there are seems to be a million ways to do stupid...I know that the code still isn't technically correct (I think I've got time going the wrong way), but it works and I know have a tuned 12-string, that I can put back under the bed.In case anybody discovers this in the future and says 'but what did they do?", here it the guts:Code:#define MAX_REF_LENGTH 2048/* note rx_buf is stereo samples, and only one channel is used */void do_dsp(int offset, int length) {static int first = 1;int i,s;if(first) {/************************************ Build reference sin/cos waves***********************************/for(i = 0; i < N_STRING; i++) {int j;phase_per_sample = freqs/SAMPLE_RATE;phase_from_tuned = 0.0;display_phase = 0;for(j = 0; j < MAX_REF_LENGTH; j++) {kernel_cos += cos(j * phase_per_sample*PI*2);kernel_sin += sin(j * phase_per_sample*PI*2);}}first = 0;}for(s = 0; s < N_STRING; s++) {int i;float x1 = 0.0, y1 = 0.0;float x2 = 0.0, y2 = 0.0;float a1, a2, diff;/* Find the phase angle at the start of the buffer */for(i = 0; i < length; i++) {x1 += kernel_cos * rx_buf[2*i];y1 += kernel_sin * rx_buf[2*i];}a1 = atan2f(y1,x1)/(2*PI);levels = x1*x1+y1*y1;/* Find the phase angle at an offset through the buffer */x2 = 0; y2 = 0;for(i = 0; i < length; i++) {x2 += kernel_cos * rx_buf[2*(i+offset)];y2 += kernel_sin * rx_buf[2*(i+offset)];}a2 = atan2f(y2,x2)/(2*PI);/* adjust the phase angle of the early signal by 'offset' samples */a1 -= phase_per_sample*offset-floor(phase_per_sample*offset);if(a2 < 0.0) a2 += 1.0;if(a2 < 0.0) a2 += 1.0;if(a1 < 0.0) a1 += 1.0;/* Work out the phase difference */diff = a2-a1;/* Clamp the phase angle to +/- 0.5 */if(diff >= 0.5) diff -= 1.0;if(diff < -0.5) diff += 1.0;phase_from_tuned = diff;}}Oh, and my tuner implementation is usable with offset of 2000 and length of 2000.Here is an "this might just be working" video:

author: RoGeorge
author_rank: Super Contributor
timestamp: May 30, 2019, 03:24:33 pm
content: Quote from: hamster_nz on May 30, 2019, 10:50:11 am

author: obiwanjacobi
author_rank: Super Contributor
timestamp: May 31, 2019, 06:34:51 am
content: Will it tune a low B or A (lower than low E) on my 7-string?Nice work, I never understood DSP math...

author: Shock
author_rank: Super Contributor
timestamp: May 31, 2019, 07:43:48 am
content: You forgot the compulsory E major chord at the end of the video.I'm afraid this project is a failure.Heh congrats, you will have to make it stroboscopic next.

author: windsmurf
author_rank: Frequent Contributor
timestamp: June 01, 2019, 07:38:19 am
content: Nice! Do you know why the last note you played (G) seemed to make the meter hover on the low E?

author: Bud
author_rank: Super Contributor
timestamp: June 29, 2019, 01:21:13 am
content: Alexa API, a step motor on each guitar string, then "Hey Google, tune my guitar!"

author: windsmurf
author_rank: Frequent Contributor
timestamp: July 03, 2019, 08:02:34 am
content: Quote from: Bud on June 29, 2019, 01:21:13 amIt should be simple to do now that Tronical enabled connectivity on their tuners...https://www.tronicaltune.com/

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 07, 2019, 04:22:06 pm
content: Quote from: Bud on June 29, 2019, 01:21:13 amBut only if the tuning algorithm runs on a remote Amazon server, of course!

author: Smokey
author_rank: Super Contributor
timestamp: July 07, 2019, 05:14:44 pm
content: This is a great thread. So much good DSP knowledge. I'm going to have to come back here and digest all the techniques. Thanks DSP wizards!

author: Mechatrommer
author_rank: Super Contributor
timestamp: July 07, 2019, 05:34:47 pm
content: dsp learning aside... free...https://play.google.com/store/search?q=guitar%20tuner

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 09, 2019, 06:01:17 pm
content: The missing obligatory chord at the end isn't just obligatory; it is actually an useful tool to detect even smaller errors in tuning, both for humans as well as machines.Then there is the entire field ofpsychoacoustics...