diff js/loudness.js @ 2224:760719986df3

Tidy up file locations.
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Thu, 14 Apr 2016 13:54:24 +0100
parents
children aca96a5183be
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/loudness.js	Thu Apr 14 13:54:24 2016 +0100
@@ -0,0 +1,169 @@
+/**
+ *  loundess.js
+ *  Loudness module for the Web Audio Evaluation Toolbox
+ *  Allows for automatic calculation of loudness of Web Audio API Buffer objects,
+ * 	return gain values to correct for a target loudness or match loudness between
+ *  multiple objects
+ */
+
+var interval_cal_loudness_event = null;
+
+if (typeof OfflineAudioContext == "undefined"){
+	var OfflineAudioContext = webkitOfflineAudioContext;
+}
+
+function calculateLoudness(buffer, timescale, target, offlineContext)
+{
+	// This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS
+	// buffer -> Web Audio API Buffer object
+	// timescale -> M or Momentary (returns Array), S or Short (returns Array),
+	//   I or Integrated (default, returns number)
+	// target -> default is -23 LUFS but can be any LUFS measurement.
+	
+	if (buffer == undefined)
+	{
+		return 0;
+	}
+	if (timescale == undefined)
+	{
+		timescale = "I";
+	}
+	if (target == undefined)
+	{
+		target = -23;
+	}
+	if (offlineContext == undefined)
+	{
+		offlineContext = new OfflineAudioContext(audioContext.destination.channelCount, buffer.buffer.duration*audioContext.sampleRate, audioContext.sampleRate);
+	}
+	// Create the required filters
+	var KFilter = offlineContext.createBiquadFilter();
+	KFilter.type = "highshelf";
+	KFilter.gain.value = 4;
+	KFilter.frequency.value = 1500;
+	
+	var HPFilter = offlineContext.createBiquadFilter();
+	HPFilter.type = "highpass";
+	HPFilter.Q.value = 0.5;
+	HPFilter.frequency.value = 38;
+	// copy Data into the process buffer
+	var processSource = offlineContext.createBufferSource();
+	processSource.buffer = buffer.buffer;
+	
+	processSource.connect(KFilter);
+	KFilter.connect(HPFilter);
+	HPFilter.connect(offlineContext.destination);
+	offlineContext.oncomplete = function(renderedBuffer) {
+		// Have the renderedBuffer information, now continue processing
+		if (typeof renderedBuffer.renderedBuffer == 'object') {
+			renderedBuffer = renderedBuffer.renderedBuffer;
+		}
+		switch(timescale)
+		{
+		case "I":
+            // Calculate the Mean Squared of a signal
+            var MS = calculateMeanSquared(renderedBuffer,0.4,0.75);
+            // Calculate the Loudness of each block
+            var MSL = calculateLoudnessFromBlocks(MS);
+            // Get blocks from Absolute Gate
+            var LK = loudnessGate(MSL,MS,-70);
+            // Calculate Loudness
+            var LK_gate = loudnessOfBlocks(LK);
+            // Get blocks from Relative Gate
+            var RK = loudnessGate(MSL,MS,LK_gate-10);
+            var RK_gate = loudnessOfBlocks(RK);
+            buffer.buffer.lufs = RK_gate;
+		}
+        buffer.ready();
+	};
+    processSource.start(0);
+	offlineContext.startRendering();
+}
+
+function calculateMeanSquared(buffer,frame_dur,frame_overlap)
+{
+    frame_size = Math.floor(buffer.sampleRate*frame_dur);
+    step_size = Math.floor(frame_size*(1.0-frame_overlap));
+    num_frames = Math.floor((buffer.length-frame_size)/step_size);
+    
+    MS = Array(buffer.numberOfChannels);
+    for (var c=0; c<buffer.numberOfChannels; c++)
+    {
+        MS[c] = new Float32Array(num_frames);
+        var data = buffer.getChannelData(c);
+        for (var no=0; no<num_frames; no++)
+        {
+            MS[c][no] = 0.0;
+            for (var ptr=0; ptr<frame_size; ptr++)
+            {
+                var sample = data[no*step_size+ptr];
+                MS[c][no] += sample*sample;
+            }
+            MS[c][no] /= frame_size;
+        }
+    }
+    return MS;
+}
+
+function calculateLoudnessFromBlocks(blocks)
+{
+    var num_frames = blocks[0].length;
+    var num_channels = blocks.length;
+    var MSL = Array(num_frames);
+    for (var n=0; n<num_frames; n++)
+    {
+        var sum = 0;
+        for (var c=0; c<num_channels; c++)
+        {
+            var G = 1.0;
+            if(G >= 3){G = 1.41;}
+            sum += blocks[c][n]*G;
+        }
+        MSL[n] = -0.691 + 10*Math.log10(sum);
+    }
+    return MSL;
+}
+
+function loudnessGate(blocks,source,threshold)
+{
+    var num_frames = source[0].length;
+    var num_channels = source.length;
+    var LK = Array(num_channels);
+    for (var c=0; c<num_channels; c++)
+    {
+        LK[c] = [];
+    }
+    
+    for (var n=0; n<num_frames; n++)
+    {
+        if (blocks[n] > threshold)
+        {
+            for (var c=0; c<num_channels; c++)
+            {
+                LK[c].push(source[c][n]);
+            }
+        }
+    }
+    return LK;
+}
+
+function loudnessOfBlocks(blocks)
+{
+    var num_frames = blocks[0].length;
+    var num_channels = blocks.length;
+    var loudness = 0.0;
+    for (var n=0; n<num_frames; n++)
+    {
+        var sum = 0;
+        for (var c=0; c<num_channels; c++)
+        {
+            var G = 1.0;
+            if(G >= 3){G = 1.41;}
+            sum += blocks[c][n]*G;
+        }
+        sum /= num_frames;
+        loudness += sum;
+    }
+    loudness = -0.691 + 10 * Math.log10(loudness);
+    return loudness;
+}
\ No newline at end of file