annotate js/loudness.js @ 2538:464c6c6692d6

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