Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I made this SoundAnalyzer class to detect beats in songs:

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++;
        }
    }
}

For some reason it's only detecting beats from 637 sec to around 641 sec, and I have no idea why. I know the beats are being inserted from multiple bands since I am finding duplicates, and it seems that it's assigning a beat to each instant energy value in between those values.

It's modeled after this: http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

So why won't the beats register properly?

share|improve this question
2  
Can you post a plot of the evolution of instantEnergyList[index + 1] and historyBuffer over time for one band? The two graphs overlayed on top of each other. That would give clues about what the problem might be. Also, energy must be the square of the magnitude, don't forget that. –  CeeJay Mar 18 '11 at 12:31
    
Ahh yes that might unveil the problem, let me see if I can somehow make some graphs –  Quincy Mar 18 '11 at 14:44
2  
But this plot is just historyBuffer, or historyBuffer/numInBuffer*C ? It looks like you have a massive C in there. Looking at the code, historyBuffer should have similar values to instantEnergy, that graph can only be if C is too high or numInBuffer is too low (way below 1), which I guess it's not the case. –  CeeJay Mar 29 '11 at 15:49
7  
The question that wouldn't die... –  Nick Wiggill Jan 27 '12 at 14:29
2  
Try asking this question on dsp.stackexchange.com –  Atav32 Jul 20 '12 at 15:25

1 Answer 1

I took a stab at it, which was dumb because I not familiar with Fourier transforms or music theory. So, after some study I don't have a solution, but I see several troubling things:

  • The code for the Sound and Soundbuffer is missing and could be easily be the culprit
  • The Fourier Transforms
    • I couldn't find the same Fourier transforms library by googling the namespace and method names, which means that code might be custom and could be the source of the problem
    • The fact that FastFourier.Calculate takes an array of short is unusual
  • The method GetEnergyList takes a ref List but this list isn't used again?
  • In several spots you see the SampleSize hard coded to 1024, but it isn't clear that is always the case.
  • It is troubling that the comment for PlaceBeatMarkers notes that N should be divided by 1024, maybe the calling code forgot to do that?
  • I'm very suspicious of the way historyBuffer is manipulated in PlaceMarkers, especially since N is passed in and then used to manipulate the historyBuffer.
  • The comment *// Fill the history buffer with n * instant energy* and the code that follows doesn't jive.

After a while I just got the feeling the code isn't really organized well and it'd be a waste of time trying to fix. If you think it is worth it, the next step I'd take is:

  1. Break it down to the most simplest part
  2. Rewrite the code in the most verbose way, name all the hidden variables
  3. Write unit tests to make sure that little part of code works correctly
  4. Add another small section of code and repeat until you've got the whole thing working right

Tips

  • You might want to make the number of bands fixed to simplify the loop logic
  • Give variable like N, C and B good names that are clear and concise, this will help you mind see logical errors easier
  • Break big sections of code into several called methods that each do a small concise step of the bigger process and can have unit tests written to ensure it is working correctly.
share|improve this answer
    
+1 Nice effort! –  Laurent Couvidou Oct 30 '12 at 12:11
    
I'm a fan of solving code riddles, as long as the riddle is good. Hence the bounty. I'm glad you took it, and your answers for finding errors in code are the best answer a code riddle could get. –  Seth Battin Oct 30 '12 at 14:22
    
Thanks guys, I was worried I hadn't tried hard enough. –  MrPhil Oct 30 '12 at 19:10

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.