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 }