matthiasm@0: function [f_audio_out,timepositions_afterDegr] = degradationUnit_applyDynamicRangeCompression(f_audio, samplingFreq, timepositions_beforeDegr, parameter) matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: % Name: degradation_applyDynamicRangeCompression matthiasm@0: % Version: 1 matthiasm@0: % Date: 2013-01-23 matthiasm@0: % Programmer: Matthias Mauch, Sebastian Ewert matthiasm@0: % matthiasm@0: % Description: matthiasm@0: % - applies dynamic range compression to a signal matthiasm@0: % - f_audio_out is the compressed audio matthiasm@0: % matthiasm@0: % Input: matthiasm@0: % f_audio - audio signal \in [-1,1]^{NxC} with C being the number of matthiasm@0: % channels matthiasm@0: % samplingFreq - sampling frequency of f_audio matthiasm@0: % timepositions_beforeDegr - some degradations delay the input signal. If matthiasm@0: % some points in time are given via this matthiasm@0: % parameter, timepositions_afterDegr will matthiasm@0: % return the corresponding positions in the matthiasm@0: % output. Set to [] if unavailable. Set f_audio matthiasm@0: % and samplingFreq to [] to compute only matthiasm@0: % timepositions_afterDegr. matthiasm@0: % matthiasm@0: % Input (optional): parameter matthiasm@0: % .preNormalization = 0; % db for 95% RMS quantile / 0 means "off" matthiasm@0: % .forgettingTime = 0.1; % seconds matthiasm@0: % .compressorThreshold = -40; % dB matthiasm@0: % .compressorSlope = 0.9; matthiasm@0: % .attackTime = 0.01; % seconds matthiasm@0: % .releaseTime = 0.01; % seconds matthiasm@0: % .delayTime = 0.01; % seconds matthiasm@0: % .normalizeOutputAudio = 1; matthiasm@0: % matthiasm@0: % Output: matthiasm@0: % f_audio_out - audio output signal matthiasm@0: % matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: % Audio Degradation Toolbox matthiasm@0: % matthiasm@0: % Centre for Digital Music, Queen Mary University of London. matthiasm@0: % This file copyright 2013 Sebastian Ewert, Matthias Mauch and QMUL. matthiasm@0: % matthiasm@0: % This program is free software; you can redistribute it and/or matthiasm@0: % modify it under the terms of the GNU General Public License as matthiasm@0: % published by the Free Software Foundation; either version 2 of the matthiasm@0: % License, or (at your option) any later version. See the file matthiasm@0: % COPYING included with this distribution for more information. matthiasm@0: % matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: % Check parameters matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: if nargin<4 matthiasm@0: parameter=[]; matthiasm@0: end matthiasm@0: if nargin<3 matthiasm@0: timepositions_beforeDegr=[]; matthiasm@0: end matthiasm@0: if nargin<2 matthiasm@0: error('Please specify input data'); matthiasm@0: end matthiasm@0: matthiasm@0: if isfield(parameter,'preNormalization')==0 matthiasm@0: parameter.preNormalization = 0; % db for 95% RMS quantile / 0 means "off" matthiasm@0: end matthiasm@0: if isfield(parameter,'forgettingTime')==0 matthiasm@0: parameter.forgettingTime = 0.1; % seconds matthiasm@0: end matthiasm@0: if isfield(parameter,'compressorThreshold')==0 matthiasm@0: parameter.compressorThreshold = -40; % dB matthiasm@0: end matthiasm@0: if isfield(parameter,'compressorSlope')==0 matthiasm@0: parameter.compressorSlope = 0.9; matthiasm@0: end matthiasm@0: if isfield(parameter,'attackTime')==0 matthiasm@0: parameter.attackTime = 0.01; % seconds matthiasm@0: end matthiasm@0: if isfield(parameter,'releaseTime')==0 matthiasm@0: parameter.releaseTime = 0.01; % seconds matthiasm@0: end matthiasm@0: if isfield(parameter,'delayTime')==0 matthiasm@0: parameter.delayTime = 0.01; % seconds matthiasm@0: end matthiasm@0: if isfield(parameter,'normalizeOutputAudio')==0 matthiasm@0: parameter.normalizeOutputAudio = 1; matthiasm@0: end matthiasm@0: matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: % Main program matthiasm@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% matthiasm@0: matthiasm@0: f_audio_out = []; matthiasm@0: if ~isempty(f_audio) matthiasm@0: matthiasm@0: %% secondary parameters matthiasm@0: matthiasm@0: AT = 1 - exp(-2.2/(samplingFreq*parameter.attackTime)); % attack parameter matthiasm@0: RT = 1 - exp(-2.2/(samplingFreq*parameter.releaseTime)); % release parameter matthiasm@0: FT = 1 - exp(-2.2/(samplingFreq*parameter.forgettingTime)); % forgetting parameter matthiasm@0: matthiasm@0: delay = floor(parameter.delayTime * samplingFreq); matthiasm@0: matthiasm@0: mono_audio = mean(f_audio, 2); matthiasm@0: matthiasm@0: if parameter.preNormalization < 0 matthiasm@0: quantMeasured = max(quantile(abs(mono_audio), 0.95),eps); matthiasm@0: quantWanted = db2mag(parameter.preNormalization); matthiasm@0: f_audio = f_audio * quantWanted / quantMeasured; matthiasm@0: mono_audio = mono_audio * quantWanted / quantMeasured; matthiasm@0: end matthiasm@0: matthiasm@0: %% matthiasm@0: matthiasm@0: nSample = size(f_audio, 1); matthiasm@0: nChannel = size(f_audio, 2); matthiasm@0: matthiasm@0: if AT == RT matthiasm@0: % Vectorized version matthiasm@0: %% matthiasm@0: runningMS_all = filter(FT,[1 -(1-FT)],mono_audio.^2); matthiasm@0: runningRMSdB_all = 10 * log10(runningMS_all); matthiasm@0: gainDB_all = min([zeros(1,length(runningRMSdB_all));... matthiasm@0: parameter.compressorSlope * (parameter.compressorThreshold - runningRMSdB_all(:)')]); matthiasm@0: preGain_all = 10.^(gainDB_all/20); matthiasm@0: gain_all = filter(AT,[1 -(1-AT)],[1/AT,preGain_all(2:end)]); matthiasm@0: % The next line is equivalent to the behaviour of the serial version. Is that a bug? matthiasm@0: %f_audio_out = repmat(gain_all(:),1,nChannel) .* [zeros(delay+1,nChannel);f_audio(1:end-(delay+1),:)]; matthiasm@0: f_audio_out = repmat(gain_all(:),1,nChannel) .* [zeros(delay,nChannel);f_audio(1:end-delay,:)]; matthiasm@0: else matthiasm@0: % Serial version (The non-linearity 'if preGain < gain' does not seem to allow for a vectorization in all cases) matthiasm@0: %% matthiasm@0: runningMS = mono_audio(1)^2 * FT; matthiasm@0: gain = 1; matthiasm@0: buffer = zeros(delay + 1, nChannel); matthiasm@0: f_audio_out = zeros(size(f_audio)); matthiasm@0: matthiasm@0: for iSample = 2:nSample matthiasm@0: runningMS = ... matthiasm@0: runningMS * (1-FT) +... matthiasm@0: mono_audio(iSample)^2 * FT; matthiasm@0: runningRMSdB = 10 * log10(runningMS); matthiasm@0: matthiasm@0: gainDB = min([0, ... matthiasm@0: parameter.compressorSlope * (parameter.compressorThreshold - runningRMSdB)]); matthiasm@0: preGain = 10^(gainDB/20); matthiasm@0: matthiasm@0: if preGain < gain % "gain" being old gain matthiasm@0: coeff = AT; % we're in the attack phase matthiasm@0: else matthiasm@0: coeff = RT; % we're in the release phase matthiasm@0: end matthiasm@0: matthiasm@0: % calculate new gain as mix of current gain (preGain) and old gain matthiasm@0: gain = (1-coeff) * gain + coeff * preGain; matthiasm@0: matthiasm@0: f_audio_out(iSample, :) = gain * buffer(end,:); matthiasm@0: if delay > 1 matthiasm@0: buffer = [f_audio(iSample, :); buffer(1:end-1,:)]; matthiasm@0: else matthiasm@0: buffer = f_audio(iSample, :); matthiasm@0: end matthiasm@0: end matthiasm@0: end matthiasm@0: matthiasm@0: if parameter.normalizeOutputAudio matthiasm@0: f_audio_out = adthelper_normalizeAudio(f_audio_out, samplingFreq); matthiasm@0: end matthiasm@0: matthiasm@0: end matthiasm@0: matthiasm@0: % This degradation does impose a temporal distortion matthiasm@0: timepositions_afterDegr = timepositions_beforeDegr + parameter.delayTime; matthiasm@0: matthiasm@0: end matthiasm@0: matthiasm@0: matthiasm@0: matthiasm@0: