如何使用谐波乘积谱获取基频?
How to get the fundamental frequency using Harmonic Product Spectrum?
我正在尝试从麦克风输入中获取音调。首先,我通过 FFT 将信号从时域分解到频域。在执行 FFT 之前,我已将 Hamming window 应用于信号。然后我得到 FFT 的复杂结果。然后我将结果传递给 Harmonic product spectrum,在其中对结果进行下采样,然后将下采样的峰值相乘并给出一个复数值。那我应该怎么做才能得到基频呢?
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 的谐波。
非常感谢您的帮助。
要获得音调估计值,您必须将求和的 bin 频率估计值除以用于该总和的下采样率。
已补充:您还应该对幅值求和 (abs()),而不是取复数和的幅值。
但是谐波乘积频谱算法 (HPS),尤其是仅使用整数比率的下采样时,通常不会提供更好的音调估计分辨率。相反,它提供了比使用单个裸 FFT 幅度峰值更稳健的粗略音高估计(不太可能被谐波愚弄)用于具有弱或缺失基本频谱内容的连续泛音丰富音色。
如果您知道如何通过分数比率对频谱进行下采样(使用插值等),您可以尝试更细粒度的下采样以从 HPS 中获得更好的音高估计。或者,您可以使用 HPS 结果通知您使用其他音高或频率估计方法搜索的较窄频率范围。
我在 Python 中实现了谐波乘积谱,以确保您的数据和算法运行良好。
这是我将谐波乘积谱应用于完整数据集时看到的结果,Hamming-windowed,具有 5 个下采样乘法阶段:
这只是底部的千赫兹,但频谱在 1 KHz 以上几乎是死的。
如果我将长音频剪辑分成 8192 个样本块(4096 个样本 50% 重叠)和 Hamming-window 每个块和 运行 HPS,这是HPS 矩阵。这是整个数据集上 HPS 光谱的电影。基频好像挺稳定的。
full source code is here—有很多代码可以帮助对数据进行分块并可视化 HPS 运行ning 在分块上的输出,但是核心 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 分配给 float 时,我不确定 C# 会做什么——也许它使用了真正的组件?我原以为这是一个编译器错误,但是使用上面的代码,你对复杂数据做了正确的事情,并且只将 float
分配给 array[i]
.
我正在尝试从麦克风输入中获取音调。首先,我通过 FFT 将信号从时域分解到频域。在执行 FFT 之前,我已将 Hamming window 应用于信号。然后我得到 FFT 的复杂结果。然后我将结果传递给 Harmonic product spectrum,在其中对结果进行下采样,然后将下采样的峰值相乘并给出一个复数值。那我应该怎么做才能得到基频呢?
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 的谐波。
非常感谢您的帮助。
要获得音调估计值,您必须将求和的 bin 频率估计值除以用于该总和的下采样率。
已补充:您还应该对幅值求和 (abs()),而不是取复数和的幅值。
但是谐波乘积频谱算法 (HPS),尤其是仅使用整数比率的下采样时,通常不会提供更好的音调估计分辨率。相反,它提供了比使用单个裸 FFT 幅度峰值更稳健的粗略音高估计(不太可能被谐波愚弄)用于具有弱或缺失基本频谱内容的连续泛音丰富音色。
如果您知道如何通过分数比率对频谱进行下采样(使用插值等),您可以尝试更细粒度的下采样以从 HPS 中获得更好的音高估计。或者,您可以使用 HPS 结果通知您使用其他音高或频率估计方法搜索的较窄频率范围。
我在 Python 中实现了谐波乘积谱,以确保您的数据和算法运行良好。
这是我将谐波乘积谱应用于完整数据集时看到的结果,Hamming-windowed,具有 5 个下采样乘法阶段:
这只是底部的千赫兹,但频谱在 1 KHz 以上几乎是死的。
如果我将长音频剪辑分成 8192 个样本块(4096 个样本 50% 重叠)和 Hamming-window 每个块和 运行 HPS,这是HPS 矩阵。这是整个数据集上 HPS 光谱的电影。基频好像挺稳定的。
full source code is here—有很多代码可以帮助对数据进行分块并可视化 HPS 运行ning 在分块上的输出,但是核心 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 分配给 float 时,我不确定 C# 会做什么——也许它使用了真正的组件?我原以为这是一个编译器错误,但是使用上面的代码,你对复杂数据做了正确的事情,并且只将 float
分配给 array[i]
.