Устройство для распознавания нот на Arduino
Зачастую начинающим музыкантам не хватает навыков в определении той или иной прозвучавшей ноты, особенно это касается задач подбора композиций на слух. В этом деле на помощь может прийти устройство распознавания нот, которое можно собрать самостоятельно.

Данный проект позволяет создать устройство на основе Arduino, которое отображает приблизительную частоту, а также музыкальную ноту звука, полученного от микрофонного датчика.
Схема этого проекта очень проста и состоит из Arduino и подключенного к нему модуля микрофона.

Здесь аналоговый выход звукового модуля подключается к аналоговому входу A0 платы Arduino Uno. Аналоговый сигнал дискретизируется и квантуется (оцифровывается). Автокорреляция, взвешивание и настройка в коде используются для нахождения основной частоты с использованием первых 3 периодов. Затем приблизительная основная частота сравнивается с частотами в октавах 3, 4 и 5 для определения ближайшей частоты музыкальной ноты. Наконец угаданная нота для ближайшей частоты выводится на экран.
Для того, чтобы устройство работало, загрузите приведенный далее код в Arduino.
#define SAMPLES 128 //Максимально 128 для Arduino Uno
#define SAMPLING_FREQUENCY 2048 //Fs = на основе Найквиста, должно быть в 2 раза больше ожидаемой частоты.
#define OFFSETSAMPLES 40 //используется для калибровки
#define TUNER -3 //Отрегулируйте, пока C3 не станет 130.50
float samplingPeriod;
unsigned long microSeconds;
int X[SAMPLES]; //создать вектор размером SAMPLES для хранения реальных значений
float autoCorr[SAMPLES]; //создать вектор размером SAMPLES для хранения мнимых значений
float storedNoteFreq[12] = {130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185, 196, 207.65, 220, 233.08, 246.94};
int sumOffSet = 0;
int offSet[OFFSETSAMPLES]; //создать вектор смещения
int avgOffSet; //создать вектор смещения
int i, k, periodEnd, periodBegin, period, adjuster, noteLocation, octaveRange;
float maxValue, minValue;
long sum;
int thresh = 0;
int numOfCycles = 0;
float signalFrequency, signalFrequency2, signalFrequency3, signalFrequencyGuess, total;
byte state_machine = 0;
int samplesPerPeriod = 0;
void setup()
{
Serial.begin(115200);
}
void loop()
{
//*****************************************************************
//Секция калибровки
//*****************************************************************
Serial.println("Calabrating. Please do not play any notes during calabration.");
for (i = 0; i < OFFSETSAMPLES; i++)
{
offSet[i] = analogRead(0); //Считывает значение с аналогового контакта 0 (A0), квантует его и сохраняет как реальный термин.
//Serial.println(offSet[i]); //используйте это, чтобы настроить модуль обнаружения звука примерно на половину или 512, когда звук не воспроизводится.
sumOffSet = sumOffSet + offSet[i];
}
samplesPerPeriod = 0;
maxValue = 0;
//*****************************************************************
//Подготовка приема входного сигнала от A0
//*****************************************************************
avgOffSet = round(sumOffSet / OFFSETSAMPLES);
Serial.println("Counting down.");
delay(1000);
Serial.println("3");
delay(1000);
Serial.println("2");
delay(1000);
Serial.println("1");
delay(1000);
Serial.println("Play your note!");
delay(250);
//*****************************************************************
//Соберем выборки SAMPLES из A0 с периодом выборки samplingPeriod
//*****************************************************************
samplingPeriod = 1.0 / SAMPLING_FREQUENCY; //Период в микросекундах
for (i = 0; i < SAMPLES; i++)
{
microSeconds = micros(); //Возвращает количество микросекунд с момента запуска текущего скрипта Arduino.
X[i] = analogRead(0); //Считывает значение с аналогового контакта 0 (A0), квантует его и сохраняет как реальный термин.
/*оставшееся время ожидания между выборками, если необходимо, в секундах */
while (micros() < (microSeconds + (samplingPeriod * 1000000)))
{
//ничего не делаем, просто ждем
}
}
//*****************************************************************
//Автокорреляционная функция
//*****************************************************************
for (i = 0; i < SAMPLES; i++) //i=адержка
{
sum = 0;
for (k = 0; k < SAMPLES - i; k++) //соответствие сигнала с задержанным сигналом
{
sum = sum + (((X[k]) - avgOffSet) * ((X[k + i]) - avgOffSet)); //X [k] - это сигнал, а X [k + i] - версия с задержкой
}
autoCorr[i] = sum / SAMPLES;
// конечный автомат обнаружения пика
if (state_machine==0 && i == 0)
{
thresh = autoCorr[i] * 0.5;
state_machine = 1;
}
else if (state_machine == 1 && i>0 && thresh < autoCorr[i] && (autoCorr[i]-autoCorr[i-1])>0) //state_machine = 1, найти 1 период для использования первого цикла
{
maxValue = autoCorr[i];
}
else if (state_machine == 1&& i>0 && thresh < autoCorr[i-1] && maxValue == autoCorr[i-1] && (autoCorr[i]-autoCorr[i-1])<=0)
{
periodBegin = i-1;
state_machine = 2;
numOfCycles = 1;
samplesPerPeriod = (periodBegin - 0);
period = samplesPerPeriod;
adjuster = TUNER+(50.04 * exp(-0.102 * samplesPerPeriod));
signalFrequency = ((SAMPLING_FREQUENCY) / (samplesPerPeriod))-adjuster; // f = fs/N
}
else if (state_machine == 2 && i>0 && thresh < autoCorr[i] && (autoCorr[i]-autoCorr[i-1])>0)
{
maxValue = autoCorr[i];
}
else if (state_machine == 2&& i>0 && thresh < autoCorr[i-1] && maxValue == autoCorr[i-1] && (autoCorr[i]-autoCorr[i-1])<=0)
{
periodEnd = i-1;
state_machine = 3;
numOfCycles = 2;
samplesPerPeriod = (periodEnd - 0);
signalFrequency2 = ((numOfCycles*SAMPLING_FREQUENCY) / (samplesPerPeriod))-adjuster; // f = (2*fs)/(2*N)
maxValue = 0;
}
else if (state_machine == 3 && i>0 && thresh < autoCorr[i] && (autoCorr[i]-autoCorr[i-1])>0)
{
maxValue = autoCorr[i];
}
else if (state_machine == 3&& i>0 && thresh < autoCorr[i-1] && maxValue == autoCorr[i-1] && (autoCorr[i]-autoCorr[i-1])<=0)
{
periodEnd = i-1;
state_machine = 4;
numOfCycles = 3;
samplesPerPeriod = (periodEnd - 0);
signalFrequency3 = ((numOfCycles*SAMPLING_FREQUENCY) / (samplesPerPeriod))-adjuster; // f = (3*fs)/(3*N)
}
}
//*****************************************************************
//Анализ результатов
//*****************************************************************
if (samplesPerPeriod == 0)
{
Serial.println("Hmm..... I am not sure");
}
else
{
//подготовить весовую функцию
total = 0;
if (signalFrequency !=0)
{
total = 1;
}
if(signalFrequency2 !=0)
{
total = total + 2;
}
if (signalFrequency3 !=0)
{
total = total + 3;
}
//рассчитать частоту с помощью весовой функции
signalFrequencyGuess = ((1/total) * signalFrequency) + ((2/total) * signalFrequency2) + ((3/total) * signalFrequency3); //найти взвешенную частоту
Serial.print("The note you played is approximately ");
Serial.print(signalFrequencyGuess); //Вывести предполагаемую частоту.
Serial.println(" Hz.");
//найти диапазон октав на основе догадки
octaveRange=3;
while (!(signalFrequencyGuess >= storedNoteFreq[0]-7 && signalFrequencyGuess <= storedNoteFreq[11]+7 ))
{
for(i = 0; i < 12; i++)
{
storedNoteFreq[i] = 2 * storedNoteFreq[i];
}
octaveRange++;
}
//Найти ближайшую ноту
minValue = 10000000;
noteLocation = 0;
for (i = 0; i < 12; i++)
{
if(minValue> abs(signalFrequencyGuess-storedNoteFreq[i]))
{
minValue = abs(signalFrequencyGuess-storedNoteFreq[i]);
noteLocation = i;
}
}
//Вывести ноту
Serial.print("I think you played ");
if(noteLocation==0)
{
Serial.print("C");
}
else if(noteLocation==1)
{
Serial.print("C#");
}
else if(noteLocation==2)
{
Serial.print("D");
}
else if(noteLocation==3)
{
Serial.print("D#");
}
else if(noteLocation==4)
{
Serial.print("E");
}
else if(noteLocation==5)
{
Serial.print("F");
}
else if(noteLocation==6)
{
Serial.print("F#");
}
else if(noteLocation==7)
{
Serial.print("G");
}
else if(noteLocation==8)
{
Serial.print("G#");
}
else if(noteLocation==9)
{
Serial.print("A");
}
else if(noteLocation==10)
{
Serial.print("A#");
}
else if(noteLocation==11)
{
Serial.print("B");
}
Serial.println(octaveRange);
}
//*****************************************************************
//Остановка выполнения здесь. Нажмите кнопку сброса на Arduino, чтобы перезагрузить
//*****************************************************************
while (1);
}
© digitrode.ru