Bass & Guitar Tuner Audio Object (Yin Algorithm)

Status
Not open for further replies.

duff

Well-known member
Hi All,

Since i play bass and guitar I thought that I would try to make a tuner but I recently moved and have not setup my workbench yet so I decided to start with the software side of things. Many existing projects use this nifty little algorithm called Yin to estimate the fundamental frequency (Fo) so I decided to port it over to the teensy. Well, to my surprise there was already a c++ library out their but the only problem was it was real slow to converge on the Fo. When I mean slow it took seconds to process the samples sometimes so I rewrote the library which greatly sped up the algorithm. I also reduced the size of the huge floating point array it used to hold the difference data. Until this point I was using the Audio library to simulate a sine wave on a second teensy at different frequencies to test it out and I thought I should just make Yin Audio object as well. This allowed me to use one teensy and feed it directly with the AudioSynthWaveformSine object to test. Now the crux of the issue is that even with my speed up it is still to slow to use in the update function so I put the algorithm in the read function. It works fine like this but from all the other Audio objects it seems like most of the processing should be in the update function so not slow down user code? This would get rid of the data buffer I have too in the library. Well, here is the my feeble attempt at making a new object and was hoping for some pointers on how to best implement it. I tried to document it as best as possible. I based it off the analyze_tonedetect object.

see github for the latest code.

I know there is Goertzel algorithm (analyze_tonedetect) but it will not work for the low frequencies of the bass and guitar.

I have some examples too of actual guitar notes (open tuning) in wav format that I converted using labview (yes labview for the haters out their;)) but not going to post here, I'll put all this up on my Github account once I finalize some stuff.
 
Last edited:
thanks @robsoles,

Another question about the Audio Library, since it processes data in blocks of 128 samples every 2.9ms, I assumed (correctly or not) that there will be 344 blocks of data approxmently every second (44032 samples). Now since I'm not math wiz I used this to calculate the algorithms sample rate but I'm not sure this is correct, probably not since it's actually sampling at ~44100 but then the last block (345) would be split between each second?

I guess my issue is that using that way to calculate the sample rate it only gives me 997.6ms of samples per second but since I can't hold all 44100 samples in memory so I need to scale back that sample rate to something reasonable. Unless there is way to rewrite this algorithm not use a bufffer but process the data in realtime but I haven't wraped my head around on how to do that because it needs all the samples before it can calculate the fundmental frequency i think?

Sorry for these basic and/or convoluted questions but I thought I should bring it up for my own understanding.
 
Last edited:
AUDIO_SAMPLE_RATE_EXACT, defined in/by Audio Library, is best reference for number of samples per second to best my knowledge.

Pretty sure it is still defined as 44117, divided by 128 I get 344.6640625 frames per second and that 345th frame is split across the passing second.

Without having time to study the code at greater length right now I am willing to assume that it needs a reasonably substantial number of samples to work with, it would be brilliant to manage the required maths on the fly without storing any samples tho (longer than the library itself would 'normally', at least) so it is something I will happily give time to when I can find time to spare to it.
 
I will happily give time to when I can find time to spare to it.

Thanks,

I knew that the way i setup the sampling rate is suspect but it does give ok results in its current compositions well at anything less than note G2(98Hz) on the bass. You will need to increase the sample rate or make the sample window smaller to get reliable results at higher frequencies.

I tried using FreqMeasure but it had problems with the harmonics in the waveforms. I remember seeing a post by paul on the arduino forums that mentions a comparator circuit with hysteresis could work which I would like to try also.
 
I've done a (commercially intended) tuner using a bandpass centered on E3, about 2 octaves wide, fed into an opamp configured as comparator (sort of hysteresis in method here, more EE at work than my doing) and finally to interrupt pin on MCU.

drove me nuts for a while there, ended up taking 'falling' sample periods into one buffer and 'rising' to other buffer and then adding (obvious partner, not mixing between rising and falling) pairs of sample periods and adding them together and comparing to other paired periods (mixing between rising and falling pairs at this point); more than 2 matched and I called 'snap' (so to speak) - very quick, accurate enough under most circumstances.

I realised the code could benefit from pairing sample periods (appropriately) when I had an AtTiny48 spit out all the sample periods it got for each string under a few different circumstances and I noticed that individually the periods were a mess and there were not enough 'at fundamental' close enough to each other to ever hope to stand a chance of fast accuracy but I am reasonably good at addition and further noticed that simply adding rising pairs together made them look a lot more usable - watching an oscilloscope portray the signal off a ringing string while contemplating the numbers may have helped me make this discovery.

I would need to get my boss's permission (- somewhat unlikely unfortunately) to release that specific batch of code publicly but I can probably write a version I wouldn't feel obliged to hide on his behalf and I could probably share more than enough of the version I wrote for his company privately anyway (not rushing into it tho, probably check for his potential grievance about that too...)


I've an ambition to sit some day and examine the prospects of storing about 17mS of samples (should be enough to cover down to 60Hz, maybe worth doubling to increase chances with low frequencies) to try to find repeating patterns (fading, some amount of shifting harmonics (bloody strings, and other details about acoustic) prob. make it trickier than it looks) to determine fundamental pitch - from what I've seen on really fast oscilloscope watching a string resonate (in correct situ, other strings not 'damped') it looks like this could work; other fish to fry (and forever 'signing up' to help in the randomest of things (f'ng knitting (teaching daughter and gf :p) as well ffs)) so reasonably well back-burnered for now; your effort is fleshed out enough to prompt me to at least look at if I can make a positive difference to it much sooner than I would take this idea of mine off the back-burners.
 
Last edited:
cool, I've been thinking on the hardware part too and like the your idea of bandpass pass filter to the comparator. I also want to design a true bypass circuit that does not degrade signal to the amp so I can incorporate into my bass rig. I won't be doing any hardware stuff for a week or so, I just moved close to a thousand miles away and everything is in boxes still and there is heatwave going on now:mad:.

One thing I'm looking for is relatively fast convergence on the fundamental frequency, it will be pretty painful having to wait seconds for tuning each string, I know I've played hundreds of gigs and being able to tune quickly during a live set is crucial.
 
I updated the code on github to work much better now. It gives good results in the bass and guitar frequency range. I'm pretty happy with this implementation of the Yin algorithm. I think its fast enough for low frequencies not to annoy you when trying to tune but any optimizations anyone can see let me know.

One thing would be nice for the audio library would be able to add your own objects without having to touch the audio library's folder. Maybe there is a way I don't know?

Now I need to get some hardware hooked up to test with real-time guitar-bass tuning.
 
There is an easy way - just include in your .ino (if using Arduino) or main .cpp(/.h) files (if not using Arduino).

Here is an example quickie I wrote a couple of days ago when I decided I didn't want to use a delay object for a single packet worth of delay; I usually put these sorts of things straight after the #include block (which we usually put at the top).
Code:
/**** Audio pass-thru to help keep streams in phase ****/
class AudioPass : public AudioStream
{
  public:
    AudioPass(void) : AudioStream(1, inputQueueArray) {	}
    virtual void update(void);
  private:
    audio_block_t *inputQueueArray[1];
};

void AudioPass::update(void)
{
  audio_block_t *out = NULL;

  out = receiveWritable();
  if (out) {
    transmit(out);
    release(out);
  }
}
/*******************************************************/
Well done with what you've managed so far, btw. I wish I could clear my plate enough to think I've the sort of time I'd like to put into looking at it (and trying it out) and stuff sooner - I will probably get around to it by the time you have the code in a condition I cannot see how to improve :)p)
 
Last edited:
Soz, potentially missing detail: you may need;
Code:
#include "AudioStream.h"
in main file prior to 'Class ****** : public AudioStream'
 
Nice! It works with my bass with no amplification. I DC biased the signal at .6V and fed it to A2 and used the AudioInputAnalog, I just followed the schematic on the Audio System Design Tool for the bias. The cool thing about the Yin algorithm for this application is it doesn't need a whole lot of signal for it work and if you "tune" the algorithm you get really good response with good values. I have a nice Korg tuner that Im checking with and it was definitely in the same ballpark. I haven't done any hard comparisons between the two yet but when I tuned my bass with the korg, Yin gave me that right frequencies. So as another test I detuned the bass and used the Yin to tune and my korg said it was in tune!

There is a little noise on the signal but I don't have the right caps for the bias circuit so I ordered some opamps and passives to do some filtering and amplification stages before feeding it to the teensy. One thing I that will help a lot is having amplified signal so Yin has more signal to work with which will speed up the convergence dramitcally.

I'll update this thread with some pics when I get my componets hooked up and working.
 
I reckon you will be better off to only provide a enough amplification of the original signal to make 'energetic plucks' stay inside the parameters of the input and then just drop that on the AudioAnalogInput and then do filtering n stuff using the library.

You can do a limited form of AGC, which suits the tuning purpose well (at least I think so), using an AudioAnalyzePeak partnered with an AudioMixer4;
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>

#define SCALED(n) exp(n/10)

// GUItool: begin automatically generated code
AudioInputAnalog             adc1;           //xy=156,200
AudioAnalyzePeak             peak1;          //xy=354,158
AudioMixer4                  mixer1;         //xy=394,214
AudioAnalyzeFreqDetect       yin1;          //
AudioConnection              patchCord1(adc1, peak1);
AudioConnection              patchCord2(adc1, 0, mixer1, 0);
AudioConnection              patchCord3(mixer1, yin1);
// GUItool: end automatically generated code

setup()
{
  AudioMemory(12);
  /* I wanna cut to the chase about simplistic AGC */
  

}

elapsedMillis samplingTime;
loop()
{
  /* cutting to chase */

  if(samplingTime>49) // 20 times a second for now
  {
    samplingTime=0;
    if(peak1.available())
    {
      float temp=peak1.read();
//       [COLOR="#FF0000"]if(temp>SCALED(-60)) mixer1.gain(0,temp/SCALED(-3)); // this is backwards![/COLOR]
      [COLOR="#0000CD"]if(temp>SCALED(-60)) mixer1.gain(0,SCALED(-3)/temp); // this is pretty aggressive[/COLOR]
    }
  }
}
I won't guarantee the above compiles coz I just tapped it into the reply box but that (a compiling version, at least) is what I did in the last version of the tuner I presented my boss (and our biggest client) a little while ago.

To some extent the best of my understanding is that the 'SCALED' business I am employing basically attenuates the signal using a dB scale (might be nearest approximation thereof, can't remember source of info atm).

Play with the '-60' (the lower this value the smaller the noise level it will attempt to amplify all the more, the higher the less responsive to noise it will be) but that -3 value there should turn out to be the ideal value imho (so, go nuts, play with it too ;)) - tweak till happy.
 
Last edited:
bump - edited above, had application of (nasty/limited form) AGC backwards, sorry about that.

cool thanks, I still need to clean up the guitar signal, there is about 65mV noise to begin with.
DS1Z_QuickPrint6.png

I'll give your code a go today, I hope my hardware gets here today but I have been playing around with the gain on the mixer. One thing I found useful is to output the filtered data to the DAC to see what the signal actually looks like.
 
Soz; didn't bump to make you have to try it (sooner or ever), just bumped after editing coz I saw the part I had to fix was off screen (until scroll in the code box) so I thought I better point out I had to fix something about it with a new post, just in case you decided to try it from a subscription email or something.

Yes, best to output so as to make sure virtual audio system is 'neat' - I only drew enough of a system to allow me to illustrate the (nasty) AGC method I applied for work, I'm pretty confident you know how to add most anything you want to it :p
 
65mV! If my calculation isn't too off I think that is around -29dB and that's pretty bad!

The following is a rudimentary input level monitor which *may* report around -29dB on your hardware if you haven't changed it much since measuring 0.065Vpp(?) noise earlier - I doubt you haven't changed it tho :)

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>

// It's a rudimentary input level monitor.

// not going to use it in this one but may as well define it here with a more fitting name.
#define db2ratio(n) exp(n/10)

// As far as I can tell this is one of the more professional ways to look at (and control) signal levels.
#define ratio2db(n) 10*log(n)

AudioInputAnalog adc1;
AudioAnalyzePeak peak1;
AudioOutputAnalog dac1;

AudioConnection patch1(adc1,peak1);
AudioConnection patch2(adc1,dac1);

void setup()
{
  AudioMemory(10); // lotsa spares, better than other way...
  Serial.begin(Serial.baud());
}

elapsedMillis delay1;
void loop()
{
  if(delay1<25) return;
  delay1=0;
  if(peak1.available()) // should be a given but feels like better practice.
  {
    Serial.print("Input dB: ");
    Serial.println(ratio2db(peak1.read()));
  }
}
 
Last edited:
If anyones is interested in making a tuner with a Teensy I turned this code into stand alone library so you no longer have to muck around with the Audio Library folder. I made some major changes and actually deleted the old repository for the stand alone library called AudioTuner.

I'm playing around with circuit maker now and will want to make a pcb to condition and amplify the signal coming from the pickups. I'll update this post with some pics with my breadboard version sometime soon and the pcb once done.
 
I've finally got some time to get my circuit mocked up:
Mockup B1.jpg
There is a Linear Technology (LTC6912-2) PGA to amplify the signal which turns out to quite useful since the high E on a guitar or at least mine has a fairly small signal compared to the lower frequency strings. Now I can dynamically change the gain to try to use the whole 1.2 V window for the adc for all strings! These chips also dc bias the signal for you and all I had to do is small hack to get the bias at ~.6V DC instead of 1.5V.

Also wrote a little gui in labview to read and visualize the results here:
Tunner VI.jpg

Next step I'm going to perf board a version before I build a PCB and fab an enclosure!

Oh ya here is my baby:
Bass.jpg
 
hi, I am beginer for teensy
I try comfile your code(https://github.com/duff2013/AudioTuner)
However, an error occurs.

"Arduino / libraries / AudioTuner-master / AudioTuner.cpp: 23: 35: fatal error: analyze_notefreq_fast.h: No such file or directory
compilation terminated. "

I can't find analyze_notefreq_fast.h file anywhere....

Thank you for any hints
(NoteFrequency example works well.)
 
hi, I am beginer for teensy
I try comfile your code(https://github.com/duff2013/AudioTuner)
However, an error occurs.

"Arduino / libraries / AudioTuner-master / AudioTuner.cpp: 23: 35: fatal error: analyze_notefreq_fast.h: No such file or directory
compilation terminated. "

I can't find analyze_notefreq_fast.h file anywhere....

Thank you for any hints
(NoteFrequency example works well.)
Please download version 3.1 I just posted to github. Thanks for the heads up.
 
Hey duff, just wanna say it's awesome to see some bass love for the Teensies! Did you ever get around to building an enclosure?
 
No, a 2 year old takes too much of my time lately:) I do want to get one of your boards though, I want to build a custom "looper" pedal with.
 
No, a 2 year old takes too much of my time lately:)

That would explain the two year old thread perfectly!!!

I'm fascinated by fast (low latency) pitch detections for bass frequencies. I pitch shift my bass to guitar frequencies (think Royal Blood style) for the band I play in. There's such a ton of cool applications for detecting fundamental frequencies on a bass. It's also awesome for bass to midi because with a bass you can build up the synthesized chords from the root rather than trying extract the root from a chord on guitar.
 
Status
Not open for further replies.
Back
Top