gokul/acoustic-guitar-tuner

This code defines the physical pin configuration and footprint layouts for the ATtiny1616 microcontroller and a matching tactile switch component, including their key hardware connection points, dimensions, and 3D models.

Version
1.0.10
License
unset
Stars
1

firmware/acoustic_tuner_attiny1616/acoustic_tuner_attiny1616.ino

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

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

// Factory calibration: multiply by reference frequency / displayed frequency.
// The internal RC clock must be calibrated before a +/-4-cent claim is valid.
constexpr float SAMPLE_CLOCK_SCALE = 1.0f;

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() {
  return tuner::estimate(samples) * SAMPLE_CLOCK_SCALE;
}

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(1731.234049f * logf(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 = 1731.234049f * logf(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) {
    // Sequence one LED at a time to avoid a nine-LED current pulse on CR2032.
    const uint8_t indicators[] = {NOTE_LEDS[0], NOTE_LEDS[1], NOTE_LEDS[2],
      NOTE_LEDS[3], NOTE_LEDS[4], NOTE_LEDS[5], FLAT_LED, TUNE_LED, SHARP_LED};
    allLedsOff();
    do {
      for (uint8_t pin : indicators) {
        digitalWrite(pin, HIGH);
        delay(150);
        digitalWrite(pin, LOW);
      }
    } while (digitalRead(MODE_PIN) == LOW);
    allLedsOff();
    delay(100);
    return;
  }
  captureSamples();
  showTuning(estimateFrequency());
}