Detect beat and play (wav) file in a synchronised manner

WeaklyTyped picture WeaklyTyped · Sep 10, 2012 · Viewed 8.8k times · Source

I am trying my hands at Audio Processing in python with this Beat Detection algorithm. I have implemented the first (non-optimized version) from the aforementioned article. While it prints some results, I have no way to detect whether it works with some accuracy or not as I do not know how to play sound with it.

Currently, I am using Popen to asynchronously start my media player with the song before going into the computation loop, but I am not sure if this strategy works and is giving synchronous results.

#!/usr/bin/python

import scipy.io.wavfile, numpy, sys, subprocess

# Some abstractions for computation
def sumsquared(arr):
    sum = 0
    for i in arr:
            sum = sum + (i[0] * i[0]) + (i[1] * i[1])

    return sum

if sys.argv.__len__() < 2:
    print 'USAGE: wavdsp <wavfile>'
    sys.exit(1)

numpy.set_printoptions(threshold='nan')
rate, data = scipy.io.wavfile.read(sys.argv[1])


# Beat detection algorithm begin 
# the algorithm has been implemented as per GameDev Article
# Initialisation
data_len = data.__len__()
idx = 0
hist_last = 44032
instant_energy = 0
local_energy = 0
le_multi = 0.023219955 # Local energy multiplier ~ 1024/44100


# Play the song
p = subprocess.Popen(['audacious', sys.argv[1]])

while idx < data_len - 48000:
    dat = data[idx:idx+1024]
    history = data[idx:hist_last]
    instant_energy = sumsquared(dat)
    local_energy = le_multi * sumsquared(history)
    print instant_energy, local_energy
    if instant_energy > (local_energy * 1.3):
            print 'Beat'

    idx = idx + 1024
    hist_last = hist_last + 1024 # Right shift history buffer

 p.terminate()

What modification/additions can I make to the script in order to get audio output and the algorithm (console) output in a time synchronised manner? i.e When console outputs result for a particular frame, that frame must be playing on the speakers.

Answer

zany picture zany · Mar 12, 2013

Working beat detection code (NumPy / PyAudio)

If you are using NumPy this code might help. It assumes the signal (read with PyAudio) is 16-bit wide Int. If that is not the case change or remove the signal.astype() and adjust the normalization-divider (max int16 here).

class SimpleBeatDetection:
    """
    Simple beat detection algorithm from
    http://archive.gamedev.net/archive/reference/programming/features/beatdetection/index.html
    """
    def __init__(self, history = 43):
        self.local_energy = numpy.zeros(history) # a simple ring buffer
        self.local_energy_index = 0 # the index of the oldest element

    def detect_beat(self, signal):

        samples = signal.astype(numpy.int) # make room for squares
        # optimized sum of squares, i.e faster version of (samples**2).sum()
        instant_energy = numpy.dot(samples, samples) / float(0xffffffff) # normalize

        local_energy_average = self.local_energy.mean()
        local_energy_variance = self.local_energy.var()

        beat_sensibility = (-0.0025714 * local_energy_variance) + 1.15142857
        beat = instant_energy > beat_sensibility * local_energy_average

        self.local_energy[self.local_energy_index] = instant_energy
        self.local_energy_index -= 1
        if self.local_energy_index < 0:
            self.local_energy_index = len(self.local_energy) - 1

        return beat

The PyAudio examples for wav read or mic record will give you the needed signal data. Create a NumPy array efficiently with frombuffer()

data = stream.read(CHUNK)
signal = numpy.frombuffer(data, numpy.int16)