annotate loudness.js @ 1350:b6389ceaeaa5

Confirmed working (using examples) on OSX (Chrome/Safari/Firefox)
author Nicholas Jillings <nickjillings@users.noreply.github.com>
date Thu, 14 Jan 2016 16:00:52 +0000
parents 397e96ee781a
children 80eea5659a10
rev   line source
nickjillings@1318 1 /**
nickjillings@1318 2 * loundess.js
nickjillings@1318 3 * Loudness module for the Web Audio Evaluation Toolbox
nickjillings@1318 4 * Allows for automatic calculation of loudness of Web Audio API Buffer objects,
nickjillings@1318 5 * return gain values to correct for a target loudness or match loudness between
nickjillings@1318 6 * multiple objects
nickjillings@1318 7 */
nickjillings@1318 8
nickjillings@1318 9 var interval_cal_loudness_event = null;
nickjillings@1318 10
nickjillings@1318 11 if (typeof OfflineAudioContext == "undefined"){
nickjillings@1318 12 var OfflineAudioContext = webkitOfflineAudioContext;
nickjillings@1318 13 }
nickjillings@1318 14
nickjillings@1318 15 function calculateLoudness(buffer, timescale, target, offlineContext)
nickjillings@1318 16 {
nickjillings@1318 17 // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS
nickjillings@1318 18 // buffer -> Web Audio API Buffer object
nickjillings@1318 19 // timescale -> M or Momentary (returns Array), S or Short (returns Array),
nickjillings@1318 20 // I or Integrated (default, returns number)
nickjillings@1318 21 // target -> default is -23 LUFS but can be any LUFS measurement.
nickjillings@1318 22
nickjillings@1318 23 if (buffer == undefined)
nickjillings@1318 24 {
nickjillings@1318 25 return 0;
nickjillings@1318 26 }
nickjillings@1318 27 if (timescale == undefined)
nickjillings@1318 28 {
nickjillings@1318 29 timescale = "I";
nickjillings@1318 30 }
nickjillings@1318 31 if (target == undefined)
nickjillings@1318 32 {
nickjillings@1318 33 target = -23;
nickjillings@1318 34 }
nickjillings@1318 35 if (offlineContext == undefined)
nickjillings@1318 36 {
nickjillings@1342 37 offlineContext = new OfflineAudioContext(buffer.buffer.numberOfChannels, buffer.buffer.length, buffer.buffer.sampleRate);
nickjillings@1318 38 }
nickjillings@1318 39 // Create the required filters
nickjillings@1318 40 var KFilter = offlineContext.createBiquadFilter();
nickjillings@1318 41 KFilter.type = "highshelf";
nickjillings@1318 42 KFilter.gain.value = 4;
nickjillings@1318 43 KFilter.frequency.value = 1480;
nickjillings@1318 44
nickjillings@1318 45 var HPFilter = offlineContext.createBiquadFilter();
nickjillings@1318 46 HPFilter.type = "highpass";
nickjillings@1318 47 HPFilter.Q.value = 0.707;
nickjillings@1318 48 HPFilter.frequency.value = 60;
nickjillings@1318 49 // copy Data into the process buffer
nickjillings@1318 50 var processSource = offlineContext.createBufferSource();
nickjillings@1342 51 processSource.buffer = buffer.buffer;
nickjillings@1318 52
nickjillings@1318 53 processSource.connect(KFilter);
nickjillings@1318 54 KFilter.connect(HPFilter);
nickjillings@1318 55 HPFilter.connect(offlineContext.destination);
nickjillings@1318 56 processSource.start();
nickjillings@1318 57 offlineContext.oncomplete = function(renderedBuffer) {
nickjillings@1318 58 // Have the renderedBuffer information, now continue processing
nickjillings@1318 59 if (typeof renderedBuffer.renderedBuffer == 'object') {
nickjillings@1318 60 renderedBuffer = renderedBuffer.renderedBuffer;
nickjillings@1318 61 }
nickjillings@1318 62 switch(timescale)
nickjillings@1318 63 {
nickjillings@1318 64 case "I":
nickjillings@1318 65 var blockEnergy = calculateProcessedLoudness(renderedBuffer, 400, 0.75);
nickjillings@1318 66 // Apply the absolute gate
nickjillings@1318 67 var loudness = calculateLoudnessFromChannelBlocks(blockEnergy);
nickjillings@1318 68 var absgatedEnergy = new Array(blockEnergy.length);
nickjillings@1318 69 for (var c=0; c<blockEnergy.length; c++)
nickjillings@1318 70 {
nickjillings@1318 71 absgatedEnergy[c] = [];
nickjillings@1318 72 }
nickjillings@1318 73 for (var i=0; i<loudness.length; i++)
nickjillings@1318 74 {
nickjillings@1318 75 if (loudness[i] >= -70)
nickjillings@1318 76 {
nickjillings@1318 77 for (var c=0; c<blockEnergy.length; c++)
nickjillings@1318 78 {
nickjillings@1318 79 absgatedEnergy[c].push(blockEnergy[c][i]);
nickjillings@1318 80 }
nickjillings@1318 81 }
nickjillings@1318 82 }
nickjillings@1318 83 var overallAbsLoudness = calculateOverallLoudnessFromChannelBlocks(absgatedEnergy);
nickjillings@1318 84
nickjillings@1318 85 //applying the relative gate 8 dB down from overallAbsLoudness
nickjillings@1318 86 var relGateLevel = overallAbsLoudness - 8;
nickjillings@1318 87 var relgateEnergy = new Array(blockEnergy.length);
nickjillings@1318 88 for (var c=0; c<blockEnergy.length; c++)
nickjillings@1318 89 {
nickjillings@1318 90 relgateEnergy[c] = [];
nickjillings@1318 91 }
nickjillings@1318 92 for (var i=0; i<loudness.length; i++)
nickjillings@1318 93 {
nickjillings@1318 94 if (loudness[i] >= relGateLevel)
nickjillings@1318 95 {
nickjillings@1318 96 for (var c=0; c<blockEnergy.length; c++)
nickjillings@1318 97 {
nickjillings@1318 98 relgateEnergy[c].push(blockEnergy[c][i]);
nickjillings@1318 99 }
nickjillings@1318 100 }
nickjillings@1318 101 }
nickjillings@1318 102 var overallRelLoudness = calculateOverallLoudnessFromChannelBlocks(relgateEnergy);
nickjillings@1342 103 buffer.buffer.lufs = overallRelLoudness;
nickjillings@1342 104 buffer.ready();
nickjillings@1318 105 }
nickjillings@1318 106 };
nickjillings@1318 107 offlineContext.startRendering();
nickjillings@1318 108 }
nickjillings@1318 109
nickjillings@1318 110 function calculateProcessedLoudness(buffer, winDur, overlap)
nickjillings@1318 111 {
nickjillings@1318 112 // Buffer Web Audio buffer node
nickjillings@1318 113 // winDur Window Duration in milliseconds
nickjillings@1318 114 // overlap Window overlap as normalised (0.5 = 50% overlap);
nickjillings@1318 115 if (buffer == undefined)
nickjillings@1318 116 {
nickjillings@1318 117 return 0;
nickjillings@1318 118 }
nickjillings@1318 119 if (winDur == undefined)
nickjillings@1318 120 {
nickjillings@1318 121 winDur = 400;
nickjillings@1318 122 }
nickjillings@1318 123 if (overlap == undefined)
nickjillings@1318 124 {
nickjillings@1318 125 overlap = 0.5;
nickjillings@1318 126 }
nickjillings@1318 127 var winSize = buffer.sampleRate*winDur/1000;
nickjillings@1318 128 var olapSize = (1-overlap)*winSize;
nickjillings@1318 129 var numberOfFrames = Math.floor(buffer.length/olapSize - winSize/olapSize + 1);
nickjillings@1318 130 var blockEnergy = new Array(buffer.numberOfChannels);
nickjillings@1318 131 for (var channel = 0; channel < buffer.numberOfChannels; channel++)
nickjillings@1318 132 {
nickjillings@1318 133 blockEnergy[channel] = new Float32Array(numberOfFrames);
nickjillings@1318 134 var data = buffer.getChannelData(channel);
nickjillings@1318 135 for (var i=0; i<numberOfFrames; i++)
nickjillings@1318 136 {
nickjillings@1318 137 var sigma = 0;
nickjillings@1318 138 for (var n=i*olapSize; n < i*olapSize+winSize; n++)
nickjillings@1318 139 {
nickjillings@1318 140 sigma += Math.pow(data[n],2);
nickjillings@1318 141 }
nickjillings@1318 142 blockEnergy[channel][i] = sigma/winSize;
nickjillings@1318 143 }
nickjillings@1318 144 }
nickjillings@1318 145 return blockEnergy;
nickjillings@1318 146 }
nickjillings@1318 147 function calculateLoudnessFromChannelBlocks(blockEnergy)
nickjillings@1318 148 {
nickjillings@1318 149 // Loudness
nickjillings@1318 150 var loudness = new Float32Array(blockEnergy[0].length);
nickjillings@1318 151 for (var i=0; i<blockEnergy[0].length; i++)
nickjillings@1318 152 {
nickjillings@1318 153 var sigma = 0;
nickjillings@1318 154 for (var channel = 0; channel < blockEnergy.length; channel++)
nickjillings@1318 155 {
nickjillings@1318 156 var G = 1.0;
nickjillings@1318 157 if (channel >= 4) {G = 1.41;}
nickjillings@1318 158 sigma += blockEnergy[channel][i]*G;
nickjillings@1318 159 }
nickjillings@1318 160 loudness[i] = -0.691 + 10*Math.log10(sigma);
nickjillings@1318 161 }
nickjillings@1318 162 return loudness;
nickjillings@1318 163 }
nickjillings@1318 164 function calculateOverallLoudnessFromChannelBlocks(blockEnergy)
nickjillings@1318 165 {
nickjillings@1318 166 // Loudness
nickjillings@1318 167 var summation = 0;
nickjillings@1318 168 for (var channel = 0; channel < blockEnergy.length; channel++)
nickjillings@1318 169 {
nickjillings@1318 170 var G = 1.0;
nickjillings@1318 171 if (channel >= 4) {G = 1.41;}
nickjillings@1318 172 var sigma = 0;
nickjillings@1318 173 for (var i=0; i<blockEnergy[0].length; i++)
nickjillings@1318 174 {
nickjillings@1318 175 blockEnergy[channel][i] *= G;
nickjillings@1318 176 sigma += blockEnergy[channel][i];
nickjillings@1318 177 }
nickjillings@1318 178 sigma /= blockEnergy.length;
nickjillings@1318 179 summation+= sigma;
nickjillings@1318 180 }
nickjillings@1318 181 return -0.691 + 10*Math.log10(summation);;
nickjillings@1318 182 }