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