Mercurial > hg > webaudioevaluationtool
changeset 792:7b522c145516
Added loudness.js to handle loudness detection modules.
author | Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk> |
---|---|
date | Tue, 15 Dec 2015 16:02:17 +0000 |
parents | 26ab4a94b266 |
children | 261d92ea87e1 |
files | index.html loudness.js |
diffstat | 2 files changed, 171 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/index.html Fri Dec 11 18:32:32 2015 +0000 +++ b/index.html Tue Dec 15 16:02:17 2015 +0000 @@ -18,6 +18,7 @@ <!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>--> <script src="jquery-2.1.4.js"></script> <script src='core.js'></script> + <script src='loudness.js'></script> <script type="text/javascript"> window.onbeforeunload = function() { return "Please only leave this page once you have completed the tests. Are you sure you have completed all testing?";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/loudness.js Tue Dec 15 16:02:17 2015 +0000 @@ -0,0 +1,170 @@ +/** + * loundess.js + * Loudness module for the Web Audio Evaluation Toolbox + * Allows for automatic calculation of loudness of Web Audio API Buffer objects, + * return gain values to correct for a target loudness or match loudness between + * multiple objects + */ + +function getLoudness(buffer, result, timescale, offlineContext) +{ + // This function returns the EBU R 128 specification loudness model + // buffer -> Web Audio API Buffer object + // timescale -> M or Momentary (returns Array), S or Short (returns Array), + // I or Integrated (default, returns number) + + // Create the required filters + if (buffer == undefined || result == undefined) + { + return 0; + } + if (timescale == undefined) + { + timescale = "I"; + } + if (offlineContext == undefined) + { + offlineContext = new OfflineAudioContext(buffer.numberOfChannels, buffer.length, buffer.sampleRate); + } + var KFilter = offlineContext.createBiquadFilter(); + KFilter.type = "highshelf"; + KFilter.gain.value = 4; + KFilter.frequency.value = 1480; + + var HPFilter = offlineContext.createBiquadFilter(); + HPFilter.type = "highpass"; + HPFilter.Q.value = 0.707; + HPFilter.frequency.value = 60; + // copy Data into the process buffer + var processSource = offlineContext.createBufferSource(); + processSource.buffer = buffer; + + processSource.connect(KFilter); + KFilter.connect(HPFilter); + HPFilter.connect(offlineContext.destination); + processSource.start(); + offlineContext.startRendering().then(function(renderedBuffer) { + // Have the renderedBuffer information, now continue processing + console.log(renderedBuffer); + switch(timescale) + { + case "I": + var blockEnergy = calculateProcessedLoudness(renderedBuffer, 400, 0.75); + // Apply the absolute gate + var loudness = calculateLoudnessFromChannelBlocks(blockEnergy); + var absgatedEnergy = new Array(blockEnergy.length); + for (var c=0; c<blockEnergy.length; c++) + { + absgatedEnergy[c] = []; + } + for (var i=0; i<loudness.length; i++) + { + if (loudness[i] >= -70) + { + for (var c=0; c<blockEnergy.length; c++) + { + absgatedEnergy[c].push(blockEnergy[c][i]); + } + } + } + var overallAbsLoudness = calculateOverallLoudnessFromChannelBlocks(absgatedEnergy); + + //applying the relative gate 8 dB down from overallAbsLoudness + var relGateLevel = overallAbsLoudness - 8; + var relgateEnergy = new Array(blockEnergy.length); + for (var c=0; c<blockEnergy.length; c++) + { + relgateEnergy[c] = []; + } + for (var i=0; i<loudness.length; i++) + { + if (loudness[i] >= relGateLevel) + { + for (var c=0; c<blockEnergy.length; c++) + { + relgateEnergy[c].push(blockEnergy[c][i]); + } + } + } + var overallRelLoudness = calculateOverallLoudnessFromChannelBlocks(relgateEnergy); + result[0] = overallRelLoudness; + } + }).catch(function(err) { + console.log(err); + result[0] = 1; + }); +} + +function calculateProcessedLoudness(buffer, winDur, overlap) +{ + // Buffer Web Audio buffer node + // winDur Window Duration in milliseconds + // overlap Window overlap as normalised (0.5 = 50% overlap); + if (buffer == undefined) + { + return 0; + } + if (winDur == undefined) + { + winDur = 400; + } + if (overlap == undefined) + { + overlap = 0.5; + } + var winSize = buffer.sampleRate*winDur/1000; + var olapSize = overlap*winSize; + var numberOfFrames = Math.floor(buffer.length/olapSize - winSize/olapSize + 1); + var blockEnergy = new Array(buffer.numberOfChannels); + for (var channel = 0; channel < buffer.numberOfChannels; channel++) + { + blockEnergy[channel] = new Float32Array(numberOfFrames); + var data = buffer.getChannelData(channel); + for (var i=0; i<numberOfFrames; i++) + { + var sigma = 0; + for (var n=i*olapSize; n < i*olapSize+winSize; n++) + { + sigma += Math.pow(data[n],2); + } + blockEnergy[channel][i] = sigma/winSize; + } + } + return blockEnergy; +} +function calculateLoudnessFromChannelBlocks(blockEnergy) +{ + // Loudness + var loudness = new Float32Array(blockEnergy[0].length); + for (var i=0; i<blockEnergy[0].length; i++) + { + var sigma = 0; + for (var channel = 0; channel < blockEnergy.length; channel++) + { + var G = 1.0; + if (channel >= 4) {G = 1.41;} + sigma += blockEnergy[channel][i]*G; + } + loudness[i] = -0.691 + 10*Math.log10(sigma); + } + return loudness; +} +function calculateOverallLoudnessFromChannelBlocks(blockEnergy) +{ + // Loudness + var summation = 0; + for (var channel = 0; channel < blockEnergy.length; channel++) + { + var G = 1.0; + if (channel >= 4) {G = 1.41;} + var sigma = 0; + for (var i=0; i<blockEnergy[0].length; i++) + { + blockEnergy[channel][i] *= G; + sigma += blockEnergy[channel][i]; + } + sigma /= blockEnergy.length; + summation+= sigma; + } + return -0.691 + 10*Math.log10(summation);; +}