annotate js/loudness.js @ 2438:02df8de58e7b

Merge branch 'master' of https://github.com/BrechtDeMan/WebAudioEvaluationTool
author www-data <www-data@sucuk.dcs.qmul.ac.uk>
date Thu, 02 Jun 2016 17:20:55 +0100
parents aca96a5183be
children 464c6c6692d6
rev   line source
nicholas@2224 1 /**
nicholas@2224 2 * loundess.js
nicholas@2224 3 * Loudness module for the Web Audio Evaluation Toolbox
nicholas@2224 4 * Allows for automatic calculation of loudness of Web Audio API Buffer objects,
nicholas@2224 5 * return gain values to correct for a target loudness or match loudness between
nicholas@2224 6 * multiple objects
nicholas@2224 7 */
nicholas@2224 8
nicholas@2224 9 var interval_cal_loudness_event = null;
nicholas@2224 10
nicholas@2224 11 if (typeof OfflineAudioContext == "undefined"){
nicholas@2224 12 var OfflineAudioContext = webkitOfflineAudioContext;
nicholas@2224 13 }
nicholas@2224 14
nicholas@2224 15 function calculateLoudness(buffer, timescale, target, offlineContext)
nicholas@2224 16 {
nicholas@2224 17 // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS
nicholas@2224 18 // buffer -> Web Audio API Buffer object
nicholas@2224 19 // timescale -> M or Momentary (returns Array), S or Short (returns Array),
nicholas@2224 20 // I or Integrated (default, returns number)
nicholas@2224 21 // target -> default is -23 LUFS but can be any LUFS measurement.
n@2432 22 if(navigator.platform == 'iPad' || navigator.platform == 'iPhone') {
n@2432 23 buffer.ready();
n@2432 24 }
nicholas@2224 25 if (buffer == undefined)
nicholas@2224 26 {
nicholas@2224 27 return 0;
nicholas@2224 28 }
nicholas@2224 29 if (timescale == undefined)
nicholas@2224 30 {
nicholas@2224 31 timescale = "I";
nicholas@2224 32 }
nicholas@2224 33 if (target == undefined)
nicholas@2224 34 {
nicholas@2224 35 target = -23;
nicholas@2224 36 }
nicholas@2224 37 if (offlineContext == undefined)
nicholas@2224 38 {
nicholas@2224 39 offlineContext = new OfflineAudioContext(audioContext.destination.channelCount, buffer.buffer.duration*audioContext.sampleRate, audioContext.sampleRate);
nicholas@2224 40 }
nicholas@2224 41 // Create the required filters
nicholas@2224 42 var KFilter = offlineContext.createBiquadFilter();
nicholas@2224 43 KFilter.type = "highshelf";
nicholas@2224 44 KFilter.gain.value = 4;
nicholas@2224 45 KFilter.frequency.value = 1500;
nicholas@2224 46
nicholas@2224 47 var HPFilter = offlineContext.createBiquadFilter();
nicholas@2224 48 HPFilter.type = "highpass";
nicholas@2224 49 HPFilter.Q.value = 0.5;
nicholas@2224 50 HPFilter.frequency.value = 38;
nicholas@2224 51 // copy Data into the process buffer
nicholas@2224 52 var processSource = offlineContext.createBufferSource();
nicholas@2224 53 processSource.buffer = buffer.buffer;
nicholas@2224 54
nicholas@2224 55 processSource.connect(KFilter);
nicholas@2224 56 KFilter.connect(HPFilter);
nicholas@2224 57 HPFilter.connect(offlineContext.destination);
nicholas@2224 58 offlineContext.oncomplete = function(renderedBuffer) {
nicholas@2224 59 // Have the renderedBuffer information, now continue processing
nicholas@2224 60 if (typeof renderedBuffer.renderedBuffer == 'object') {
nicholas@2224 61 renderedBuffer = renderedBuffer.renderedBuffer;
nicholas@2224 62 }
nicholas@2224 63 switch(timescale)
nicholas@2224 64 {
nicholas@2224 65 case "I":
nicholas@2224 66 // Calculate the Mean Squared of a signal
nicholas@2224 67 var MS = calculateMeanSquared(renderedBuffer,0.4,0.75);
nicholas@2224 68 // Calculate the Loudness of each block
nicholas@2224 69 var MSL = calculateLoudnessFromBlocks(MS);
nicholas@2224 70 // Get blocks from Absolute Gate
nicholas@2224 71 var LK = loudnessGate(MSL,MS,-70);
nicholas@2224 72 // Calculate Loudness
nicholas@2224 73 var LK_gate = loudnessOfBlocks(LK);
nicholas@2224 74 // Get blocks from Relative Gate
nicholas@2224 75 var RK = loudnessGate(MSL,MS,LK_gate-10);
nicholas@2224 76 var RK_gate = loudnessOfBlocks(RK);
nicholas@2224 77 buffer.buffer.lufs = RK_gate;
nicholas@2224 78 }
nicholas@2224 79 buffer.ready();
nicholas@2224 80 };
nicholas@2224 81 processSource.start(0);
nicholas@2224 82 offlineContext.startRendering();
nicholas@2224 83 }
nicholas@2224 84
nicholas@2224 85 function calculateMeanSquared(buffer,frame_dur,frame_overlap)
nicholas@2224 86 {
nicholas@2224 87 frame_size = Math.floor(buffer.sampleRate*frame_dur);
nicholas@2224 88 step_size = Math.floor(frame_size*(1.0-frame_overlap));
nicholas@2224 89 num_frames = Math.floor((buffer.length-frame_size)/step_size);
nicholas@2224 90
nicholas@2224 91 MS = Array(buffer.numberOfChannels);
nicholas@2224 92 for (var c=0; c<buffer.numberOfChannels; c++)
nicholas@2224 93 {
nicholas@2224 94 MS[c] = new Float32Array(num_frames);
nicholas@2224 95 var data = buffer.getChannelData(c);
nicholas@2224 96 for (var no=0; no<num_frames; no++)
nicholas@2224 97 {
nicholas@2224 98 MS[c][no] = 0.0;
nicholas@2224 99 for (var ptr=0; ptr<frame_size; ptr++)
nicholas@2224 100 {
nicholas@2224 101 var sample = data[no*step_size+ptr];
nicholas@2224 102 MS[c][no] += sample*sample;
nicholas@2224 103 }
nicholas@2224 104 MS[c][no] /= frame_size;
nicholas@2224 105 }
nicholas@2224 106 }
nicholas@2224 107 return MS;
nicholas@2224 108 }
nicholas@2224 109
nicholas@2224 110 function calculateLoudnessFromBlocks(blocks)
nicholas@2224 111 {
nicholas@2224 112 var num_frames = blocks[0].length;
nicholas@2224 113 var num_channels = blocks.length;
nicholas@2224 114 var MSL = Array(num_frames);
nicholas@2224 115 for (var n=0; n<num_frames; n++)
nicholas@2224 116 {
nicholas@2224 117 var sum = 0;
nicholas@2224 118 for (var c=0; c<num_channels; c++)
nicholas@2224 119 {
nicholas@2224 120 var G = 1.0;
nicholas@2224 121 if(G >= 3){G = 1.41;}
nicholas@2224 122 sum += blocks[c][n]*G;
nicholas@2224 123 }
nicholas@2224 124 MSL[n] = -0.691 + 10*Math.log10(sum);
nicholas@2224 125 }
nicholas@2224 126 return MSL;
nicholas@2224 127 }
nicholas@2224 128
nicholas@2224 129 function loudnessGate(blocks,source,threshold)
nicholas@2224 130 {
nicholas@2224 131 var num_frames = source[0].length;
nicholas@2224 132 var num_channels = source.length;
nicholas@2224 133 var LK = Array(num_channels);
nicholas@2224 134 for (var c=0; c<num_channels; c++)
nicholas@2224 135 {
nicholas@2224 136 LK[c] = [];
nicholas@2224 137 }
nicholas@2224 138
nicholas@2224 139 for (var n=0; n<num_frames; n++)
nicholas@2224 140 {
nicholas@2224 141 if (blocks[n] > threshold)
nicholas@2224 142 {
nicholas@2224 143 for (var c=0; c<num_channels; c++)
nicholas@2224 144 {
nicholas@2224 145 LK[c].push(source[c][n]);
nicholas@2224 146 }
nicholas@2224 147 }
nicholas@2224 148 }
nicholas@2224 149 return LK;
nicholas@2224 150 }
nicholas@2224 151
nicholas@2224 152 function loudnessOfBlocks(blocks)
nicholas@2224 153 {
nicholas@2224 154 var num_frames = blocks[0].length;
nicholas@2224 155 var num_channels = blocks.length;
nicholas@2224 156 var loudness = 0.0;
nicholas@2224 157 for (var n=0; n<num_frames; n++)
nicholas@2224 158 {
nicholas@2224 159 var sum = 0;
nicholas@2224 160 for (var c=0; c<num_channels; c++)
nicholas@2224 161 {
nicholas@2224 162 var G = 1.0;
nicholas@2224 163 if(G >= 3){G = 1.41;}
nicholas@2224 164 sum += blocks[c][n]*G;
nicholas@2224 165 }
nicholas@2224 166 sum /= num_frames;
nicholas@2224 167 loudness += sum;
nicholas@2224 168 }
nicholas@2224 169 loudness = -0.691 + 10 * Math.log10(loudness);
nicholas@2224 170 return loudness;
nicholas@2224 171 }