Mercurial > hg > webaudioevaluationtool
changeset 1430:e3ff8b7d6538
Automatic Loudness normalisation to -23 LUFS
author | Nicholas Jillings <nickjillings@users.noreply.github.com> |
---|---|
date | Wed, 16 Dec 2015 12:15:18 +0000 |
parents | c06930d14ff7 |
children | 77edd1b2329b |
files | core.js loudness.js |
diffstat | 2 files changed, 63 insertions(+), 51 deletions(-) [+] |
line wrap: on
line diff
--- a/core.js Tue Dec 15 16:02:17 2015 +0000 +++ b/core.js Wed Dec 16 12:15:18 2015 +0000 @@ -19,6 +19,10 @@ // Add a prototype to the bufferSourceNode to reference to the audioObject holding it AudioBufferSourceNode.prototype.owner = undefined; +// Add a prototype to the bufferNode to hold the desired LINEAR gain +AudioBuffer.prototype.gain = undefined; +// Add a prototype to the bufferNode to hold the computed LUFS loudness +AudioBuffer.prototype.lufs = undefined; window.onload = function() { // Function called once the browser has loaded all files. @@ -113,7 +117,8 @@ } if (buffer == null) { - buffer = new audioEngineContext.bufferObj(URL); + buffer = new audioEngineContext.bufferObj(); + buffer.getMedia(URL); audioEngineContext.buffers.push(buffer); } }); @@ -757,45 +762,49 @@ this.audioObjects = []; this.buffers = []; - this.bufferObj = function(url) + this.bufferObj = function() { - this.url = url; + this.url = null; this.buffer = null; this.xmlRequest = new XMLHttpRequest(); this.users = []; - this.xmlRequest.open('GET',this.url,true); - this.xmlRequest.responseType = 'arraybuffer'; - - var bufferObj = this; - - // Create callback to decode the data asynchronously - this.xmlRequest.onloadend = function() { - audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) { - bufferObj.buffer = decodedData; - for (var i=0; i<bufferObj.users.length; i++) - { - bufferObj.users[i].state = 1; - if (bufferObj.users[i].interfaceDOM != null) + this.getMedia = function(url) { + this.url = url; + this.xmlRequest.open('GET',this.url,true); + this.xmlRequest.responseType = 'arraybuffer'; + + var bufferObj = this; + + // Create callback to decode the data asynchronously + this.xmlRequest.onloadend = function() { + audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) { + bufferObj.buffer = decodedData; + for (var i=0; i<bufferObj.users.length; i++) { - bufferObj.users[i].interfaceDOM.enable(); + bufferObj.users[i].state = 1; + if (bufferObj.users[i].interfaceDOM != null) + { + bufferObj.users[i].interfaceDOM.enable(); + } } - } - }, function(){ - // Should only be called if there was an error, but sometimes gets called continuously - // Check here if the error is genuine - if (bufferObj.buffer == undefined) { - // Genuine error - console.log('FATAL - Error loading buffer on '+audioObj.id); - if (request.status == 404) - { - console.log('FATAL - Fragment '+audioObj.id+' 404 error'); - console.log('URL: '+audioObj.url); - errorSessionDump('Fragment '+audioObj.id+' 404 error'); + calculateLoudness(bufferObj.buffer,"I"); + }, function(){ + // Should only be called if there was an error, but sometimes gets called continuously + // Check here if the error is genuine + if (bufferObj.buffer == undefined) { + // Genuine error + console.log('FATAL - Error loading buffer on '+audioObj.id); + if (request.status == 404) + { + console.log('FATAL - Fragment '+audioObj.id+' 404 error'); + console.log('URL: '+audioObj.url); + errorSessionDump('Fragment '+audioObj.id+' 404 error'); + } } - } - }); + }); + }; + this.xmlRequest.send(); }; - this.xmlRequest.send(); }; this.play = function(id) { @@ -838,7 +847,7 @@ this.audioObjects[i].outputGain.gain.value = 0.0; this.audioObjects[i].stop(); } else if (i == id) { - this.audioObjects[id].outputGain.gain.value = this.audioObjects[id].specification.gain; + this.audioObjects[id].outputGain.gain.value = this.audioObjects[id].specification.gain*this.audioObjects[id].buffer.buffer.gain; this.audioObjects[id].play(audioContext.currentTime+0.01); } } @@ -881,7 +890,8 @@ if (buffer == null) { console.log("[WARN]: Buffer was not loaded in pre-test! "+URL); - buffer = new this.bufferObj(URL); + buffer = new this.bufferObj(); + buffer.getMedia(URL); this.buffers.push(buffer); } this.audioObjects[audioObjectId].specification = element; @@ -931,24 +941,17 @@ this.setSynchronousLoop = function() { // Pads the signals so they are all exactly the same length var length = 0; - var lens = []; var maxId; for (var i=0; i<this.audioObjects.length; i++) { - lens.push(this.audioObjects[i].buffer.buffer.length); if (length < this.audioObjects[i].buffer.buffer.length) { length = this.audioObjects[i].buffer.buffer.length; maxId = i; } } - // Perform difference - for (var i=0; i<lens.length; i++) - { - lens[i] = length - lens[i]; - } // Extract the audio and zero-pad - for (var i=0; i<lens.length; i++) + for (var i=0; i<this.audioObjects.length; i++) { var orig = this.audioObjects[i].buffer.buffer; var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate); @@ -959,8 +962,9 @@ for (var n=0; n<orig.length; n++) {inData[n] = outData[n];} } + hold.gain = orig.gain; + hold.lufs = orig.lufs; this.audioObjects[i].buffer.buffer = hold; - delete orig; } }; @@ -994,7 +998,7 @@ this.buffer; this.loopStart = function() { - this.outputGain.gain.value = 1.0; + this.outputGain.gain.value = this.specification.gain*this.buffer.buffer.gain; this.metric.startListening(audioEngineContext.timer.getTestTime()); };
--- a/loudness.js Tue Dec 15 16:02:17 2015 +0000 +++ b/loudness.js Wed Dec 16 12:15:18 2015 +0000 @@ -6,15 +6,17 @@ * multiple objects */ -function getLoudness(buffer, result, timescale, offlineContext) +var interval_cal_loudness_event = null; + +function calculateLoudness(buffer, timescale, target, offlineContext) { - // This function returns the EBU R 128 specification loudness model + // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS // buffer -> Web Audio API Buffer object // timescale -> M or Momentary (returns Array), S or Short (returns Array), // I or Integrated (default, returns number) + // target -> default is -23 LUFS but can be any LUFS measurement. - // Create the required filters - if (buffer == undefined || result == undefined) + if (buffer == undefined) { return 0; } @@ -22,10 +24,15 @@ { timescale = "I"; } + if (target == undefined) + { + target = -23; + } if (offlineContext == undefined) { offlineContext = new OfflineAudioContext(buffer.numberOfChannels, buffer.length, buffer.sampleRate); } + // Create the required filters var KFilter = offlineContext.createBiquadFilter(); KFilter.type = "highshelf"; KFilter.gain.value = 4; @@ -45,7 +52,6 @@ processSource.start(); offlineContext.startRendering().then(function(renderedBuffer) { // Have the renderedBuffer information, now continue processing - console.log(renderedBuffer); switch(timescale) { case "I": @@ -87,11 +93,13 @@ } } var overallRelLoudness = calculateOverallLoudnessFromChannelBlocks(relgateEnergy); - result[0] = overallRelLoudness; + buffer.lufs = overallRelLoudness; + var diff = -23 -overallRelLoudness; + buffer.gain = decibelToLinear(diff); } }).catch(function(err) { console.log(err); - result[0] = 1; + buffer.lufs = 1; }); }