annotate loudness.js @ 1359:89ad0630d860

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