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