annotate loudness.js @ 1121:595511282fa7

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