view loudness.js @ 1168:c0022a09c4f6

Merge
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Mon, 01 Feb 2016 10:38:54 +0000
parents c44fbf72f7f2 ca08f21777c0
children eef2d4ea18fb
line wrap: on
line source
/**
 *  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);
	processSource.start();
	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();
	};
	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;
}