Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
151 lines (123 sloc) 4.35 KB
package me.mziccard.audio.bpm
import scala.collection.mutable.ArrayBuffer
import me.mziccard.audio.AudioFile
class SoundEnergyBPMDetector private (
private val audioFile : AudioFile,
private val samplesPerBlock : Int) extends BPMDetector {
/**
* Number of blocks considered in each second
**/
private val blocksPerSecond : Int = (audioFile.sampleRate / samplesPerBlock).toInt;
/**
* Buffer containing the last block read. Made of
* <code>samplesPerBlock</code> frames
**/
private val blockBuffer : Array[Double] =
new Array[Double](audioFile.numChannels * samplesPerBlock);
/**
* Circular buffer saving the energy values for the last
* blocksPerSecond blocks.
**/
private val energyBuffer : Array[Double] =
new Array[Double](blocksPerSecond)
/**
* Index in the <code>energyBuffer</code> array where to
* store the next energy value
**/
private var energyBufferPointer = 0;
/**
* Number of blocks read so far
**/
private var blockCounter = 0;
/**
* Total number of beats identified in the track
**/
private var beatCounter : Int = 0;
/**
* The tempo in beats-per-minute computed for the track
**/
var _bpm : Double = -1.0;
/**
* Array of instantaneous BPM values collected in the track
* could be used to extract an overall BPM as the median/maximum/minimum
**/
private var instantBpm = ArrayBuffer[Double]()
private def averageLocalEnergy() : Double = {
var energySum : Double = 0;
energyBuffer.foreach(
(e : Double) => { energySum = energySum + e });
return energySum/blocksPerSecond;
}
private def energyVariance(average : Double) : Double = {
var energyVariance : Double = 0;
energyBuffer.foreach(
(e : Double) => {
//println("iter " + e + " - " + average)
energyVariance = energyVariance + (e - average)*(e - average) });
return energyVariance/blocksPerSecond;
}
private def C(variance : Double) : Double = {
return SoundEnergyBPMDetector.C_ADDER +
SoundEnergyBPMDetector.C_MULTIPLIER * variance;
}
def bpm() : Double = {
if (_bpm == -1.0) {
var localBlockCounter : Int = 0;
var localPeakCounter : Int = 0;
var localBeatCounter : Int = 0;
var readFrames : Int =
audioFile.readNormalizedFrames(blockBuffer, samplesPerBlock);
while(readFrames != 0) {
if (readFrames == samplesPerBlock) {
var energy : Double = 0;
var i = 0;
while (i < samplesPerBlock) {
energy = energy +
blockBuffer(2*i) * blockBuffer(2*i) +
blockBuffer(2*i+1) * blockBuffer(2*i+1);
i = i + 1;
}
energyBuffer(energyBufferPointer) = energy;
energyBufferPointer = (energyBufferPointer + 1) % blocksPerSecond;
blockCounter = blockCounter + 1;
localBlockCounter = localBlockCounter + 1;
if (blockCounter > blocksPerSecond) {
val average = averageLocalEnergy();
val variance = energyVariance(average);
val Cparameter = C(variance);
val soil = Cparameter*average;
if (energy > soil) {
localPeakCounter = localPeakCounter + 1;
if (localPeakCounter == 4) {
localPeakCounter = 0;
localBeatCounter = localBeatCounter + 1;
beatCounter = beatCounter + 1;
}
} else {
localPeakCounter = 0;
}
if (localBlockCounter > audioFile.sampleRate.toInt * 5 / samplesPerBlock) {
val beatsPerMinute : Double =
(localBeatCounter * audioFile.sampleRate * 60).toDouble / (localBlockCounter * samplesPerBlock);
instantBpm += beatsPerMinute
localBeatCounter = 0;
localBlockCounter = 0;
}
}
}
readFrames =
audioFile.readNormalizedFrames(blockBuffer, samplesPerBlock);
}
_bpm = (beatCounter * audioFile.sampleRate * 60).toDouble / (blockCounter * samplesPerBlock)
}
return _bpm
}
}
object SoundEnergyBPMDetector {
val SAMPLES_PER_BLOCK = 1024
val C_MULTIPLIER = -0.0000075;
val C_ADDER = 1.5142857;
def apply(audioFile : AudioFile) : SoundEnergyBPMDetector = {
return new SoundEnergyBPMDetector(audioFile, SAMPLES_PER_BLOCK)
}
}