annotate loudness.js @ 749:07c996307cbd

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