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