Trying to detect frequency of guitar audio efficiently and quickly

Hello, I'm currently quite new with programming in Arduino, already have been programming for a year, but not able to crank this one out. I'm trying to detect the frequency (or period, then calculate the inverse of it for frequency) of a given audio signal coming from a guitar. I'm using an Arduino Mega 2560 (not able to buy or get another board, as it costed all of my life savings) and connecting it to a circuit that filters the guitar signal (between 800mV and 32mV), amplifies it (to about 1.55Vp) and mounts it on a direct voltage signal to get a voltage between 0 and 3V.

I've managed to built a program that allows me to write a custom frequency PWM signal between 0Hz and 4KHz from the amplitude of a 10K potentiometer (using PWM library), and another one that replicates the frequency of a sine wave through digitalWrite and delays. I tried to merge the two programs together, but to no luck at all, and the frequency detector one works really weirdly, as digitalWrite + delay doesn't keep a constant HIGH outpút, but a pulse train.

What I'm looking for is for a way to detect the frequency of an audio signal and paste it into the parameters of the PWM frequency generator function, or for a way that is better, more-efficient, quicker or just different, as I've been trying to get this working for a week and to no avail. The code for both programs is below, first the frequency generator, and second the frequency reader (which doesn't quite work sometimes).

#include <PWM.h>

void setup() {
  InitTimersSafe();
  pinMode(11, OUTPUT);
}

void loop()
{ 
  int sV = analogRead(A3);
  int32_t freq = sV * 3.91111;
  SetPinFrequencySafe(11, freq);
  pwmWrite(11, 128);
}
int ciclo = 0, i = 0, period = 0;

void setup() {
    pinMode(A3, INPUT);
    pinMode(11, OUTPUT);
    analogReference(EXTERNAL);
}

void loop() {
  while(analogRead(A3) >= 480){
    ciclo++;
    delayMicroseconds(1);
  }
  period = (0.000001 * ciclo);
  period = 1000 * period;
  digitalWrite(11, HIGH);
  delay(period);
  digitalWrite(11, LOW);
  delay(period);
  ciclo = 0;
  period = 0;
}

Are you aware that by default analogRead() on the Mega takes about 110 microseconds?

Since audio signals are rarely sine or rectangular waves, most people use autocorrelation or Fourier transform methods to measure frequency components or pitch. The OpenMusicLabs FFT or FHT code is pretty good.

This autocorrelation pitch detection tutorial looks quite good: https://coertvonk.com/sw/arduino/pitch-detector/frequency-and-pitch-detection-31575

Wasn't aware of that, thanks for pointing in out.

What you're saying is a little bit going over my head, honestly. What is autocorrelation and fourier transforms? And how on Earth do I code that? FHT?

The code is written for you, for both techniques, but you do need to have some understanding of what it does and how to use it.

As usual, do the research and reading, and experiment with examples.

The approach you have now probably cannot be made to function reliably, if at all in most practical cases.

From what I could understand and infer, there's no code correction for registering low notes. Since guitars can go below 150Hz, I'd probably need to find a way to adapt it... Adapting code that was previously there is not exactly my forte x'd

That is likely only the fundamental frequency of one string. There will be many overtones of that frequency.

Is there a way to simulate audio-like signals in Proteus, for example? It's the simulation tool I use most often to test code

In what code, and why do you think a "code correction" is needed?

With some effort and experimentation with a practical setup, I'm confident that you can overcome your confusion. Simulation is very unlikely to help understand the issues that arise in real world pitch detection.

But here is a simple example of using autocorrelation to detect the frequency peak in some IDEAL test data:

// example of frequency measurement using autocorrelation

// number of samples. If using default ADC sample rate on Arduino Uno (9600 sps)
// total sample time = 256/9600 = 26.7 milliseconds

#define LENGTH 256
int8_t rawData[LENGTH];  //input signal
float sample_rate = 9600.0;
float frequency = 0.0;

int len = LENGTH;
int i, k;
long sum, sum_old;  //autocorrelation results
int thresh = 0;
byte pd_state = 0;  //state variable

void setup() {

  int period = 0;
  long sum_max = 0;
  Serial.begin(115200);

  //generate test sine wave data
  float testF = 125.0; //Hz

  for (i = 0; i < len; i++) {
    rawData[i] = 120.*sin(2.0 * PI * testF * i / sample_rate);
  }

    // Autocorrelation calculation for all possible time shifts
  for (i = 0; i < len; i++)
  {

    sum_old = sum;
    sum = 0;

   //autocorrelation for one time shift

    for (k = 0; k < len - i; k++) sum += (rawData[k]) * (rawData[k + i]) / 256;

    // Peak Detect State Machine
    if (pd_state == 2 && (sum - sum_old) <= 0) //state = past the peak
    {
      sum_max = sum_old;  //remember peak value
      period = i;  //remember peak index
      pd_state = 3; //state = peak found
    }
    if (pd_state == 1 && (sum > thresh) && (sum - sum_old) > 0) pd_state = 2; //state = in peak

    //first time through
    if (i == 0) {
      thresh = sum / 2; //set peak detect threshold
      pd_state = 1;  //state = peak search
    }
  }

  if (period > 0) {
    frequency = sample_rate / period;

    Serial.print("Average correlation = ");
    Serial.println(((float)sum) / len);
    Serial.print("Peak value = ");
    Serial.println(sum_max);
    Serial.print("f (peak) = ");
    Serial.println(frequency);
  }
}

void loop() {}

I've never tried it but from what I've read, most people trying to make guitar tuners with the Arduino fail. :frowning: But I've also read that Autocorrelation works better than FFT or FHT.

I once saw a video of a guitar played into an oscilloscope (just one note/string at a time). When the string was first plucked the waveform was "nasty" and complex (lots of overtones). As it died-down (the "sustain" part of the note) it started to look like a clean sine wave.

The lowest note on a standard-tuned guitar is around 80Hz. A standard bass guitar goes one octave lower (around 40Hz).

...With FFT the bins all have equal frequency width. This means "perceptually" you loose resolution as you go down in frequency. i.e. 10 Hz accuracy is probably good-enough at 5KHz but if you're trying to tune a guitar it's not close enough at 60Hz.

I'd assume FHT is similar and autocorrelation is probably not like that.

And you have chosen to do one of the most difficult if not imposable tasks.

The big problem, apart from you not understanding basic frequency measuring techniques, is that when you strike a guitar string you get not only the fundamental frequency but a load of harmonics. What makes the thing harder is that the mix of harmonics constantly change thorough the duration of the note. Therefore it is almost impossible to detect exactly what note is playing.

have you considered counting zero crossings to estimate the frequency.

i'll have to look inside my tuner, but it must not take much

here's an instructable Guitar Tuner with an Arduino

Does this mean that professional guitartuners have a configurable low-pass-filter to dampen the higher harmony-frequencies?

best regards Stefan

harmonics (above the root frequency) shape the waveform. below is an example of how square waves are composed of harmonics, but different mixes of harmonic (only odd, even, ...) can create triangle or sawtooth shapes. all have the same fundamental frequency

counting zero crossings effectively ignores harmonic content

JhZnq

Amplitude versus time plot of a typical note on a guitar string, chosen at random from examples on the web.

it says guitar waveform, but not of a single notes

each period of the waveform, at the fundamental frequency should be repetitive, but changes over time as the harmonic content changes

Yes but that is for synthesising a note from a collection of harmonics, in a real note you do not get the chance of counting zero crossings.

The whole point is that any harmonic could be the highest harmonic at any point for the duration of the note so a simple zero crossing could show anything in terms of frequency.

Many people have tried this project and there hasn't been one successful implementation reported.

I don't think you get the complexity of the resulting signal.
See:-
Guitar Physics

basing my observations from staring at my guitar connected to an oscilloscope (and my experience with signal processing)

but it's been a few decades

That's what my original program already does, but like other replies pointed out, not exactly a good approach, or at least how I did it doesn't seem to be particularly good.

And you have chosen to do one of the most difficult if not imposable tasks.

So I've been banging my head against my keyboard trying to come up with multiple solutions to stuff like this for almost the entire year because of a project, and it was all worthless because nobody has been able to figure it out yet? The Universe is working against me, not even gonna be able to graduate at this point--

It's worse than mentioned. The plucked string has an exponential decay so the signal amplitude varies widely. The harmonics are not in a fixed relationship with the fundamental due to string tension, and slide around in phase dynamically. Thus any analysis in the time domain is limited to extremely simplistic and poorly performing frequency resolution.