n@749: /** n@749: * loundess.js n@749: * Loudness module for the Web Audio Evaluation Toolbox n@749: * Allows for automatic calculation of loudness of Web Audio API Buffer objects, n@749: * return gain values to correct for a target loudness or match loudness between n@749: * multiple objects n@749: */ n@749: n@749: var interval_cal_loudness_event = null; n@749: nicholas@752: if (typeof OfflineAudioContext == "undefined"){ nicholas@752: var OfflineAudioContext = webkitOfflineAudioContext; nicholas@752: } nicholas@752: n@749: function calculateLoudness(buffer, timescale, target, offlineContext) n@749: { n@749: // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS n@749: // buffer -> Web Audio API Buffer object n@749: // timescale -> M or Momentary (returns Array), S or Short (returns Array), n@749: // I or Integrated (default, returns number) n@749: // target -> default is -23 LUFS but can be any LUFS measurement. n@749: n@749: if (buffer == undefined) n@749: { n@749: return 0; n@749: } n@749: if (timescale == undefined) n@749: { n@749: timescale = "I"; n@749: } n@749: if (target == undefined) n@749: { n@749: target = -23; n@749: } n@749: if (offlineContext == undefined) n@749: { n@749: offlineContext = new OfflineAudioContext(buffer.numberOfChannels, buffer.length, buffer.sampleRate); n@749: } n@749: // Create the required filters n@749: var KFilter = offlineContext.createBiquadFilter(); n@749: KFilter.type = "highshelf"; n@749: KFilter.gain.value = 4; n@749: KFilter.frequency.value = 1480; n@749: n@749: var HPFilter = offlineContext.createBiquadFilter(); n@749: HPFilter.type = "highpass"; n@749: HPFilter.Q.value = 0.707; n@749: HPFilter.frequency.value = 60; n@749: // copy Data into the process buffer n@749: var processSource = offlineContext.createBufferSource(); n@749: processSource.buffer = buffer; n@749: n@749: processSource.connect(KFilter); n@749: KFilter.connect(HPFilter); n@749: HPFilter.connect(offlineContext.destination); n@749: processSource.start(); nicholas@752: offlineContext.oncomplete = function(renderedBuffer) { n@749: // Have the renderedBuffer information, now continue processing nicholas@752: if (typeof renderedBuffer.renderedBuffer == 'object') { nicholas@752: renderedBuffer = renderedBuffer.renderedBuffer; nicholas@752: } n@749: switch(timescale) n@749: { n@749: case "I": n@749: var blockEnergy = calculateProcessedLoudness(renderedBuffer, 400, 0.75); n@749: // Apply the absolute gate n@749: var loudness = calculateLoudnessFromChannelBlocks(blockEnergy); n@749: var absgatedEnergy = new Array(blockEnergy.length); n@749: for (var c=0; c= -70) n@749: { n@749: for (var c=0; c= relGateLevel) n@749: { n@749: for (var c=0; c= 4) {G = 1.41;} n@749: sigma += blockEnergy[channel][i]*G; n@749: } n@749: loudness[i] = -0.691 + 10*Math.log10(sigma); n@749: } n@749: return loudness; n@749: } n@749: function calculateOverallLoudnessFromChannelBlocks(blockEnergy) n@749: { n@749: // Loudness n@749: var summation = 0; n@749: for (var channel = 0; channel < blockEnergy.length; channel++) n@749: { n@749: var G = 1.0; n@749: if (channel >= 4) {G = 1.41;} n@749: var sigma = 0; n@749: for (var i=0; i