如何从FFT中检测字符串音调

5
我从傅里叶变换得到了频谱。它看起来像这样:sound spektrogram created by passing police
警察刚好经过 颜色代表强度。
X轴表示时间,
Y轴表示频率,其中0位于顶部。
当吹哨或警笛只留下一条痕迹时,许多其他声音似乎包含大量谐波频率。 sound spektrogram of EHGDAE tuned guitar电吉他直接插入麦克风(标准调弦) 真正糟糕的是,如您所见,没有主要强度-有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个值都比这个选择的值小,则被认为是峰值。这个算法并不是很智能,但实现起来非常容易。然而,它不能确定字符串的主要频率,就像我之前预料的那样。 Guitar strings now with highlighted strongest frequency
红色圆点标出了最强的峰值。你可以点击这里查看它的运行情况(或者说不太运行)。

1
与其试图重新发明轮子,你应该真正使用其中一个流行的音高检测算法,例如谐波产品谱。 - Paul R
1
你正在解决数字音频工程师几十年来一直在摆弄的问题。据我所知,到目前为止最好的结果是Melodyne。如果需要,请查看这个线程。 - MrPaulch
参见:FFT音高检测-旋律提取:https://dev59.com/6l3Va4cB1Zd3GeqPC64i - DrKoch
1个回答

3
你在弦音谱中看到的是一组和弦,其频率为:

f0,2*f0,3*f0,...

其中f0是您弦乐音的基频或音高。

要从频谱(FFT的输出,绝对值,可能是对数)中估计f0,请不要寻找最强的分量,而是查找所有这些和弦之间的距离

一个非常好的方法是对(abs,real)频谱进行第二个(反)FFT。这会产生一个强线在t0 == 1 / f0处。

序列fft-> abs()-> fft-1等效于通过自相关函数(ACF)计算,感谢Wiener-Khinchin定理

此方法的精度取决于FFT(或ACF)的长度和采样率。如果使用Sinc函数在结果的采样点之间插值来插值“实际”峰值,则可以显着提高精度。

对于更好的结果,您可以校正中间频谱:大多数声音具有平均粉色频谱。如果在反FFT之前放大更高的频率(根据反粉色频谱),则ACF会更好(它将更多地考虑更高的谐波,从而提高准确性)。


我现在点赞了,但稍后我会测试一下。感谢您的贡献。 - Tomáš Zato
请您详细说明一下“使用sinc函数在结果的采样点之间插值出‘真实’最大值”的含义是什么?我不确定“采样点之间的最大值”指的是什么。请注意,我在这方面没有任何大学学位。 - Tomáš Zato
我该如何选择这些值?我认为随机选择或常量偏移不是我想要的。另外,你所说的公式是使用无限和的Sinc插值吗?那里的alpha、x和k代表什么? - Tomáš Zato
你的问题的答案无法放在评论区中。请开一个新问题。 - DrKoch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接