Sunday, June 2, 2024
 Popular · Latest · Hot · Upcoming
87
rated 0 times [  91] [ 4]  / answers: 1 / hits: 15444  / 9 Years ago, thu, may 7, 2015, 12:00:00

I'm interested in using the JavaScript WebAudioAPI to detect song beats, and then render them in a canvas.



I can handle the canvas part, but I'm not a big audio guy and really don't understand how to make a beat detector in JavaScript.



I've tried following this article but cannot, for the life of me, connect the dots between each function to make a functional program.



I know I should show you some code but honestly I don't have any, all my attempts have failed miserably and the relevant code it's in the previously mentioned article.



Anyways I'd really appreciate some guidance, or even better a demo of how to actually detect song beats with the WebAudioAPI.



Thanks!


More From » audio

 Answers
11

The main thing to understand about the referenced article by Joe Sullivan is that even though it gives a lot of source code, it's far from final and complete code. To reach a working solution you will still need both some coding and debugging skills.


This answer draws most of its code from the referenced article, original licensing applies where appropriate.


Below is a naïve sample implementation for using the functions described by the above article, you still need to figure out correct thresholds for a functional solution.




The code consists of preparation code written for the answer:



and then, as described in the article:



  • filtering the audio, in this example with a low-pass filter

  • calculating peaks using a threshold

  • grouping interval counts and then tempo counts


For the threshold I used an arbitrary value of .98 of the range between maximum and minimum values; when grouping I added some additional checks and arbitrary rounding to avoid possible infinite loops and make it an easy-to-debug sample.


Note that commenting is scarce to keep the sample implementation brief because:



  • the logic behind processing is explained in the referenced article

  • the syntax can be referenced in the API docs of the related methods






audio_file.onchange = function() {
var file = this.files[0];
var reader = new FileReader();
var context = new(window.AudioContext || window.webkitAudioContext)();
reader.onload = function() {
context.decodeAudioData(reader.result, function(buffer) {
prepare(buffer);
});
};
reader.readAsArrayBuffer(file);
};

function prepare(buffer) {
var offlineContext = new OfflineAudioContext(1, buffer.length, buffer.sampleRate);
var source = offlineContext.createBufferSource();
source.buffer = buffer;
var filter = offlineContext.createBiquadFilter();
filter.type = lowpass;
source.connect(filter);
filter.connect(offlineContext.destination);
source.start(0);
offlineContext.startRendering();
offlineContext.oncomplete = function(e) {
process(e);
};
}

function process(e) {
var filteredBuffer = e.renderedBuffer;
//If you want to analyze both channels, use the other channel later
var data = filteredBuffer.getChannelData(0);
var max = arrayMax(data);
var min = arrayMin(data);
var threshold = min + (max - min) * 0.98;
var peaks = getPeaksAtThreshold(data, threshold);
var intervalCounts = countIntervalsBetweenNearbyPeaks(peaks);
var tempoCounts = groupNeighborsByTempo(intervalCounts);
tempoCounts.sort(function(a, b) {
return b.count - a.count;
});
if (tempoCounts.length) {
output.innerHTML = tempoCounts[0].tempo;
}
}

// http://tech.beatport.com/2014/web-audio/beat-detection-using-web-audio/
function getPeaksAtThreshold(data, threshold) {
var peaksArray = [];
var length = data.length;
for (var i = 0; i < length;) {
if (data[i] > threshold) {
peaksArray.push(i);
// Skip forward ~ 1/4s to get past this peak.
i += 10000;
}
i++;
}
return peaksArray;
}

function countIntervalsBetweenNearbyPeaks(peaks) {
var intervalCounts = [];
peaks.forEach(function(peak, index) {
for (var i = 0; i < 10; i++) {
var interval = peaks[index + i] - peak;
var foundInterval = intervalCounts.some(function(intervalCount) {
if (intervalCount.interval === interval) return intervalCount.count++;
});
//Additional checks to avoid infinite loops in later processing
if (!isNaN(interval) && interval !== 0 && !foundInterval) {
intervalCounts.push({
interval: interval,
count: 1
});
}
}
});
return intervalCounts;
}

function groupNeighborsByTempo(intervalCounts) {
var tempoCounts = [];
intervalCounts.forEach(function(intervalCount) {
//Convert an interval to tempo
var theoreticalTempo = 60 / (intervalCount.interval / 44100);
theoreticalTempo = Math.round(theoreticalTempo);
if (theoreticalTempo === 0) {
return;
}
// Adjust the tempo to fit within the 90-180 BPM range
while (theoreticalTempo < 90) theoreticalTempo *= 2;
while (theoreticalTempo > 180) theoreticalTempo /= 2;

var foundTempo = tempoCounts.some(function(tempoCount) {
if (tempoCount.tempo === theoreticalTempo) return tempoCount.count += intervalCount.count;
});
if (!foundTempo) {
tempoCounts.push({
tempo: theoreticalTempo,
count: intervalCount.count
});
}
});
return tempoCounts;
}

// http://stackoverflow.com/questions/1669190/javascript-min-max-array-values
function arrayMin(arr) {
var len = arr.length,
min = Infinity;
while (len--) {
if (arr[len] < min) {
min = arr[len];
}
}
return min;
}

function arrayMax(arr) {
var len = arr.length,
max = -Infinity;
while (len--) {
if (arr[len] > max) {
max = arr[len];
}
}
return max;
}

<input id=audio_file type=file accept=audio/*></input>
<audio id=audio_player></audio>
<p>
Most likely tempo: <span id=output></span>
</p>




[#66689] Wednesday, May 6, 2015, 9 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
sandra

Total Points: 708
Total Questions: 100
Total Answers: 84

Location: Bosnia and Herzegovina
Member since Thu, Jun 24, 2021
3 Years ago
sandra questions
Tue, Jun 30, 20, 00:00, 4 Years ago
Sun, May 31, 20, 00:00, 4 Years ago
Wed, May 20, 20, 00:00, 4 Years ago
Fri, May 31, 19, 00:00, 5 Years ago
;