comparison loudness.js @ 749:07c996307cbd

Bug #1510 Fixed.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Mon, 21 Dec 2015 11:53:05 +0000
parents
children fab881bdeeac 43801b3d6131
comparison
equal deleted inserted replaced
-1:000000000000 749:07c996307cbd
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 function calculateLoudness(buffer, timescale, target, offlineContext)
12 {
13 // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS
14 // buffer -> Web Audio API Buffer object
15 // timescale -> M or Momentary (returns Array), S or Short (returns Array),
16 // I or Integrated (default, returns number)
17 // target -> default is -23 LUFS but can be any LUFS measurement.
18
19 if (buffer == undefined)
20 {
21 return 0;
22 }
23 if (timescale == undefined)
24 {
25 timescale = "I";
26 }
27 if (target == undefined)
28 {
29 target = -23;
30 }
31 if (offlineContext == undefined)
32 {
33 offlineContext = new OfflineAudioContext(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
34 }
35 // Create the required filters
36 var KFilter = offlineContext.createBiquadFilter();
37 KFilter.type = "highshelf";
38 KFilter.gain.value = 4;
39 KFilter.frequency.value = 1480;
40
41 var HPFilter = offlineContext.createBiquadFilter();
42 HPFilter.type = "highpass";
43 HPFilter.Q.value = 0.707;
44 HPFilter.frequency.value = 60;
45 // copy Data into the process buffer
46 var processSource = offlineContext.createBufferSource();
47 processSource.buffer = buffer;
48
49 processSource.connect(KFilter);
50 KFilter.connect(HPFilter);
51 HPFilter.connect(offlineContext.destination);
52 processSource.start();
53 offlineContext.startRendering().then(function(renderedBuffer) {
54 // Have the renderedBuffer information, now continue processing
55 switch(timescale)
56 {
57 case "I":
58 var blockEnergy = calculateProcessedLoudness(renderedBuffer, 400, 0.75);
59 // Apply the absolute gate
60 var loudness = calculateLoudnessFromChannelBlocks(blockEnergy);
61 var absgatedEnergy = new Array(blockEnergy.length);
62 for (var c=0; c<blockEnergy.length; c++)
63 {
64 absgatedEnergy[c] = [];
65 }
66 for (var i=0; i<loudness.length; i++)
67 {
68 if (loudness[i] >= -70)
69 {
70 for (var c=0; c<blockEnergy.length; c++)
71 {
72 absgatedEnergy[c].push(blockEnergy[c][i]);
73 }
74 }
75 }
76 var overallAbsLoudness = calculateOverallLoudnessFromChannelBlocks(absgatedEnergy);
77
78 //applying the relative gate 8 dB down from overallAbsLoudness
79 var relGateLevel = overallAbsLoudness - 8;
80 var relgateEnergy = new Array(blockEnergy.length);
81 for (var c=0; c<blockEnergy.length; c++)
82 {
83 relgateEnergy[c] = [];
84 }
85 for (var i=0; i<loudness.length; i++)
86 {
87 if (loudness[i] >= relGateLevel)
88 {
89 for (var c=0; c<blockEnergy.length; c++)
90 {
91 relgateEnergy[c].push(blockEnergy[c][i]);
92 }
93 }
94 }
95 var overallRelLoudness = calculateOverallLoudnessFromChannelBlocks(relgateEnergy);
96 buffer.lufs = overallRelLoudness;
97 }
98 }).catch(function(err) {
99 console.log(err);
100 buffer.lufs = 1;
101 });
102 }
103
104 function calculateProcessedLoudness(buffer, winDur, overlap)
105 {
106 // Buffer Web Audio buffer node
107 // winDur Window Duration in milliseconds
108 // overlap Window overlap as normalised (0.5 = 50% overlap);
109 if (buffer == undefined)
110 {
111 return 0;
112 }
113 if (winDur == undefined)
114 {
115 winDur = 400;
116 }
117 if (overlap == undefined)
118 {
119 overlap = 0.5;
120 }
121 var winSize = buffer.sampleRate*winDur/1000;
122 var olapSize = (1-overlap)*winSize;
123 var numberOfFrames = Math.floor(buffer.length/olapSize - winSize/olapSize + 1);
124 var blockEnergy = new Array(buffer.numberOfChannels);
125 for (var channel = 0; channel < buffer.numberOfChannels; channel++)
126 {
127 blockEnergy[channel] = new Float32Array(numberOfFrames);
128 var data = buffer.getChannelData(channel);
129 for (var i=0; i<numberOfFrames; i++)
130 {
131 var sigma = 0;
132 for (var n=i*olapSize; n < i*olapSize+winSize; n++)
133 {
134 sigma += Math.pow(data[n],2);
135 }
136 blockEnergy[channel][i] = sigma/winSize;
137 }
138 }
139 return blockEnergy;
140 }
141 function calculateLoudnessFromChannelBlocks(blockEnergy)
142 {
143 // Loudness
144 var loudness = new Float32Array(blockEnergy[0].length);
145 for (var i=0; i<blockEnergy[0].length; i++)
146 {
147 var sigma = 0;
148 for (var channel = 0; channel < blockEnergy.length; channel++)
149 {
150 var G = 1.0;
151 if (channel >= 4) {G = 1.41;}
152 sigma += blockEnergy[channel][i]*G;
153 }
154 loudness[i] = -0.691 + 10*Math.log10(sigma);
155 }
156 return loudness;
157 }
158 function calculateOverallLoudnessFromChannelBlocks(blockEnergy)
159 {
160 // Loudness
161 var summation = 0;
162 for (var channel = 0; channel < blockEnergy.length; channel++)
163 {
164 var G = 1.0;
165 if (channel >= 4) {G = 1.41;}
166 var sigma = 0;
167 for (var i=0; i<blockEnergy[0].length; i++)
168 {
169 blockEnergy[channel][i] *= G;
170 sigma += blockEnergy[channel][i];
171 }
172 sigma /= blockEnergy.length;
173 summation+= sigma;
174 }
175 return -0.691 + 10*Math.log10(summation);;
176 }