Как определить тон строки из FFT
Я получил спектр от преобразования Фурье. Это выглядит так:
Полиция просто проходила рядом
Цвет представляет интенсивность.
Ось X - это время.
Ось Y - это частота - где 0 сверху.
В то время как свист или полицейская сирена оставляют только один след, многие другие тона, похоже, содержат много гармонических частот.
Электрогитара подключена прямо к микрофону (стандартная настройка)
Очень плохо то, что, как вы можете видеть, нет большой интенсивности - есть 2-3 частоты, которые почти равны.
Я написал алгоритм обнаружения пиков, чтобы выделить самый значительный пик:
function findPeaks(data, look_range, minimal_val) {
if(look_range==null)
look_range = 10;
if(minimal_val == null)
minimal_val = 20;
//Array of peaks
var peaks = [];
//Currently the max value (that might or might not end up in peaks array)
var max_value = 0;
var max_value_pos = 0;
//How many values did we check without changing the max value
var smaller_values = 0;
//Tmp variable for performance
var val;
var lastval=Math.round(data.averageValues(0,4));
//console.log(lastval);
for(var i=0, l=data.length; i<l; i++) {
//Remember the value for performance and readibility
val = data[i];
//If last max value is larger then the current one, proceed and remember
if(max_value>val) {
//iterate the ammount of values that are smaller than our champion
smaller_values++;
//If there has been enough smaller values we take this one for confirmed peak
if(smaller_values > look_range) {
//Remember peak
peaks.push(max_value_pos);
//Reset other variables
max_value = 0;
max_value_pos = 0;
smaller_values = 0;
}
}
//Only take values when the difference is positive (next value is larger)
//Also aonly take values that are larger than minimum thresold
else if(val>lastval && val>minimal_val) {
//Remeber this as our new champion
max_value = val;
max_value_pos = i;
smaller_values = 0;
//console.log("Max value: ", max_value);
}
//Remember this value for next iteration
lastval = val;
}
//Sort peaks so that the largest one is first
peaks.sort(function(a, b) {return -data[a]+data[b];});
//if(peaks.length>0)
// console.log(peaks);
//Return array
return peaks;
}
Идея состоит в том, чтобы пройтись по данным и запомнить значение, превышающее пороговое значение minimal_val
, Если следующий look_range
значения меньше, чем выбранное значение, оно считается пиковым. Этот алгоритм не очень умный, но его очень легко реализовать.
Тем не менее, он не может сказать, какая основная частота строки, как я и ожидал:
Красные точки выделяют самый сильный пик
Вот jsFiddle, чтобы увидеть, как это действительно работает (или, скорее, не работает).
1 ответ
То, что вы видите в спектре струнного тона, - это набор гармоник в
f0, 2*f0, 3*f0, ...
с f0 является основной частотой или высотой тона вашей струны.
Для оценки f0 по спектру (выход FFT, значение abs, вероятно, логарифмический) вы должны искать не самый сильный компонент, а расстояние между всеми этими гармониками.
Один очень хороший способ сделать это - второе (обратное) БПФ (абс, реальное) спектра. Это дает сильную линию при t0 == 1/f0.
Последовательность fft -> abs() -> fft-1 эквивалентна вычислению функции автокорреляции (ACF) благодаря теореме Винера – Хинчина.
Точность такого подхода зависит от длины FFT (или ACF) и вашей частоты дискретизации. Вы можете значительно улучшить точность, если интерполируете "реальный" максимум между точками выборки результата, используя функцию sinc.
Для еще лучших результатов вы можете исправить промежуточный спектр: большинство звуков имеют средний розовый спектр. Если вы усиливаете более высокие частоты (в соответствии с обратным розовым спектром) перед обратным БПФ, АКФ будет "лучше" (в большей степени учитывается высшая гармоника, что повышает точность).