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