Skip to content
Permalink
master
Go to file
 
 
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)
}
}
You can’t perform that action at this time.