我正在尝试使用自相关从录制的声音(44.1 kHz)中获得音高等级。 我在做什么基本上在这里描述:http://cnx.org/content/m11714/latest/并在这里实现:http://code.google.com/p/yaalp/source/browse/trunk/csaudio/WaveAudio/WaveAudio/PitchDetection.cs(使用PitchDetectAlgorithm.Amdf的部分)
因此,为了检测音高等级,我建立了一个数组,其中C2到B3(2个八度音阶)的频率具有归一化的相关性,并选择具有最高值的数组(首先进行“1 - 相关”变换)不搜索最小值但最大值)
我用生成的音频(简单的窦)测试了它:
data[i] = (short)(Math.Sin(2 * Math.PI * i/fs * freq) * short.MaxValue);
但它仅适用于低于B4的输入频率。 调查生成的数组我发现从G3开始的另一个窥视演变最终变得比正确的更大。我的B4被检测为E. 改变分析频率的数量根本没有帮助。
我的缓冲区大小是4000个样本,B4的频率是~493Hz,所以我想不出这个失败的原因。是否还有对频率或缓冲区大小的限制?那里出了什么问题?
我知道我可以像Performous一样使用FFT,但使用这种方法看起来很简单,并且还提供了可用于显示可视化的加权频率。我不想轻易放弃它,至少理解为什么会失败。
更新:使用的核心功能:
private double _GetAmdf(int tone)
{
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period
int accumDist = 0; // accumulated distances
int sampleIndex = 0; // index of sample to analyze
// Start value= index of sample one period ahead
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
{
// calc distance (correlation: 1-dist/IntMax*2) to corresponding sample in next period (0=equal .. IntMax*2=totally different)
int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
accumDist += dist;
}
return 1.0 - (double)accumDist / Int16.MaxValue / sampleIndex;
}
使用该功能,音高/音调为(伪代码)
tone = Max(_GetAmdf(tone)) <- for tone = C2..
我还尝试使用实际的自相关:
double accumDist=0;
//...
double dist = _AnalysisBuffer[sampleIndex] * _AnalysisBuffer[correlatingSampleIndex];
//...
const double scaleValue = (double)Int16.MaxValue * (double)Int16.MaxValue;
return accumDist / (scaleValue * sampleIndex);
但除了B4作为E
之外,无法将A3作为D注意:我不按缓冲区长度除以实际比较的样本数。不确定这是否正确,但似乎是逻辑。
答案 0 :(得分:2)
这是使用自相关和类似的音高滞后估计(AMDF,ASDF等)的常见八度音程问题
低一个八度音程(或任何其他整数倍)的频率也将在移位的波形相似性中提供良好的匹配(例如,移位2pi的正弦波看起来与移位4pi的正弦波相同,这表示八度音程更低。根据噪声以及连续峰值与采样峰值的接近程度,一个或另一个估计峰值可能略高,而音调没有变化。
因此需要使用一些其他测试来消除波形相关或滞后匹配中的低八度(或其他子频率)峰值(例如,峰值看起来足够接近像一个或多个其他峰值,一个或多个八度音阶或其他频率倍增等。)
答案 1 :(得分:1)
我不知道c#,但是如果您提供的少量代码是正确的,并且像大多数其他类似c的语言一样,它会引入大量的内部代码。失真。
在大多数类c语言(以及我所知道的大多数其他语言,如java)中,Math.sin()之类的输出将在[-1,1]范围内。在转换为int,short或long时,这将变为[-1,0]。从本质上讲,你将把你的正弦波改变成一个非常扭曲的方波,带有许多泛音,这可能就是这些图书馆所采用的。
试试这个:
data[i] = (short)(32,767 * Math.Sin(2 * Math.PI * i/fs * freq));
答案 2 :(得分:0)
除了@Bjorn和@Hotpaw所说的所有内容之外,过去我发现了@ hotpaw2所描述的问题。
如果您计算的是一个样本的差异(正如我在方程中看到的那样计算AMDF),那么您的代码中并不清楚!
我在java中做过,你可以在Tarsos找到完整的源代码!
这里是java中帖子的等效步骤:
int maxShift = audioBuffer.length;
for (int i = 0; i < maxShift; i++) {
frames1 = new double[maxShift - i + 1];
frames2 = new double[maxShift - i + 1];
t = 0;
for (int aux1 = 0; aux1 < maxShift - i; aux1++) {
t = t + 1;
frames1[t] = audioBuffer[aux1];
}
t = 0;
for (int aux2 = i; aux2 < maxShift; aux2++) {
t = t + 1;
frames2[t] = audioBuffer[aux2];
}
int frameLength = frames1.length;
calcSub = new double[frameLength];
for (int u = 0; u < frameLength; u++) {
calcSub[u] = frames1[u] - frames2[u];
}
double summation = 0;
for (int l = 0; l < frameLength; l++) {
summation += Math.abs(calcSub[l]);
}
amd[i] = summation;
}