Author Topic: Wanted: Algorithm for a Guitar tuner  (Read 6710 times)

0 Members and 1 Guest are viewing this topic.

Offline hamster_nzTopic starter

  • Super Contributor
  • ***
  • Posts: 2812
  • Country: nz
Wanted: Algorithm for a Guitar tuner
« on: May 29, 2019, 10:37:35 pm »
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 laptop

I'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 cheap  ;D

The 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?
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22436
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: Wanted: Algorithm for a Guitar tuner
« Reply #1 on: May 30, 2019, 01:03:20 am »
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?... :-DD

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline KE5FX

  • Super Contributor
  • ***
  • Posts: 2017
  • Country: us
    • KE5FX.COM
Re: Wanted: Algorithm for a Guitar tuner
« Reply #2 on: May 30, 2019, 01:14:29 am »
Hint: The output of an FFT consists of two components at each bin, magnitude and _______ :)

 

Offline hamster_nzTopic starter

  • Super Contributor
  • ***
  • Posts: 2812
  • Country: nz
Re: Wanted: Algorithm for a Guitar tuner
« Reply #3 on: May 30, 2019, 01:56:16 am »
Hint: The output of an FFT consists of two components at each bin, magnitude and _______ :)

I 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  :D )

- Show in screen

As 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).
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline Tom45

  • Frequent Contributor
  • **
  • Posts: 556
  • Country: us
Re: Wanted: Algorithm for a Guitar tuner
« Reply #4 on: May 30, 2019, 02:40:47 am »
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.
 

Online MasterT

  • Frequent Contributor
  • **
  • Posts: 836
  • Country: ca
Re: Wanted: Algorithm for a Guitar tuner
« Reply #5 on: May 30, 2019, 02:55:05 am »
The key word is autocorrelation.
http://www.akellyirl.com/reliable-frequency-detection-using-dsp-techniques/
I haven't tested it myself, but seems right.   
 

Offline RoGeorge

  • Super Contributor
  • ***
  • Posts: 6848
  • Country: ro
Re: Wanted: Algorithm for a Guitar tuner
« Reply #6 on: May 30, 2019, 03:23:10 am »
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

Offline KE5FX

  • Super Contributor
  • ***
  • Posts: 2017
  • Country: us
    • KE5FX.COM
Re: Wanted: Algorithm for a Guitar tuner
« Reply #7 on: May 30, 2019, 03:35:52 am »
Hint: The output of an FFT consists of two components at each bin, magnitude and _______ :)

I think that this what I attempted last night:


That 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() in dsplib.cpp.

 

Offline RoGeorge

  • Super Contributor
  • ***
  • Posts: 6848
  • Country: ro
Re: Wanted: Algorithm for a Guitar tuner
« Reply #8 on: May 30, 2019, 05:01:11 am »
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_detection
https://en.wikipedia.org/wiki/Lock-in_amplifier

Continuously 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 in between 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: [Select]
If reference == 1 then
    sigma1 = sum(signum(sample))
else
    discard sample;

If reference_quadrature == 1 then
    sigma2 = sum(signum(sample))
else
    discard sample;

Didn't test it, thought, so I hope I'm not assuming anything fundamentally wrong here  ^-^.
 
The following users thanked this post: hamster_nz

Offline jeremy

  • Super Contributor
  • ***
  • Posts: 1079
  • Country: au
Re: Wanted: Algorithm for a Guitar tuner
« Reply #9 on: May 30, 2019, 05:13:27 am »
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.
 

Offline hamster_nzTopic starter

  • Super Contributor
  • ***
  • Posts: 2812
  • Country: nz
Re: Wanted: Algorithm for a Guitar tuner
« Reply #10 on: May 30, 2019, 10:50:11 am »
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: [Select]
#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[i] = freqs[i]/SAMPLE_RATE;       
         phase_from_tuned[i] = 0.0;
         display_phase[i]    = 0;
         for(j = 0; j < MAX_REF_LENGTH; j++) {
            kernel_cos[i][j] += cos(j * phase_per_sample[i]*PI*2);
            kernel_sin[i][j] += sin(j * phase_per_sample[i]*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[s][i] * rx_buf[2*i];
         y1 += kernel_sin[s][i] * rx_buf[2*i];
      }
      a1 = atan2f(y1,x1)/(2*PI);
      levels[s] = 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[s][i] * rx_buf[2*(i+offset)];
        y2 += kernel_sin[s][i] * 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[s]*offset-floor(phase_per_sample[s]*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[s] = 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:

« Last Edit: May 30, 2019, 11:50:28 am by hamster_nz »
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 
The following users thanked this post: oPossum

Offline RoGeorge

  • Super Contributor
  • ***
  • Posts: 6848
  • Country: ro

Offline obiwanjacobi

  • Super Contributor
  • ***
  • Posts: 1013
  • Country: nl
  • What's this yippee-yayoh pin you talk about!?
    • Marctronix Blog
Re: Wanted: Algorithm for a Guitar tuner
« Reply #12 on: May 31, 2019, 06:34:51 am »
Will it tune a low B or A (lower than low E) on my 7-string?
 ;D

Nice work, I never understood DSP math...
Arduino Template Library | Zalt Z80 Computer
Wrong code should not compile!
 

Offline Shock

  • Super Contributor
  • ***
  • Posts: 4279
  • Country: au
Re: Wanted: Algorithm for a Guitar tuner
« Reply #13 on: May 31, 2019, 07:43:48 am »
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. :)
Soldering/Rework: Pace ADS200, Pace MBT350
Multimeters: Fluke 189, 87V, 117, 112   >>> WANTED STUFF <<<
Oszilloskopen: Lecroy 9314, Phillips PM3065, Tektronix 2215a, 314
 

Offline windsmurf

  • Frequent Contributor
  • **
  • !
  • Posts: 625
  • Country: us
Re: Wanted: Algorithm for a Guitar tuner
« Reply #14 on: June 01, 2019, 07:38:19 am »
Nice!  Do you know why the last note you played (G) seemed to make the meter hover on the low E?
 

Online Bud

  • Super Contributor
  • ***
  • Posts: 7145
  • Country: ca
Re: Wanted: Algorithm for a Guitar tuner
« Reply #15 on: June 29, 2019, 01:21:13 am »
Alexa API, a step motor on each guitar string, then "Hey Google, tune my guitar!"  :popcorn:
Facebook-free life and Rigol-free shack.
 

Offline windsmurf

  • Frequent Contributor
  • **
  • !
  • Posts: 625
  • Country: us
Re: Wanted: Algorithm for a Guitar tuner
« Reply #16 on: July 03, 2019, 08:02:34 am »
Alexa API, a step motor on each guitar string, then "Hey Google, tune my guitar!"  :popcorn:

It should be simple to do now that Tronical enabled connectivity on their tuners...
https://www.tronicaltune.com/
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15483
  • Country: fr
Re: Wanted: Algorithm for a Guitar tuner
« Reply #17 on: July 07, 2019, 04:22:06 pm »
Alexa API, a step motor on each guitar string, then "Hey Google, tune my guitar!"  :popcorn:

But only if the tuning algorithm runs on a remote Amazon server, of course!
 :-DD
 

Offline Smokey

  • Super Contributor
  • ***
  • Posts: 2937
  • Country: us
  • Not An Expert
Re: Wanted: Algorithm for a Guitar tuner
« Reply #18 on: July 07, 2019, 05:14:44 pm »
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!
 

Offline Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11712
  • Country: my
  • reassessing directives...
Re: Wanted: Algorithm for a Guitar tuner
« Reply #19 on: July 07, 2019, 05:34:47 pm »
Nature: Evolution and the Illusion of Randomness (Stephen L. Talbott): Its now indisputable that... organisms “expertise” contextualizes its genome, and its nonsense to say that these powers are under the control of the genome being contextualized - Barbara McClintock
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7003
  • Country: fi
    • My home page and email address
Re: Wanted: Algorithm for a Guitar tuner
« Reply #20 on: July 09, 2019, 06:01:17 pm »
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 of psychoacoustics...
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf