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