I decided to make an app that will change display color while music playing. Kind of disco lights. Just to practice Android app development.

Sounds pretty simple. You just need to enable a microphone and respond to sound. But you need to implement a beat detection algorithm.

During development, I studied what is sound and how electronic devices work with it. Let’s get started.

How a microphone works

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

Since I used 16 bit 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.

Next task was to detect a beat.

How to detect a beat

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

There are a number of algorithms to detect beats. I used one from there: link

Here is my implementation in Java of that algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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, I calculate instant energy. It is a sum of pow of buffered samples.

1
2
3
4
5
6
7
8
9
float getInstantEnergy(short[] samples) {
float sum = 0;

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

return sum;
}

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

1
2
3
4
5
6
7
float getAverage(float[] values) {
float sum = 0;
for (float value : values) {
sum += value;
}
return sum / values.length;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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 coefficient was an extremely low number, so I limit it with that values:

1
2
3
4
5
if (constant < 1.2f) {
constant = 1.2f;
} else if (constant > 2f) {
constant = 2f;
}

Conclusion

That algorithm is pretty simple and is far from perfect. But it fit my purpose - to study something new and practice it. Maybe later I will change it to more precise.

You can try app here:
https://play.google.com/store/apps/details?id=tech.bogomolov.discolight