annotate loudness.js @ 1174:dd7ba3054bf9

Test create drag and drop active. Auto-builds on page HTML from specification.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Thu, 11 Feb 2016 12:06:54 +0000
parents c0022a09c4f6
children eef2d4ea18fb
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@1158 37 offlineContext = new OfflineAudioContext(audioContext.destination.channelCount, buffer.buffer.duration*audioContext.sampleRate, audioContext.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@1166 43 KFilter.frequency.value = 1500;
n@1118 44
n@1118 45 var HPFilter = offlineContext.createBiquadFilter();
n@1118 46 HPFilter.type = "highpass";
n@1166 47 HPFilter.Q.value = 0.5;
n@1166 48 HPFilter.frequency.value = 38;
n@1118 49 // copy Data into the process buffer
n@1118 50 var processSource = offlineContext.createBufferSource();
n@1142 51 processSource.buffer = 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@1157 65 // Calculate the Mean Squared of a signal
n@1157 66 var MS = calculateMeanSquared(renderedBuffer,0.4,0.75);
n@1157 67 // Calculate the Loudness of each block
n@1157 68 var MSL = calculateLoudnessFromBlocks(MS);
n@1157 69 // Get blocks from Absolute Gate
n@1157 70 var LK = loudnessGate(MSL,MS,-70);
n@1157 71 // Calculate Loudness
n@1157 72 var LK_gate = loudnessOfBlocks(LK);
n@1157 73 // Get blocks from Relative Gate
n@1157 74 var RK = loudnessGate(MSL,MS,LK_gate-10);
n@1157 75 var RK_gate = loudnessOfBlocks(RK);
n@1157 76 buffer.buffer.lufs = RK_gate;
n@1118 77 }
n@1159 78 buffer.ready();
n@1118 79 };
n@1118 80 offlineContext.startRendering();
n@1118 81 }
n@1118 82
n@1157 83 function calculateMeanSquared(buffer,frame_dur,frame_overlap)
n@1118 84 {
n@1157 85 frame_size = Math.floor(buffer.sampleRate*frame_dur);
n@1157 86 step_size = Math.floor(frame_size*(1.0-frame_overlap));
n@1157 87 num_frames = Math.floor((buffer.length-frame_size)/step_size);
n@1157 88
n@1157 89 MS = Array(buffer.numberOfChannels);
n@1157 90 for (var c=0; c<buffer.numberOfChannels; c++)
n@1157 91 {
n@1157 92 MS[c] = new Float32Array(num_frames);
n@1157 93 var data = buffer.getChannelData(c);
n@1157 94 for (var no=0; no<num_frames; no++)
n@1157 95 {
n@1157 96 MS[c][no] = 0.0;
n@1157 97 for (var ptr=0; ptr<frame_size; ptr++)
n@1157 98 {
n@1157 99 var sample = data[no*step_size+ptr];
n@1157 100 MS[c][no] += sample*sample;
n@1157 101 }
n@1157 102 MS[c][no] /= frame_size;
n@1157 103 }
n@1157 104 }
n@1157 105 return MS;
n@1118 106 }
n@1157 107
n@1157 108 function calculateLoudnessFromBlocks(blocks)
n@1118 109 {
n@1157 110 var num_frames = blocks[0].length;
n@1157 111 var num_channels = blocks.length;
n@1157 112 var MSL = Array(num_frames);
n@1157 113 for (var n=0; n<num_frames; n++)
n@1157 114 {
n@1157 115 var sum = 0;
n@1157 116 for (var c=0; c<num_channels; c++)
n@1157 117 {
n@1157 118 var G = 1.0;
n@1157 119 if(G >= 3){G = 1.41;}
n@1157 120 sum += blocks[c][n]*G;
n@1157 121 }
n@1157 122 MSL[n] = -0.691 + 10*Math.log10(sum);
n@1157 123 }
n@1157 124 return MSL;
n@1118 125 }
n@1157 126
n@1157 127 function loudnessGate(blocks,source,threshold)
n@1118 128 {
n@1157 129 var num_frames = source[0].length;
n@1157 130 var num_channels = source.length;
n@1157 131 var LK = Array(num_channels);
n@1157 132 for (var c=0; c<num_channels; c++)
n@1157 133 {
n@1157 134 LK[c] = [];
n@1157 135 }
n@1157 136
n@1157 137 for (var n=0; n<num_frames; n++)
n@1157 138 {
n@1157 139 if (blocks[n] > threshold)
n@1157 140 {
n@1157 141 for (var c=0; c<num_channels; c++)
n@1157 142 {
n@1157 143 LK[c].push(source[c][n]);
n@1157 144 }
n@1157 145 }
n@1157 146 }
n@1157 147 return LK;
n@1118 148 }
n@1157 149
n@1157 150 function loudnessOfBlocks(blocks)
n@1157 151 {
n@1157 152 var num_frames = blocks[0].length;
n@1157 153 var num_channels = blocks.length;
n@1157 154 var loudness = 0.0;
n@1157 155 for (var n=0; n<num_frames; n++)
n@1157 156 {
n@1157 157 var sum = 0;
n@1157 158 for (var c=0; c<num_channels; c++)
n@1157 159 {
n@1157 160 var G = 1.0;
n@1157 161 if(G >= 3){G = 1.41;}
n@1157 162 sum += blocks[c][n]*G;
n@1157 163 }
n@1157 164 sum /= num_frames;
n@1157 165 loudness += sum;
n@1157 166 }
n@1157 167 loudness = -0.691 + 10 * Math.log10(loudness);
n@1157 168 return loudness;
n@1157 169 }