【问题标题】:How to get the fundamental frequency using Harmonic Product Spectrum?如何使用谐波产品频谱获得基频?
【发布时间】:2017-01-06 22:07:22
【问题描述】:

我正在尝试从麦克风输入中获取音高。首先,我通过 FFT 将信号从时域分解到频域。在执行 FFT 之前,我已将汉明窗应用于信号。然后我得到了 FFT 的复杂结果。然后我将结果传递给谐波乘积频谱,在其中对结果进行下采样,然后将下采样的峰值相乘,并给出一个复数值。那我应该怎么做才能得到基频呢?

    public float[] HarmonicProductSpectrum(Complex[] data)
    {
        Complex[] hps2 = Downsample(data, 2);
        Complex[] hps3 = Downsample(data, 3);
        Complex[] hps4 = Downsample(data, 4);
        Complex[] hps5 = Downsample(data, 5);
        float[] array = new float[hps5.Length];

        for (int i = 0; i < array.Length; i++)
        {
            checked
            {
                array[i] = data[i].X * hps2[i].X * hps3[i].X * hps4[i].X * hps5[i].X;
            }
        }
        return array;
    }

    public Complex[] Downsample(Complex[] data, int n)
    {
        Complex[] array = new Complex[Convert.ToInt32(Math.Ceiling(data.Length * 1.0 / n))];
        for (int i = 0; i < array.Length; i++)
        {
            array[i].X = data[i * n].X;
        }
        return array;
    } 

我已经尝试使用,

    magnitude[i] = (float)Math.Sqrt(array[i] * array[i] + (data[i].Y * data[i].Y));  

在 HarmonicProductSpectrum 方法的 for 循环中。然后尝试使用最大的 bin,

        float max_mag = float.MinValue;
        float max_index = -1;

        for (int i = 0; i < array.Length / 2; i++)
            if (magnitude[i] > max_mag)
            {
                max_mag = magnitude[i];
                max_index = i;
            }

然后我尝试使用频率来获取频率,

    var frequency = max_index * 44100 / 1024;

但是对于 A4 音符(440 Hz),我得到了像 1248.926、1205,859、2454.785 这样的垃圾值,而这些值看起来不像 A4 的谐波。

非常感谢您的帮助。

【问题讨论】:

  • 你能把你正在使用的音频文件放到网上吗?我想看看这个,用 Python 等高级语言复制你的算法,因为你很可能有算法问题,但我想确保我使用的是相同的“A4 笔记”和你一样(我对音乐一无所知????)。
  • 在 windows 设置中仔细检查麦克风的采样率,它可能是 48,000 而不是你在最后一行的 44,100,这会将你的 1249 数字变成 1360,即只有 8% 的谐波而不是 17%..耸耸肩,看起来不对,但需要检查。
  • @AhmedFasih A4 note 谢谢艾哈迈德 :)
  • @Quantic 是的,它是 44100。我知道 FFT 是正确的,因为一旦我使用幅度获得最大 bin 的频率,它会为 440Hz 的 A4 音符提供 437.5Hz 和 445.312Hz。我想执行 HPS 以获得更好的频率分辨率
  • 引用:“八度音阶错误很常见(检测有时八度音阶太高)。要更正,请应用此规则:如果低于最初选择的音高的第二个峰值幅度大约是所选音高的 1/2并且幅度比高于阈值(例如,5 个谐波为 0.2),然后选择较低的八度音阶峰值作为当前帧的音高”。

标签: c# signal-processing fft audio-processing pitch


【解决方案1】:

我在 Python 中实现了谐波积谱,以确保您的数据和算法运行良好。

以下是我在将谐波积谱应用于完整数据集时看到的结果,汉明窗,具有 5 个下采样-乘法阶段:

这只是最底层的千赫兹,但在 1 KHz 以上的频谱几乎已经死了。

如果我将长音频片段分成 8192 个样本块(4096 个样本 50% 重叠),并对每个块进行汉明窗口并在其上运行 HPS,这就是 HPS 的矩阵。这是整个数据集上 HPS 频谱的电影。基频似乎相当稳定。

full source code is here——有很多代码可以帮助对数据进行分块并可视化在块上运行的 HPS 的输出,但核心 HPS 功能(从def hps(… 开始)很短。但它有一些技巧。

鉴于您发现峰值的奇怪频率,可能是您在从 0 到 44.1 KHz 的整个频谱上运行?您只想保留“正”频率,即从 0 到 22.05 KHz,并对其应用 HPS 算法(下采样-乘法)。

但是假设您从纯正频率频谱开始,正确地获取它的幅度,看起来您应该得到合理的结果。尝试保存您的HarmonicProductSpectrum 的输出,看看它是否与上述类似。

同样,完整的源代码位于https://gist.github.com/fasiha/957035272009eb1c9eb370936a6af2eb。 (在那里我尝试了另外几个谱估计器,来自 Scipy 的 Welch 方法和我的 Blackman-Tukey 谱估计器端口。我不确定您是否准备实施 HPS 或者您是否会考虑其他音高估计器,所以我m 将 Welch/Blackman-Tukey 结果留在那里。)


原创我写了这篇评论,但不得不不断修改它,因为它令人困惑,所以这里是一个迷你答案。

根据我对 this intro to HPS 的简短阅读,我认为在找到四个抽取的响应后,您的量级并没有正确。

你想要:

array[i] = sqrt(data[i] * Complex.conjugate(data[i]) *
                hps2[i] * Complex.conjugate(hps2[i]) *
                hps3[i] * Complex.conjugate(hps3[i]) *
                hps4[i] * Complex.conjugate(hps4[i]) *
                hps5[i] * Complex.conjugate(hps5[i])).X;

这使用sqrt(x * Complex.conjugate(x)) 技巧找到x 的震级,然后将所有 5 个震级相乘。

(实际上,它会将sqrt 移到产品之外,所以你只做一个sqrt,节省一些时间,但结果相同。所以也许这是另一个技巧。)

最后一招:将结果取为实数部分,因为有时由于浮点精度问题,一个微小的虚数部分(如 1e-15)仍然存在。

完成此操作后,array 应该只包含真实的floats,您可以应用 max-bin-finding。


如果没有Conjugate 方法,那么老式的方法应该可以工作:

public float mag2(Complex c) { return c.X * c.X + c.Y * c.Y; }

// in HarmonicProductSpectrum 
array[i] = sqrt(mag2(data[i]) * mag2(hps2[i]) * mag2(hps3[i]) * mag2(hps4[i]) * mag2(hps5[i]));

您在下面的 cmets 中建议的两种方法存在代数缺陷,但上面应该是正确的。我不确定当您将 Complex 分配给浮点数时 C# 会做什么——也许它使用了真正的组件?我原以为那是编译器错误,但使用上面的代码,您对复杂数据做正确的事情,并且只将 float 分配给 array[i]

【讨论】:

  • 问题是 Ahmed 我在这里没有使用 System.Numerics.Complex。我正在使用NAudio.dsp.Complex,它没有共轭功能。如何手动执行此操作?如果我做array[i] = data[i].X * hps2[i].X * hps3[i].X * hps4[i].X * hps5[i].X; 然后下一行magnitude[i] = sqrt (array[i]*array[i]); 并且你不需要虚数部分来表示幅度怎么办?在 FFT 中,我们从结果中得到幅度magnitude[i] = sqrt(result[i].X*result[i].X + result[i].Y*result[i].Y) ;
  • 抱歉,我忘了在 FFT 中提及 magnitude[i] = sqrt(result[i].X*result[i].X + result[i].Y*result[i].Y) ; 我正在手动获取共轭,就像 sqrt((real + imaginary*i) * (real - imaginary*i)) = sqrt (real^2 + imaginary^2) 因为 i^2 = -1 .. 当 HPS 中的虚部发生了什么计算量级?
  • 查看我的附录,它应该可以满足您的需求。我认为你的两个建议都有代数问题。
  • 我试过你所说的,但我得到的是 2411.719 和 2454.785。我应该下采样多少次,它真的会影响最终值吗?
  • @Giggity 查看我使用 Python 对算法进行可视化的编辑。抱歉,我对 C# 一无所知,但你的 WAVE 文件的算法似乎确实有效。
【解决方案2】:

要获得音高估计值,您必须将总的 bin 频率估计值除以用于该总和的下采样率。

补充:您还应该对幅度求和(abs()),而不是取复数和的幅度。

但是谐波积谱算法 (HPS),尤其是在仅使用整数比率的下采样时,通常不能提供更好的音高估计分辨率。相反,它提供了比使用单个裸 FFT 幅度峰值更强大的粗略音高估计(不太可能被谐波欺骗),用于具有弱或缺少基本频谱内容的连续泛音丰富的音色。

如果您知道如何通过分数比(使用插值等)对频谱进行下采样,则可以尝试更细粒度的下采样,以便从 HPS 中获得更好的音高估计。或者,您可以使用 HPS 结果通知您使用另一种音高或频率估计方法搜索的较窄频率范围。

【讨论】:

  • 你能给我一个示例代码来将复杂的 HPS 结果转换为频率(浮点)吗?通过音频通过滤波器,我可以过滤掉谐波,对吧?
  • 您的幅度()函数看起来是正确的。如果您过滤掉您认为的谐波频段,您可能最终会失去一些音色和音高。
  • 好吧,假设我有 437.5Hz 和 445.312Hz。如何插入这两个值?似乎我应该像这样 (437.50*x + 445.312*y) / 2 将它乘以某个值(比如说 x 和 y),并且应该将它除以值的数量,在这种情况下为 2。我对么? x 和 y 值应该是多少?
  • 查找抛物线插值和 Sinc 内核插值(带限重建)。
  • 好的,我试试
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
相关资源
最近更新 更多