annotate loudness.js @ 486:3bcee92d95ab Dev_main

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