我试图从麦克风输入中获取音高。首先,我通过FFT将信号从时域分解到频域。在执行FFT之前,我已将Hamming窗口应用于信号。然后我得到了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的谐波。
非常感谢帮助。
答案 0 :(得分:1)
要获得音高估计值,您必须将您的sumed bin频率估计值除以用于该总和的下采样比率。
补充:您还应该对幅度(abs())求和,而不是取复数和的幅度。
但是谐波乘积谱算法(HPS),尤其是仅使用整数比率的下采样时,通常不能提供更好的音调估计分辨率。相反,它提供了更强大的粗略音调估计(不太可能被谐波愚弄),而不是使用单个裸FFT幅度峰值来获得具有弱或基本频谱内容缺失的连续泛音丰富音色。
如果您知道如何通过分数比率(使用插值等)对频谱进行下采样,则可以尝试更精细的下采样,以便从HPS中获得更好的音高估计。或者您可以使用HPS结果通知您使用其他音高或频率估算方法搜索的较窄频率范围。
答案 1 :(得分:1)
我在Python中实现了谐波产品频谱,以确保您的数据和算法运行良好。
以下是我将谐波产品频谱应用于完整数据集时的看法,汉明窗口,具有5个下采样乘法阶段:
这只是底部的千赫兹,但光谱几乎已经超过1 KHz。
如果我将长音频片段分成8192个样本块(4096样本50%重叠)和Hamming-window每个块并在其上运行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,或者你是否会考虑其他音高估计器,所以我'我离开了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
应该只包含真实的float
,您可以应用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]));
您在下面的评论中提出了两种方法的代数缺陷,但上述内容应该是正确的。当你将一个Complex分配给一个浮点数时,我不确定C#会做什么 - 也许它使用真正的组件?我原以为这是一个编译错误,但是使用上面的代码,你对复杂的数据做了正确的事情,并且只将float
分配给array[i]
。