djmoffat@1099: /** djmoffat@1099: * loundess.js djmoffat@1099: * Loudness module for the Web Audio Evaluation Toolbox djmoffat@1099: * Allows for automatic calculation of loudness of Web Audio API Buffer objects, djmoffat@1099: * return gain values to correct for a target loudness or match loudness between djmoffat@1099: * multiple objects djmoffat@1099: */ djmoffat@1099: djmoffat@1099: var interval_cal_loudness_event = null; djmoffat@1099: djmoffat@1099: if (typeof OfflineAudioContext == "undefined"){ djmoffat@1099: var OfflineAudioContext = webkitOfflineAudioContext; djmoffat@1099: } djmoffat@1099: djmoffat@1099: function calculateLoudness(buffer, timescale, target, offlineContext) djmoffat@1099: { djmoffat@1099: // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS djmoffat@1099: // buffer -> Web Audio API Buffer object djmoffat@1099: // timescale -> M or Momentary (returns Array), S or Short (returns Array), djmoffat@1099: // I or Integrated (default, returns number) djmoffat@1099: // target -> default is -23 LUFS but can be any LUFS measurement. djmoffat@1099: djmoffat@1099: if (buffer == undefined) djmoffat@1099: { djmoffat@1099: return 0; djmoffat@1099: } djmoffat@1099: if (timescale == undefined) djmoffat@1099: { djmoffat@1099: timescale = "I"; djmoffat@1099: } djmoffat@1099: if (target == undefined) djmoffat@1099: { djmoffat@1099: target = -23; djmoffat@1099: } djmoffat@1099: if (offlineContext == undefined) djmoffat@1099: { djmoffat@1099: offlineContext = new OfflineAudioContext(audioContext.destination.channelCount, buffer.buffer.duration*audioContext.sampleRate, audioContext.sampleRate); djmoffat@1099: } djmoffat@1099: // Create the required filters djmoffat@1099: var KFilter = offlineContext.createBiquadFilter(); djmoffat@1099: KFilter.type = "highshelf"; djmoffat@1099: KFilter.gain.value = 4; djmoffat@1099: KFilter.frequency.value = 1500; djmoffat@1099: djmoffat@1099: var HPFilter = offlineContext.createBiquadFilter(); djmoffat@1099: HPFilter.type = "highpass"; djmoffat@1099: HPFilter.Q.value = 0.5; djmoffat@1099: HPFilter.frequency.value = 38; djmoffat@1099: // copy Data into the process buffer djmoffat@1099: var processSource = offlineContext.createBufferSource(); djmoffat@1099: processSource.buffer = buffer.buffer; djmoffat@1099: djmoffat@1099: processSource.connect(KFilter); djmoffat@1099: KFilter.connect(HPFilter); djmoffat@1099: HPFilter.connect(offlineContext.destination); djmoffat@1099: processSource.start(); djmoffat@1099: offlineContext.oncomplete = function(renderedBuffer) { djmoffat@1099: // Have the renderedBuffer information, now continue processing djmoffat@1099: if (typeof renderedBuffer.renderedBuffer == 'object') { djmoffat@1099: renderedBuffer = renderedBuffer.renderedBuffer; djmoffat@1099: } djmoffat@1099: switch(timescale) djmoffat@1099: { djmoffat@1099: case "I": djmoffat@1099: // Calculate the Mean Squared of a signal djmoffat@1099: var MS = calculateMeanSquared(renderedBuffer,0.4,0.75); djmoffat@1099: // Calculate the Loudness of each block djmoffat@1099: var MSL = calculateLoudnessFromBlocks(MS); djmoffat@1099: // Get blocks from Absolute Gate djmoffat@1099: var LK = loudnessGate(MSL,MS,-70); djmoffat@1099: // Calculate Loudness djmoffat@1099: var LK_gate = loudnessOfBlocks(LK); djmoffat@1099: // Get blocks from Relative Gate djmoffat@1099: var RK = loudnessGate(MSL,MS,LK_gate-10); djmoffat@1099: var RK_gate = loudnessOfBlocks(RK); djmoffat@1099: buffer.buffer.lufs = RK_gate; djmoffat@1099: } djmoffat@1099: buffer.ready(); djmoffat@1099: }; djmoffat@1099: offlineContext.startRendering(); djmoffat@1099: } djmoffat@1099: djmoffat@1099: function calculateMeanSquared(buffer,frame_dur,frame_overlap) djmoffat@1099: { djmoffat@1099: frame_size = Math.floor(buffer.sampleRate*frame_dur); djmoffat@1099: step_size = Math.floor(frame_size*(1.0-frame_overlap)); djmoffat@1099: num_frames = Math.floor((buffer.length-frame_size)/step_size); djmoffat@1099: djmoffat@1099: MS = Array(buffer.numberOfChannels); djmoffat@1099: for (var c=0; c= 3){G = 1.41;} djmoffat@1099: sum += blocks[c][n]*G; djmoffat@1099: } djmoffat@1099: MSL[n] = -0.691 + 10*Math.log10(sum); djmoffat@1099: } djmoffat@1099: return MSL; djmoffat@1099: } djmoffat@1099: djmoffat@1099: function loudnessGate(blocks,source,threshold) djmoffat@1099: { djmoffat@1099: var num_frames = source[0].length; djmoffat@1099: var num_channels = source.length; djmoffat@1099: var LK = Array(num_channels); djmoffat@1099: for (var c=0; c threshold) djmoffat@1099: { djmoffat@1099: for (var c=0; c= 3){G = 1.41;} djmoffat@1099: sum += blocks[c][n]*G; djmoffat@1099: } djmoffat@1099: sum /= num_frames; djmoffat@1099: loudness += sum; djmoffat@1099: } djmoffat@1099: loudness = -0.691 + 10 * Math.log10(loudness); djmoffat@1099: return loudness; djmoffat@1099: }