我正在尝试为Android手机制作简单的音高检测应用程序。我已经拿到手机显示我已计算的自相关值的图表,这些值存储在一维的双精度数组中。现在我需要弄清楚如何检测数组中的重复模式。这是自相关图的屏幕截图,我哼着稳定的音调:
我尝试在这个幻灯片中给出了1D数组的递归峰值查找算法:http://courses.csail.mit.edu/6.006/spring11/lectures/lec02.pdf但我在Android上出现了内存错误。
接下来,我尝试使用此算法实现类似于查找二阶导数的内容:https://stackoverflow.com/a/3869172但是来自手机的自相关值非常紧张,以至于找到了太多的最小值和最大值。
我需要弄清楚如何做的是对自相关数据应用某种滤波器以使其平滑但我在数学上很糟糕并且不知道该怎么做。我尝试将自相关值四舍五入到只有几个小数位但我没有得到我想要的结果。
基本上我需要帮助弄清楚如何找到重复模式的整体最大值(实际上只是第一个可能没问题)。在上面的屏幕截图中,图案是一个高峰,然后是两个较短的峰。我需要知道第二个高峰何时发生,以便我可以计算出音高。
答案 0 :(得分:0)
您正在尝试估算样本数据中幅度峰值的频率。您可以这样做而无需手动查找估计峰值然后计算出频率。相反,您可以使用快速傅里叶变换,这可以从幅度与时间的关系图转换为频率与时间的关系图。这里概括地描述了这个概念http://en.wikipedia.org/wiki/Fast_Fourier_transform
...并且有几个Java库实现了转换,包括 Apache Commons Math - http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/transform/FastFourierTransformer.html 和 JTransform - https://sites.google.com/site/piotrwendykier/software/jtransforms
答案 1 :(得分:0)
要回答我自己的问题,这就是我最终要做的事情。 (对不起,我花了很长时间才回到这个问题来发布答案。)
double frequency = findFrequency(lowPassFilter(signal));
private double findFrequency(double[] signal) {
int[] signs = new int[signal.length];
for (int i = 0; i < signal.length - 1; i++) {
double diff = signal[i+1] - signal[i];
if (diff < 0) {
signs[i] = -1;
} else if (diff == 0) {
signs[i] = 0;
} else {
signs[i] = 1;
}
}
int[] secondDerivatives = new int[signs.length];
for (int i = 0; i < signs.length - 1; i++) {
secondDerivatives[i] = signs[i+1] - signs[i];
}
double biggestSoFar = 0.0;
int indexOfBiggestSoFar = 0;
for (int i = 0; i < secondDerivatives.length; i++) {
if (secondDerivatives[i] == -2 && signal[i] > biggestSoFar) {
biggestSoFar = signal[i];
indexOfBiggestSoFar = i;
}
}
return 1 / (double)indexOfBiggestSoFar * AudioListener.SAMPLE_RATE;
}
private double[] lowPassFilter(double[] signal) {
double alpha = 0.15;
for (int i = 1; i < signal.length; i++ ) {
signal[i] = signal[i] + alpha * (signal[i] - signal[i-1]);
}
return signal;
}