为什么此节拍检测代码无法正确注册某些节拍?


38

我制作了此SoundAnalyzer类来检测歌曲中的节拍:

class SoundAnalyzer
{
    public SoundBuffer soundData;
    public Sound sound;
    public List<double> beatMarkers = new List<double>();

    public SoundAnalyzer(string path)
    {
        soundData = new SoundBuffer(path);
        sound = new Sound(soundData);
    }

    // C = threshold, N = size of history buffer / 1024  B = bands
    public void PlaceBeatMarkers(float C, int N, int B)
    {
        List<double>[] instantEnergyList = new List<double>[B];
        GetEnergyList(B, ref instantEnergyList);
        for (int i = 0; i < B; i++)
        {
            PlaceMarkers(instantEnergyList[i], N, C);
        }
        beatMarkers.Sort();
    }

    private short[] getRange(int begin, int end, short[] array)
    {
        short[] result = new short[end - begin];
        for (int i = 0; i < end - begin; i++)
        {
            result[i] = array[begin + i];
        }
        return result;
    }

    // get a array of with a list of energy for each band
    private void GetEnergyList(int B, ref List<double>[] instantEnergyList)
    {
        for (int i = 0; i < B; i++)
        {
            instantEnergyList[i] = new List<double>();
        }
        short[] samples = soundData.Samples;

        float timePerSample = 1 / (float)soundData.SampleRate;
        int sampleIndex = 0;
        int nextSamples = 1024;
        int samplesPerBand = nextSamples / B;

        // for the whole song
        while (sampleIndex + nextSamples < samples.Length)
        {
            complex[] FFT = FastFourier.Calculate(getRange(sampleIndex, nextSamples + sampleIndex, samples));
            // foreach band
            for (int i = 0; i < B; i++)
            {
                double energy = 0;
                for (int j = 0; j < samplesPerBand; j++)
                    energy += FFT[i * samplesPerBand + j].GetMagnitude();

                energy /= samplesPerBand;
                instantEnergyList[i].Add(energy);

            }

            if (sampleIndex + nextSamples >= samples.Length)
                nextSamples = samples.Length - sampleIndex - 1;
            sampleIndex += nextSamples;
            samplesPerBand = nextSamples / B;
        }
    }

    // place the actual markers
    private void PlaceMarkers(List<double> instantEnergyList, int N, float C)
    {
        double timePerSample = 1 / (double)soundData.SampleRate;
        int index = N;
        int numInBuffer = index;
        double historyBuffer = 0;

        //Fill the history buffer with n * instant energy
        for (int i = 0; i < index; i++)
        {
            historyBuffer += instantEnergyList[i];
        }

        // If instantEnergy / samples in buffer < instantEnergy for the next sample then add beatmarker.
        while (index + 1 < instantEnergyList.Count)
        {
            if(instantEnergyList[index + 1] > (historyBuffer / numInBuffer) * C)
                beatMarkers.Add((index + 1) * 1024 * timePerSample); 
            historyBuffer -= instantEnergyList[index - numInBuffer];
            historyBuffer += instantEnergyList[index + 1];
            index++;
        }
    }
}

由于某种原因,它只能检测到637秒到641秒左右的节拍,我不知道为什么。我知道节拍是从多个频段插入的,因为我发现重复项,而且似乎正在为这些值之间的每个瞬时能量值分配节拍。

它是在此之后建模的:http : //www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

那为什么节拍不能正确记录呢?


2
您可以发布一个频段随时间变化的InstantEnergyList [index + 1]和historyBuffer的图吗?这两个图彼此重叠。这样可以提供有关问题可能出在哪里的线索。同样,能量必须是大小的平方,请不要忘记这一点。
CeeJay 2011年

嗯,是的,这可能会揭示问题,让我看看我是否可以以某种方式绘制图表
Quincy

2
但是此图只是historyBuffer或historyBuffer / numInBuffer * C吗?似乎您的计算机中有大量C。查看代码,historyBuffer应该具有与InstantEnergy类似的值,该图只能在C太高或numInBuffer太低(远低于1)时出现,我猜不是这样。
CeeJay 2011年

7
不会死的问题...
工程师

Answers:


7

我刺了一下它,这是愚蠢的,因为我不熟悉傅里叶变换或音乐理论。因此,经过一番研究,我没有解决方案,但是我看到了一些令人困扰的事情:

  • 声音和声音缓冲区的代码丢失,很容易成为罪魁祸首
  • 傅立叶变换
    • 我通过搜索名称空间和方法名称找不到相同的Fourier转换库,这意味着代码可能是自定义的,并且可能是问题的根源
    • FastFourier.Calculate需要简短数组的事实是不寻常的
  • GetEnergyList方法采用一个引用列表,但是是否不再使用此列表?
  • 在几个地方可以看到SampleSize硬编码为1024,但尚不清楚是否总是如此。
  • 令人不安的是,PlaceBeatMarkers的注释指出N应该除以1024,也许调用代码忘记这样做了?
  • 我对在PlaceMarkers中操纵historyBuffer的方式非常怀疑,尤其是因为传入了N然后用于操纵historyBuffer。
  • 注释*// Fill the history buffer with n * instant energy*和其后的代码并不复杂。

过了一会儿,我才感觉到代码的组织不是很好,并且浪费时间进行修复。如果您认为值得,那么下一步是:

  1. 分解成最简单的部分
  2. 以最冗长的方式重写代码,命名所有隐藏变量
  3. 编写单元测试以确保很少一部分代码可以正常工作
  4. 再添加一小段代码,然后重复进行直到完成整个工作为止

提示

  • 您可能需要固定频带数以简化循环逻辑
  • 给变量N,C和B清晰明了的名称,这将帮助您更轻松地看到逻辑错误
  • 将大段代码分成几个称为方法的方法,每个方法在较大的过程中执行一小段简洁的步骤,并且可以编写单元测试以确保其正常工作。

我喜欢解决代码难题,只要难题是好的。因此赏金。我很高兴您接受了它,而您发现代码错误的答案是代码谜语可以获得的最佳答案。
塞斯·巴丁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.