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