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