Mercurial > hg > webaudioevaluationtool
view loudness.js @ 1116:c44fbf72f7f2
All interfaces support comment boxes. Comment box identification matches presented tag (for instance, AB will be Comment on fragment A, rather than 1). Tighter buffer loading protocol, audioObjects register with the buffer rather than checking for buffer existence (which can be buggy depending on the buffer state). Buffers now have a state to ensure exact location in loading chain (downloading, decoding, LUFS, ready).
author | Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk> |
---|---|
date | Fri, 29 Jan 2016 11:11:57 +0000 |
parents | |
children | b5bf2f57187c c0022a09c4f6 |
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; }