comparison js/loudness.js @ 2224:760719986df3

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