Disco Lights app development

This post is about how to create a beat detector in an Android application. You can use this algorithm as a base for BMP or tempo detection. Let’s dive in.

How a microphone works

Sound waves move the diaphragm and coil inside a microphone back and forth. These movements are converted into electrical current. The electrical current is then converted to discrete values. These discrete values are called audio samples. Samples are stored sequentially in the binary format.

I used 16 bits per sample. It is represented as an integer in the range from -32768 to 32767. The default format of sample representation is PCM (Pulse-Code Modulation).

When you record audio on 44KHz frequency, you will have 44000 samples in one second.

How to detect a beat

In Android, sound from the microphone writes to a buffer. A buffer contains some samples. Application read buffer and then do calculations in it.

There are several algorithms to detect beats. I used one from there: link

We are detecting the beat from an array of samples. Here is a high layer of the algorithm: we will get instant energy. Then we compare the instant energy with the average values. If the instant energy is higher than average, it will be treated as a beat. This is a simplified algorithm explanation.

Here is my implementation in Java of that algorithm.

boolean isBeat(short[] samples) {
    float instantEnergy = getInstantEnergy(samples);

    float[] values = ringBuffer.getValues();

    float localAverage = getAverage(values);

    float variance = getVariance(values, localAverage);
    float constant = (-0.0025714f * variance) + 1.5142857f;

    if (constant < 1.2f) {
        constant = 1.2f;
    } else if (constant > 2f) {
        constant = 2f;
    }

    ringBuffer.add(instantEnergy);

    return instantEnergy > constant * localAverage;
}

First, calculate instant energy. It is a sum of pow of buffered samples.

float getInstantEnergy(short[] samples) {
    float sum = 0;

    for (short sample : samples) {
        sum += Math.pow(sample, 2);
    }

    return sum;
}

Then get an average of previously calculated instant energies (storing them in a Circular buffer).

float getAverage(float[] values) {
    float sum = 0;
    for (float value : values) {
        sum += value;
    }
    return sum / values.length;
}

Next, get an average variance, which is the difference between every previous instant energy and average instant energy.

float getVariance(float[] values, float avg, int blockNum) {
    // blockNum = circular buffer size
    float diffSum = 0;
    for (int i = 0; i < blockNum; i++) {
        diffSum += Math.pow(values[i] - avg, 2);
    }

    return diffSum / blockNum;
}
``` 

If current instant energy is more than average instant energy multiplied to coefficient, then we treat this buffer as the beat.
```java
instantEnergy > constant * localAverage

In my case sometimes the coefficient was an extremely low number, so I limit it with that values.

if (constant < 1.2f) {
    constant = 1.2f;
} else if (constant > 2f) {
    constant = 2f;
}

That’s it. That algorithm is pretty simple and is far from perfect. Still, it may cover your requirements.