gokul/acoustic-guitar-tuner

This code defines a miniature circuit board featuring a microcontroller (ATTINY1616-MNR), audio and power components, tactile switch, various LEDs, and passive components arranged with specific footprints, connections, and mechanical markings for a mini acoustic guitar tuner.

Version
1.0.8
License
unset
Stars
2

firmware/acoustic_tuner_attiny1616.ino

/*
 * Mini Acoustic Guitar Tuner — ATtiny1616 / megaTinyCore
 * 8 kHz sampling and normalized autocorrelation for standard EADGBe tuning.
 */

#include <Arduino.h>
#include <math.h>

constexpr uint16_t SAMPLE_RATE = 8000;
constexpr uint16_t SAMPLE_COUNT = 256;
constexpr uint16_t SAMPLE_PERIOD_US = 1000000UL / SAMPLE_RATE;

constexpr uint8_t AUDIO_PIN = PIN_PA2;
constexpr uint8_t MODE_PIN = PIN_PB0;

constexpr uint8_t NOTE_LEDS[] = {
  PIN_PA4, PIN_PA5, PIN_PA6, PIN_PA7, PIN_PB5, PIN_PB4
};
constexpr uint8_t FLAT_LED = PIN_PB3;
constexpr uint8_t TUNE_LED = PIN_PB1;
constexpr uint8_t SHARP_LED = PIN_PB2;

constexpr float STRING_HZ[] = { 82.4069f, 110.0f, 146.832f, 195.998f, 246.942f, 329.628f };
constexpr float IN_TUNE_CENTS = 4.0f;

int16_t samples[SAMPLE_COUNT];

void allLedsOff() {
  for (uint8_t pin : NOTE_LEDS) digitalWrite(pin, LOW);
  digitalWrite(FLAT_LED, LOW);
  digitalWrite(TUNE_LED, LOW);
  digitalWrite(SHARP_LED, LOW);
}

void captureSamples() {
  uint32_t next = micros();
  int32_t sum = 0;
  for (uint16_t i = 0; i < SAMPLE_COUNT; ++i) {
    while ((int32_t)(micros() - next) < 0) {}
    const int16_t value = analogRead(AUDIO_PIN);
    samples[i] = value;
    sum += value;
    next += SAMPLE_PERIOD_US;
  }

  const int16_t mean = sum / SAMPLE_COUNT;
  for (uint16_t i = 0; i < SAMPLE_COUNT; ++i) samples[i] -= mean;
}

float estimateFrequency() {
  int32_t energy = 0;
  for (uint16_t i = 0; i < SAMPLE_COUNT; ++i) {
    energy += (int32_t)samples[i] * samples[i];
  }
  if (energy < 12000) return 0.0f;

  // Guitar range: 70–360 Hz at 8 kHz corresponds to lags 23–114.
  uint16_t bestLag = 0;
  float bestScore = 0.0f;
  for (uint16_t lag = 22; lag <= 118; ++lag) {
    int32_t correlation = 0;
    int32_t energyA = 0;
    int32_t energyB = 0;
    for (uint16_t i = 0; i < SAMPLE_COUNT - lag; ++i) {
      const int16_t a = samples[i];
      const int16_t b = samples[i + lag];
      correlation += (int32_t)a * b;
      energyA += (int32_t)a * a;
      energyB += (int32_t)b * b;
    }
    if (energyA == 0 || energyB == 0) continue;
    const float score = correlation / sqrtf((float)energyA * (float)energyB);
    if (score > bestScore) {
      bestScore = score;
      bestLag = lag;
    }
  }

  if (bestScore < 0.62f || bestLag == 0) return 0.0f;

  // Parabolic interpolation around the autocorrelation maximum.
  auto corrAt = [](uint16_t lag) {
    int32_t c = 0;
    for (uint16_t i = 0; i < SAMPLE_COUNT - lag; ++i) c += (int32_t)samples[i] * samples[i + lag];
    return (float)c;
  };
  float refinedLag = bestLag;
  if (bestLag > 22 && bestLag < 118) {
    const float left = corrAt(bestLag - 1);
    const float center = corrAt(bestLag);
    const float right = corrAt(bestLag + 1);
    const float denominator = left - 2.0f * center + right;
    if (fabsf(denominator) > 1.0f) refinedLag += 0.5f * (left - right) / denominator;
  }
  return SAMPLE_RATE / refinedLag;
}

uint8_t nearestString(float frequency) {
  uint8_t best = 0;
  float smallestError = 100000.0f;
  for (uint8_t i = 0; i < 6; ++i) {
    const float cents = fabsf(1200.0f * log2f(frequency / STRING_HZ[i]));
    if (cents < smallestError) {
      smallestError = cents;
      best = i;
    }
  }
  return best;
}

void showTuning(float frequency) {
  allLedsOff();
  if (frequency <= 0.0f) return;

  const uint8_t stringIndex = nearestString(frequency);
  digitalWrite(NOTE_LEDS[stringIndex], HIGH);
  const float cents = 1200.0f * log2f(frequency / STRING_HZ[stringIndex]);
  if (cents < -IN_TUNE_CENTS) digitalWrite(FLAT_LED, HIGH);
  else if (cents > IN_TUNE_CENTS) digitalWrite(SHARP_LED, HIGH);
  else digitalWrite(TUNE_LED, HIGH);
}

void setup() {
  analogReadResolution(10);
  pinMode(AUDIO_PIN, INPUT);
  pinMode(MODE_PIN, INPUT_PULLUP);
  for (uint8_t pin : NOTE_LEDS) pinMode(pin, OUTPUT);
  pinMode(FLAT_LED, OUTPUT);
  pinMode(TUNE_LED, OUTPUT);
  pinMode(SHARP_LED, OUTPUT);
  allLedsOff();
}

void loop() {
  if (digitalRead(MODE_PIN) == LOW) {
    for (uint8_t pin : NOTE_LEDS) digitalWrite(pin, HIGH);
    digitalWrite(FLAT_LED, HIGH);
    digitalWrite(TUNE_LED, HIGH);
    digitalWrite(SHARP_LED, HIGH);
    while (digitalRead(MODE_PIN) == LOW) delay(10);
    allLedsOff();
    delay(100);
    return;
  }
  captureSamples();
  showTuning(estimateFrequency());
}