我整理了一个小例子,尝试使用谐波乘积频谱算法来提取简单正弦波的音高。我不确定我的实现,如果我完全理解了一切,这就是为什么我希望在这里得到一些帮助。
代码在这里:https : //gist.github.com/akuehntopf/4da9bced2cb88cfa2d19(不过,您需要 JTransform 和 JavaFX 来编译)。
请使用以下图表查看在处理过程中数据会发生什么情况。注意:我使用的是 160Hz 的正弦波,采样率为 8000。声音数据的持续时间为 1 秒。
首先显示正弦波(见第一张图片)。
其次,我正在对数据应用汉明窗(第二张图片)
然后我根据以下计算数据的功率谱
private float[] powerSpectrum(float[] window) {
float[] powerSpectrum = new float[window.length];
float[] fftBuffer = new float[window.length * 2 + 1];
System.arraycopy(window, 0, fftBuffer, 0, window.length);
FloatFFT_1D fft = new FloatFFT_1D(window.length);
fft.realForward(fftBuffer);
for (int i=0; i < fftBuffer.length / 2 - 1; i++) {
float real = fftBuffer[2*i];
float imag = fftBuffer[2*i+1];
powerSpectrum[i] = (float)Math.sqrt(real*real + imag * imag);
}
return powerSpectrum;
}
这给了我第三张图中的频谱。
观察到峰值已经大约在正确的位置,但我们选择 HPS,所以继续(我不太确定从这里开始的所有内容):
接下来,我将信号与其压缩形式相乘数次(随着压缩的增加)......
// 4. Compress
float[] spectrumCopy = new float[spectrum.length];
System.arraycopy(spectrum, 0, spectrumCopy, 0, spectrum.length);
for (int compression = 2; compression < 4; compression++) {
for (int i = 1; i < spectrum.length; i++) {
spectrum[i] = spectrum[i] * getCompressedSample(spectrumCopy, 1, compression, i);
}
}
它使用函数
private float getCompressedSample(float[] buffer, int offset, int compression, int loc) {
if (offset + loc * compression < buffer.length) {
return buffer[offset + loc * compression];
}
return 0;
}
我在互联网上的某个地方找到了它。但我的理解是,我们压缩功率谱 n 次,同时仅每 2 次,然后仅每 3 次,以此类推功率谱中的样本。然后将原始光谱与这些压缩光谱中的每一个相乘。
我得到的结果是第四张图
观察峰是否发生了变化。
根据我的理解,下一步将是找到具有最高峰值的 bin,进行插值(我使用二次插值)并使用以下公式重新计算音调频率:
private float getFrequencyForIndex(int index, int size, int rate) {
float freq = (float)index * (float)rate / (float)size;
return freq;
}
然而,这给了我错误的结果。我几乎被困住了。对此主题的任何帮助将不胜感激!提前致谢!
对于下一步,我希望能够从乐器(吉他/尤克里里)中提取音高频率数据,但当然首先要做的是:-)

