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