annotate loudness.js @ 1089:3de455e48d70

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