Page 1 of 1

[resolved] Implementing a beat detection algorithm

Posted: Sat Jun 08, 2013 11:38 am
by Fang
Hey all,

I want to put beat detection into my game. It doesn't have to be super accurate, nothing fancy really, so I have settled for the simple sound energy algorithm, as outlined here.

That's all really cool, but to be honest I have no idea how to implement that in LÖVE. The maths behind it are doable for me, so I just need to know how to handle the sample history buffer, and the "do this every 1024 samples" thing. I know I can pass my audio file into love.sound.newSoundData, but I don't know how to proceed from there. Should I just store all samples beforehand? In my love.update, how would I know what sample position to use for the algorithm? How would I handle a looping background song?

As the end result, I want to be able to make the background of a screen flash whenever it detects a beat in the music that's playing.

If I could get code examples that'd be great, but I can make do with some pointers on how to do this as well. Thanks!
~ Fang

Re: Implementing a beat detection algorithm

Posted: Sat Jun 08, 2013 12:05 pm
by Boolsheet
LÖVE is not designed to handle real time audio processing very efficiently. It requires you to load the whole song into memory with love.sound.newSoundData and then check the current offset of the playing source with Source:tell to get the right samples with SoundData:getSample. (Note that getSample returns the samples of a stereo track in an interleaved manner)

Here's an example.

Re: Implementing a beat detection algorithm

Posted: Sun Jun 09, 2013 7:05 pm
by Fang
Thanks Boolsheet, I got it working now! It's quite flawed still, only using the simple sound energy algorithm #1, but it runs fine, so that's great!

When instantiating:

Code: Select all

self.soundData = love.sound.newSoundData( "filename.mp3" )
self.source = love.audio.newSource( self.soundData )
self.lastChecked = 0
self.beat = false
love.audio.play( self.source )
In the update function:

Code: Select all

local curSample = self.source:tell( "samples" )
if curSample-1024 > self.lastChecked then
	
	local instantEnergy = 0
	for i=curSample, curSample+1024 do
		instantEnergy = instantEnergy + self.soundData:getSample(i)^2
	end
	local localAverageEnergy = 0
	for i=curSample-44032, curSample do
		localAverageEnergy = localAverageEnergy + self.soundData:getSample(i)^2
	end
	localAverageEnergy = localAverageEnergy * (1024/44100)
	
	if instantEnergy > localAverageEnergy*1.8 then
		self.beat = true
	else
		self.beat = false
	end
	
	self.lastChecked = curSample
	
end
Sure, this could use a lot of optimization, but I'm happy to have it running this easily. Thanks again!