annotate loudness.js @ 1389:1a72ddac0e17

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