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());
}