【问题标题】:Pitch detection via auto correlation fails on higher pitches通过自动相关的音高检测在更高的音高上失败
【发布时间】:2014-04-29 13:21:06
【问题描述】:

我正在尝试使用自相关从录制的语音 (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 开始,另一个 peek 进化了,最终变得比正确的更大。我的 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 也失败了

注意:我不是除以 Bufferlength 而是除以实际比较的样本数。不确定这是否正确,但似乎合乎逻辑。

【问题讨论】:

    标签: audio signal-processing voice pitch-tracking


    【解决方案1】:

    这是使用自相关和类似的音高滞后估计(AMDF、ASDF 等)的常见八度音阶问题

    低一个八度(或任何其他整数倍)的频率也会在偏移波形相似性方面提供同样好的匹配(例如,偏移 2pi 的正弦波看起来与偏移 4pi 的正弦波相同,这表示低一个八度。根据噪声以及连续峰值与采样峰值的接近程度,一个或另一个估计峰值可能略高,而音高没有变化。

    因此,需要使用其他一些测试来消除波形相关或滞后匹配中的较低倍频程(或其他约数频率)峰值(例如,一个峰值看起来是否足够接近一个或多个其他峰值、一个或多个倍频程或其他频率倍增等)

    【讨论】:

    • 我不在乎结果是哪个八度,它只需要是正确的音符类。因此,如果我输入 B4 窦,只要是 B,我就会对 B2 输出感到满意。还是我误解了你?
    • B 音高的第三个约数或第三个倍数不在 B 音高的频率上。对于任何非 2 的幂都是一样的。
    【解决方案2】:

    我不懂 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));
    

    【讨论】:

    • 对,刚刚注意到我确实通过删除不必要的代码省略了一个重要的细节。我实际上使用了你的建议
    • 您应该使用更完整但仍然简短的示例更新您的问题。您可能还想添加 c# 标签并删除语音标签。
    • 完成。它与链接代码中的基本相同。不会更改标签,因为问题与语音(-pitch-detection)有关,这会影响所使用的算法,而不仅仅是 C#。也很乐意接受其他语言的答案。
    【解决方案3】:

    除了@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;
        }
    

    【讨论】:

    • 你的代码和我的有什么不同?它只是我的一个更复杂的版本: for (int i = 0; i
    • 我不是 C# 专家,但在您的源代码中我不知道您的 samplesPerPeriod 是否等于 1(正在计算一个样本的差异),此代码的所有差异是最小位置将如何被对待以确保没有问题与错误八度
    • samplesPerPeriod 永远不是 1。我只比较音调(从 C2 到 Cx),所以 samplesPerPeriod 介于 75 到 674 之间
    • 可能是你离边界很近才能找到正确的频率,75说我理论上你找不到588hz以上的频率,而B4可以靠近边界,你可以试试start从 42 到 674,看看它现在是否可以跟踪 B4 的音高....
    • 没有帮助。但是,如果我在从频率获取 samplesPerPeriod 时不进行四舍五入,我会在几次测试中得到更好的结果
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-14
    • 2015-12-12
    相关资源
    最近更新 更多