@sebastbake/music-tempo
Version:
Finding out tempo of the music
245 lines (216 loc) • 9.78 kB
HTML
<html>
<head>
<meta charset="utf-8">
<base data-ice="baseUrl" href="../../">
<title data-ice="title">src/OnsetDetection.js | API Document</title>
<link type="text/css" rel="stylesheet" href="css/style.css">
<link type="text/css" rel="stylesheet" href="css/prettify-tomorrow.css">
<script src="script/prettify/prettify.js"></script>
<script src="script/manual.js"></script>
</head>
<body class="layout-container" data-ice="rootContainer">
<header>
<a href="./">Home</a>
<a href="identifiers.html">Reference</a>
<a href="source.html">Source</a>
<a data-ice="repoURL" href="https://github.com/killercrush/music-tempo" class="repo-url-github">Repository</a>
<div class="search-box">
<span>
<img src="./image/search.png">
<span class="search-input-edge"></span><input class="search-input"><span class="search-input-edge"></span>
</span>
<ul class="search-result"></ul>
</div>
</header>
<nav class="navigation" data-ice="nav"><div>
<ul>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/Agent.js~Agent.html">Agent</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/BeatTracking.js~BeatTracking.html">BeatTracking</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/FFT.js~FFT.html">FFT</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/MusicTempo.js~MusicTempo.html">MusicTempo</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/OnsetDetection.js~OnsetDetection.html">OnsetDetection</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/TempoInduction.js~TempoInduction.html">TempoInduction</a></span></span></li>
</ul>
</div>
</nav>
<div class="content" data-ice="content"><h1 data-ice="title">src/OnsetDetection.js</h1>
<pre class="source-code line-number raw-source-code"><code class="prettyprint linenums" data-ice="content">/**
* Spectral flux calculating and peaks finding
* @class
*/
export default class OnsetDetection {
/**
* Get spectral flux
* @param {Float32Array} audioData - non-interleaved IEEE 32-bit linear PCM with a nominal range of -1 -> +1 (Web Audio API - Audio Buffer)
* @param {Object} fft - object with methods for performing FFT
* @param {Object} [params={}] - parameters
* @param {Number} [params.bufferSize=2048] - FFT windows size
* @param {Number} [params.hopSize=441] - spacing of audio frames in samples
* @return {Array} spectralFlux - the array of spectral flux values
*/
static calculateSF(audioData, fft, params = {}) {
if (typeof fft == "undefined") {
throw new ReferenceError("fft is undefined");
}
if (typeof fft.getHammingWindow !== "function" || typeof fft.getSpectrum !== "function") {
throw new ReferenceError("fft doesn't contain getHammingWindow or getSpectrum methods");
}
// Array.fill polyfill
if (!Array.prototype.fill) {
Array.prototype.fill = function(value) {
if (this == null) {
throw new TypeError('this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
var start = arguments[1];
var relativeStart = start >> 0;
var k = relativeStart < 0 ?
Math.max(len + relativeStart, 0) :
Math.min(relativeStart, len);
var end = arguments[2];
var relativeEnd = end === undefined ?
len : end >> 0;
var final = relativeEnd < 0 ?
Math.max(len + relativeEnd, 0) :
Math.min(relativeEnd, len);
while (k < final) {
O[k] = value;
k++;
}
return O;
};
}
params.bufferSize = params.bufferSize || 2048;
//params.samplingRate = params.samplingRate || 44100;
params.hopSize = params.hopSize || 441;
const {bufferSize, hopSize} = params;
let k = Math.floor(Math.log(bufferSize) / Math.LN2);
if (Math.pow(2, k) !== bufferSize) {
throw "Invalid buffer size (" + bufferSize + "), must be power of 2";
}
const hammWindow = fft.getHammingWindow(bufferSize);
let spectralFlux = [];
let spectrumLength = bufferSize / 2 + 1;
let previousSpectrum = new Array(spectrumLength);
previousSpectrum.fill(0);
let im = new Array(bufferSize);
let length = audioData.length;
let zerosStart = new Array(bufferSize - hopSize);
zerosStart.fill(0);
audioData = zerosStart.concat(audioData);
let zerosEnd = new Array(bufferSize - (audioData.length % hopSize));
zerosEnd.fill(0);
audioData = audioData.concat(zerosEnd);
for (let wndStart = 0; wndStart < length; wndStart += hopSize) {
let wndEnd = wndStart + bufferSize;
let re = [];
let k = 0;
for (let i = wndStart; i < wndEnd; i++) {
re[k] = hammWindow[k] * audioData[i];
k++;
}
im.fill(0);
fft.getSpectrum(re, im);
let flux = 0;
for(let j = 0; j < spectrumLength; j++) {
let value = re[j] - previousSpectrum[j];
flux += value < 0 ? 0 : value;
}
spectralFlux.push(flux);
previousSpectrum = re;
}
return spectralFlux;
}
/**
* Normalize data to have a mean of 0 and standard deviation of 1
* @param {Array} data - data array
*/
static normalize(data) {
if (!Array.isArray(data)) {
throw "Array expected";
}
if (data.length == 0) {
throw "Array is empty";
}
let sum = 0;
let squareSum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i];
squareSum += data[i] * data[i];
}
let mean = sum / data.length;
let standardDeviation = Math.sqrt( (squareSum - sum * mean) / data.length );
if (standardDeviation == 0)
standardDeviation = 1;
for (let i = 0; i < data.length; i++) {
data[i] = (data[i] - mean) / standardDeviation;
}
}
/**
* Finding local maxima in an array
* @param {Array} spectralFlux - input data
* @param {Object} [params={}] - parametrs
* @param {Number} [params.decayRate=0.84] - how quickly previous peaks are forgotten
* @param {Number} [params.peakFindingWindow=6] - minimum distance between peaks
* @param {Number} [params.meanWndMultiplier=3] - multiplier for peak finding window
* @param {Number} [params.peakThreshold=0.35] - minimum value of peaks
* @return {Array} peaks - array of peak indexes
*/
static findPeaks(spectralFlux, params = {}) {
const length = spectralFlux.length;
const sf = spectralFlux;
const decayRate = params.decayRate || 0.84;
const peakFindingWindow = params.peakFindingWindow || 6;
const meanWndMultiplier = params.meanWndMultiplier || 3;
const peakThreshold = params.peakThreshold || 0.35;
let max = 0;
let av = sf[0];
let peaks = [];
for (let i = 0; i < length; i++) {
av = decayRate * av + (1 - decayRate) * sf[i];
if (sf[i] < av) continue;
let wndStart = i - peakFindingWindow;
let wndEnd = i + peakFindingWindow + 1;
if (wndStart < 0) wndStart = 0;
if (wndEnd > length) wndEnd = length;
if (av < sf[i]) av = sf[i];
let isMax = true;
for (let j = wndStart; j < wndEnd; j++) {
if (sf[j] > sf[i]) isMax = false;
}
if (isMax) {
let meanWndStart = i - peakFindingWindow * meanWndMultiplier;
let meanWndEnd = i + peakFindingWindow;
if (meanWndStart < 0) meanWndStart = 0;
if (meanWndEnd > length) meanWndEnd = length;
let sum = 0;
let count = meanWndEnd - meanWndStart;
for (let j = meanWndStart; j < meanWndEnd; j++) {
sum += sf[j];
}
if (sf[i] > sum / count + peakThreshold) {
peaks.push(i);
}
}
}
if (peaks.length < 2) {
throw "Fail to find peaks";
}
return peaks;
}
}</code></pre>
</div>
<footer class="footer">
Generated by <a href="https://esdoc.org">ESDoc<span data-ice="esdocVersion">(0.5.2)</span><img src="./image/esdoc-logo-mini-black.png"></a>
</footer>
<script src="script/search_index.js"></script>
<script src="script/search.js"></script>
<script src="script/pretty-print.js"></script>
<script src="script/inherited-summary.js"></script>
<script src="script/test-summary.js"></script>
<script src="script/inner-link.js"></script>
<script src="script/patch-for-local.js"></script>
</body>
</html>