UNPKG

@sebastbake/music-tempo

Version:

Finding out tempo of the music

245 lines (216 loc) 9.78 kB
<!DOCTYPE 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 -&gt; +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 == &quot;undefined&quot;) { throw new ReferenceError(&quot;fft is undefined&quot;); } if (typeof fft.getHammingWindow !== &quot;function&quot; || typeof fft.getSpectrum !== &quot;function&quot;) { throw new ReferenceError(&quot;fft doesn&apos;t contain getHammingWindow or getSpectrum methods&quot;); } // Array.fill polyfill if (!Array.prototype.fill) { Array.prototype.fill = function(value) { if (this == null) { throw new TypeError(&apos;this is null or not defined&apos;); } var O = Object(this); var len = O.length &gt;&gt;&gt; 0; var start = arguments[1]; var relativeStart = start &gt;&gt; 0; var k = relativeStart &lt; 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len); var end = arguments[2]; var relativeEnd = end === undefined ? len : end &gt;&gt; 0; var final = relativeEnd &lt; 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len); while (k &lt; 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 &quot;Invalid buffer size (&quot; + bufferSize + &quot;), must be power of 2&quot;; } 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 &lt; length; wndStart += hopSize) { let wndEnd = wndStart + bufferSize; let re = []; let k = 0; for (let i = wndStart; i &lt; wndEnd; i++) { re[k] = hammWindow[k] * audioData[i]; k++; } im.fill(0); fft.getSpectrum(re, im); let flux = 0; for(let j = 0; j &lt; spectrumLength; j++) { let value = re[j] - previousSpectrum[j]; flux += value &lt; 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 &quot;Array expected&quot;; } if (data.length == 0) { throw &quot;Array is empty&quot;; } let sum = 0; let squareSum = 0; for (let i = 0; i &lt; 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 &lt; 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 &lt; length; i++) { av = decayRate * av + (1 - decayRate) * sf[i]; if (sf[i] &lt; av) continue; let wndStart = i - peakFindingWindow; let wndEnd = i + peakFindingWindow + 1; if (wndStart &lt; 0) wndStart = 0; if (wndEnd &gt; length) wndEnd = length; if (av &lt; sf[i]) av = sf[i]; let isMax = true; for (let j = wndStart; j &lt; wndEnd; j++) { if (sf[j] &gt; sf[i]) isMax = false; } if (isMax) { let meanWndStart = i - peakFindingWindow * meanWndMultiplier; let meanWndEnd = i + peakFindingWindow; if (meanWndStart &lt; 0) meanWndStart = 0; if (meanWndEnd &gt; length) meanWndEnd = length; let sum = 0; let count = meanWndEnd - meanWndStart; for (let j = meanWndStart; j &lt; meanWndEnd; j++) { sum += sf[j]; } if (sf[i] &gt; sum / count + peakThreshold) { peaks.push(i); } } } if (peaks.length &lt; 2) { throw &quot;Fail to find peaks&quot;; } 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>