65.9K
CodeProject 正在变化。 阅读更多。
Home

FFT 吉他调音器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (72投票s)

2009年1月2日

MIT

4分钟阅读

viewsIcon

589561

downloadIcon

30294

使用快速傅里叶变换计算捕获音频声音的基频

引言

本文介绍如何使用快速傅里叶变换(FFT)算法来计算捕获的音频声音的基本频率。此外,我们将看到如何将该算法应用于分析实时声音以构建一个简单的吉他调音器:该代码提供了解决计算演奏音高基本频率问题的解决方案。

背景

计算机可以通过连接到声卡的麦克风捕获实时声音/音乐。现代声卡可以捕获数字信号。数字信号是一组在均匀间隔的时间点上获取的量化声音值。数字信号不提供关于声音中存在的频率的任何信息。要确定这一点,需要对数据进行分析。

短时傅里叶变换(STFT)可以表示信号的相位和幅度。STFT的结果可用于生成信号的频谱图:幅度平方随时间和频率的变化。我们将使用快速傅里叶变换(FFT)来生成短时间段信号的频谱图。计算出频谱图后,可以通过查找幅度平方最大值的索引来确定基本频率。改进的算法会找到几个这样的位置,即候选频率箱,其中幅度平方值接近最大值,然后通过使用信号数据进一步分析它们以验证候选基本频率。

当乐器演奏一个音符时,声音波由弦、空气或扬声器产生——乐器产生一个音乐音符。音乐音符的一个特性是音高(基本频率)。传统上,音乐字母频率按八度音程划分,然后再按半音划分。一个八度音程有12个命名音高:C(主音)、C#、D、D#、E、F、F#、G、G#、A、A# 和 B。八度音程也有名称:大、小、单划、双划等。“标准音高”(A4)的声波基本频率等于 440 Hz。两个相邻音符的频率相差 21/12,而两个相邻八度音程中同名音符的频率相差 2。

表格:音符及其基本频率
音符名称 传统八度名称(科学),Hz
大(2) 小(3) 单划(4) 双划(5)
C 65.4064 130.8128 261.6256 523.2511
C# 69.2957 138.5913 277.1826 554.3653
D 73.4162 146.8324 293.6648 587.3295
D# 77.7817 155.5635 311.1270 622.2540
E 82.4069 164.8138 329.6276 659.2551
F 87.3071 174.6141 349.2282 698.4565
F# 92.4986 184.9972 369.9944 739.9888
G 97.9989 195.9977 391.9954 783.9909
G# 103.8262 207.6523 415.3047 830.6094
A 110.0000 220.0000 440.0000 880.0000
A# 116.5409 233.0819 466.1638 932.3275
B 123.4708 246.9417 493.8833 987.7666

典型的(六弦)吉他通常演奏从大八度到双划八度音程的音高。开放弦的音高(E2、A2、D3、G3、B3 和 E4)在表中以粗体显示。

Using the Code

该解决方案包含三个项目:主窗口应用程序(FftGuitarTuner)、声音分析库(SoundAnalysis)和声音捕获库(SoundCapture)。解决方案的核心和SoundAnalysis项目是 FFT 算法(请参阅SoundAnalysis.FftAlgorithm类的Calculate方法)。

// bit reversal
ComplexNumber[] data = new ComplexNumber[length];
for (int i = 0; i < x.Length; i++)
{
    int j = ReverseBits(i, bitsInLength);
    data[j] = new ComplexNumber(x[i]);
}

// Cooley-Tukey 
for (int i = 0; i < bitsInLength; i++)
{
    int m = 1 << i;
    int n = m * 2;
    double alpha = -(2 * Math.PI / n);

    for (int k = 0; k < m; k++)
    {
        // e^(-2*pi/N*k)
        ComplexNumber oddPartMultiplier = 
           new ComplexNumber(0, alpha * k).PoweredE();

        for (int j = k; j < length; j += n)
        {
            ComplexNumber evenPart = data[j];
            ComplexNumber oddPart = oddPartMultiplier * data[j + m];
            data[j] = evenPart + oddPart;
            data[j + m] = evenPart - oddPart;
        }
    }
}

// calculate spectrogram
double[] spectrogram = new double[length];
for (int i = 0; i < spectrogram.Length; i++)
{
    spectrogram[i] = data[i].AbsPower2();
}

算法的数据来自声卡捕获缓冲区。抽象的SoundCapture.SoundCaptureBase实用程序类是 DirectSound 的CaptureCaptureBuffer类的适配器,它有助于封装缓冲和设置音频格式参数。该应用程序需要 Microsoft DirectX 9 运行时组件才能从麦克风捕获实时声音。

Main Application Form

图:主应用程序窗体

启动应用程序后,选择声音设备并演奏一个音符。应用程序将捕获实时声音并计算信号的当前基本频率。这些信息可用于调优吉他。

关注点

为了计算快速傅里叶变换,使用了 Cooley-Tukey 算法。该算法对于所需任务具有良好的性能。为了挑战该算法,应用程序实时分析约 22,000 个样本块:声音以 44,100 Hz 的速率和 16 位样本大小捕获,并且分析每秒执行两次。

声音分析库可用于音调、背景噪音、声音或语音检测。连续声音的频谱图系列可以显示为二维(或三维)图像以进行可视化呈现。

参考文献

  1. “音乐音符”,维基百科
  2. “短时傅里叶变换”,维基百科
  3. “快速傅里叶变换”,维基百科
  4. “Cooley-Tukey FFT 算法”,维基百科

历史

  • 2009年1月1日:初始版本
  • 2009年1月2日:添加了算法代码片段
  • 2010年8月3日:修正了文章中的拼写错误;新的频率检测算法
© . All rights reserved.