function varargout = mirpulseclarity(orig,varargin)
%   r = mirpulseclarity(x) estimates the rhythmic clarity, indicating the
%       strength of the beats estimated by the mirtempo function.
%   Optional arguments:
%       mirpulseclarity(...,s): specifies a strategy for pulse clarity
%           estimation.
%           Possible values: 'MaxAutocor' (default), 'MinAutocor',
%               'KurtosisAutocor', MeanPeaksAutocor', 'EntropyAutocor', 
%               'InterfAutocor', 'TempoAutocor', 'ExtremEnvelop', 
%               'Attack', 'Articulation'
%       mirpulseclarity(...,'Frame',l,h): orders a frame decomposition of
%           the audio input of window length l (in seconds) and hop factor
%           h, expressed relatively to the window length.
%           Default values: l = 5 seconds and h = .1
%       Onset detection strategies: 'Envelope' (default), 'DiffEnvelope', 
%           'SpectralFlux', 'Pitch'.
%       Options related to the autocorrelation computation can be specified
%           as well: 'Min', 'Max', 'Resonance', 'Enhanced'
%       Options related to the tempo estimation can be specified here
%           as well: 'Sum', 'Total', 'Contrast'.
%       cf. User's Manual for more details.
%   [r,a] = mirpulseclarity(x) also returns the beat autocorrelation.
        
        model.key = 'Model';
        model.type = 'Integer';
        model.default = 0;
    option.model = model;

        stratg.type = 'String';
        stratg.choice = {'MaxAutocor','MinAutocor','MeanPeaksAutocor',...
                         'KurtosisAutocor','EntropyAutocor',...
                         'InterfAutocor','TempoAutocor','ExtremEnvelop',...
                         'Attack','Articulation'};    ...,'AttackDiff'
        stratg.default = 'MaxAutocor';
    option.stratg = stratg;

        frame.key = 'Frame';
        frame.type = 'Integer';
        frame.number = 2;
        frame.keydefault = [5 .1];
        frame.default = [0 0];
    option.frame = frame;
        
%% options related to mironsets:  

        fea.type = 'String';
        fea.choice = {'Envelope','DiffEnvelope','SpectralFlux','Pitch'};
        fea.default = 'Envelope';
    option.fea = fea;
    
    
    %% options related to 'Envelope':
    
            envmeth.key = 'Method';
            envmeth.type = 'String';
            envmeth.choice = {'Filter','Spectro'};
            envmeth.default = 'Spectro';
        option.envmeth = envmeth;

            %% options related to 'Filter':

                ftype.key = 'FilterType';
                ftype.type = 'String';
                ftype.choice = {'IIR','HalfHann'};
                ftype.default = 'IIR';
            option.ftype = ftype;

                fb.key = 'Filterbank';
                fb.type = 'Integer';
                fb.default = 20;
            option.fb = fb;

                fbtype.key = 'FilterbankType';
                fbtype.type = 'String';
                fbtype.choice = {'Gammatone','Scheirer','Klapuri'};
                fbtype.default = 'Scheirer';
            option.fbtype = fbtype;

            %% options related to 'Spectro':

                band.type = 'String';
                band.choice = {'Freq','Mel','Bark','Cents'};
                band.default = 'Freq';
            option.band = band;


            diffhwr.key = 'HalfwaveDiff';
            diffhwr.type = 'Integer';
            diffhwr.default = 0;
            diffhwr.keydefault = 1;
        option.diffhwr = diffhwr;

            lambda.key = 'Lambda';
            lambda.type = 'Integer';
            lambda.default = 1;
        option.lambda = lambda;

            aver.key = 'Smooth';
            aver.type = 'Integer';
            aver.default = 0;
            aver.keydefault = 30;
        option.aver = aver;

            oplog.key = 'Log';
            oplog.type = 'Boolean';
            oplog.default = 0;
        option.log = oplog;
        
            mu.key = 'Mu';
            mu.type = 'Boolean';
            mu.default = 1;
        option.mu = mu;

    %% options related to 'SpectralFlux'
    
            inc.key = 'Inc';
            inc.type = 'Boolean';
            inc.default = 1;
        option.inc = inc;

            median.key = 'Median';
            median.type = 'Integer';
            median.number = 2;
            median.default = [0 0]; % Not same default as in mirtempo
        option.median = median;

            hw.key = 'Halfwave';
            hw.type = 'Boolean';
            hw.default = 0; %NaN; %0; % Not same default as in mirtempo
        option.hw = hw;    
        
    
%% options related to mirattackslope
        slope.type = 'String';
        slope.choice = {'Diff','Gauss'};
        slope.default = 'Diff';
    option.slope = slope;
    
%% options related to mirautocor:    
    
        enh.key = 'Enhanced';
        enh.type = 'Integers';
        enh.default = [];
        enh.keydefault = 2:10;
    option.enh = enh;
    
        r.key = 'Resonance';
        r.type = 'String';
        r.choice = {'ToiviainenSnyder','vonNoorden',0,'off','no'};
        r.default = 'ToiviainenSnyder';
    option.r = r;
        
        mi.key = 'Min';
        mi.type = 'Integer';
        mi.default = 40;
    option.mi = mi;
        
        ma.key = 'Max';
        ma.type = 'Integer';
        ma.default = 200;
    option.ma = ma;    

%% options related to mirtempo:

        sum.key = 'Sum';
        sum.type = 'String';
        sum.choice = {'Before','After','Adjacent'};
        sum.default = 'Before';
    option.sum = sum;
    
        m.key = 'Total';
        m.type = 'Integer';
        m.default = 1;
    option.m = m;
        
        thr.key = 'Contrast';
        thr.type = 'Integer';
        thr.default = 0.01; % Not same default as in mirtempo
    option.thr = thr;

specif.option = option;

varargout = mirfunction(@mirpulseclarity,orig,varargin,nargout,specif,@init,@main);



%% Initialisation

function [x type] = init(x,option)
%if isframed(x)
%    warning('WARNING IN MIRPULSECLARITY: The input should not be already decomposed into frames.');
%    disp(['Suggestion: Use the ''Frame'' option instead.'])
%end
if iscell(x)
    x = x{1};
end
if isamir(x,'mirautocor')
    type = {'mirscalar','mirautocor'};
elseif length(option.model) > 1
    a = x;
    type = {'mirscalar'};
    for m = 1:length(option.model)
        if option.frame.length.val
            y = mirpulseclarity(a,'Model',option.model(m),...
                                  'Frame',option.frame.length.val,...
                                          option.frame.length.unit,...
                                          option.frame.hop.val,...
                                          option.frame.hop.unit);
        else
            y = mirpulseclarity(a,'Model',option.model(m));
        end
        if m == 1
            x = y;
        else
            x = x + y;
        end
    end
else
    if option.model
        switch option.model
            case 1
            case 2
                option.envmeth = 'Filter';
                option.fbtype = 'Gammatone';
                option.mu = 0;
                option.r = 0;
                option.lambda = .8;
                option.sum = 'After';
        end
    end
    if length(option.stratg)>7 && strcmpi(option.stratg(end-6:end),'Autocor')
        if (strcmpi(option.stratg,'MaxAutocor') || ...
            strcmpi(option.stratg,'MinAutocor') || ...
            strcmpi(option.stratg,'EntropyAutocor'))
            option.m = 0;
        end
        if strcmpi(option.stratg,'MinAutocor')
            option.enh = 0;
        end
        if option.frame.length.val
            [t,x] = mirtempo(x,option.fea,'Method',option.envmeth,...
                       option.band,...
                       'Sum',option.sum,'Enhanced',option.enh,...
                       'Resonance',option.r,'Smooth',option.aver,...
                       'HalfwaveDiff',option.diffhwr,...
                       'Lambda',option.lambda,...
                       'Frame',option.frame.length.val,...
                               option.frame.length.unit,...
                               option.frame.hop.val,...
                               option.frame.hop.unit,...
                       'FilterbankType',option.fbtype,...
                       'FilterType',option.ftype,...
                       'Filterbank',option.fb,'Mu',option.mu,...
                       'Log',option.log,...
                       'Inc',option.inc,'Halfwave',option.hw,...
                       'Median',option.median(1),option.median(2),...
                       'Min',option.mi,'Max',option.ma,...
                       'Total',option.m,'Contrast',option.thr);
        else
            [t,x] = mirtempo(x,option.fea,'Method',option.envmeth,...
                       option.band,...
                       'Sum',option.sum,'Enhanced',option.enh,...
                       'Resonance',option.r,'Smooth',option.aver,...
                       'HalfwaveDiff',option.diffhwr,...
                       'Lambda',option.lambda,...
                       'FilterbankType',option.fbtype,...
                       'FilterType',option.ftype,...
                       'Filterbank',option.fb,'Mu',option.mu,...
                       'Log',option.log,...
                       'Inc',option.inc,'Halfwave',option.hw,...
                       'Median',option.median(1),option.median(2),...
                       'Min',option.mi,'Max',option.ma,...
                       'Total',option.m,'Contrast',option.thr);
        end
        type = {'mirscalar','mirautocor'};
    elseif strcmpi(option.stratg,'ExtremEnvelop')
        x = mironsets(x,'Filterbank',option.fb);
        type = {'mirscalar','mirenvelope'};
    elseif strcmpi(option.stratg,'Attack')
        x = mirattackslope(x,option.slope);
        type = {'mirscalar','mirenvelope'};
%    elseif strcmpi(option.stratg,'AttackDiff')
%        type = {'mirscalar','mirenvelope'};
    elseif strcmpi(option.stratg,'Articulation')
        x = mirlowenergy(x,'ASR');
        type = {'mirscalar','mirscalar'};
    else
        type = {'mirscalar','miraudio'};
    end
end



%% Main function

function o = main(a,option,postoption)
if option.model == 2
    option.stratg = 'InterfAutocor';
end
if isa(a,'mirscalar') && not(strcmpi(option.stratg,'Attack')) % not very nice test... to improve.
    o = {a};
    return
end
if option.m == 1 && ...
        (strcmpi(option.stratg,'InterfAutocor') || ...
         strcmpi(option.stratg,'MeanPeaksAutocor'))
    option.m = Inf;
end
if iscell(a)
    a = a{1};
end
if strcmpi(option.stratg,'MaxAutocor')
    d = get(a,'Data');
    rc = mircompute(@max,d);
elseif strcmpi(option.stratg,'MinAutocor')
    d = get(a,'Data');
    rc = mircompute(@minusmin,d);
elseif strcmpi(option.stratg,'MeanPeaksAutocor')
    m = get(a,'PeakVal');
    rc = mircompute(@meanpeaks,m);    
elseif strcmpi(option.stratg,'KurtosisAutocor')
    a = mirpeaks(a,'Extract','Total',option.m,'NoBegin','NoEnd');
    k = mirkurtosis(a);
    %d = get(k,'Data');
    %rc = mircompute(@meanpeaks,d);
    rc = mirmean(k);
elseif strcmpi(option.stratg,'EntropyAutocor')
    rc = mirentropy(a);
elseif strcmpi(option.stratg,'InterfAutocor')
    a = mirpeaks(a,'Total',option.m,'NoBegin','NoEnd');
    m = get(a,'PeakVal');
    p = get(a,'PeakPosUnit');
    rc = mircompute(@interf,m,p);
elseif strcmpi(option.stratg,'TempoAutocor')
    a = mirpeaks(a,'Total',1,'NoBegin','NoEnd');
    p = get(a,'PeakPosUnit');
    rc = mircompute(@tempo,p);
elseif strcmpi(option.stratg,'ExtremEnvelop')
    a = mirenvelope(a,'Normal');
    p = mirpeaks(a,'Order','Abscissa');
    p = get(p,'PeakPreciseVal');
    n = mirpeaks(a,'Valleys','Order','Abscissa');
    n = get(n,'PeakPreciseVal');
    rc = mircompute(@shape,p,n);
elseif strcmpi(option.stratg,'Attack')
    rc = mirmean(a);
%elseif strcmpi(option.stratg,'AttackDiff')
%    a = mirpeaks(a);
%    m = get(a,'PeakVal');
%    rc = mircompute(@meanpeaks,m);    
elseif strcmpi(option.stratg,'Articulation')
    rc = a;
end

if iscell(rc)
    pc = mirscalar(a,'Data',rc,'Title','Pulse clarity');
else
    pc = set(rc,'Title',['Pulse clarity (',get(rc,'Title'),')']);
end

if option.model
    switch option.model
        case 1
            alpha = 0;
            beta = 2.2015;
            lambda = .1;
        case 2
            alpha = 0;
            beta = 3.5982;
            lambda = 1.87;
    end
    if not(lambda == 0)
        pc = (pc+alpha)^lambda * beta;
    else
        pc = log(pc+alpha) * beta;
    end
    title = ['Pulse clarity (Model ',num2str(option.model),')'];
    pc = set(pc,'Title',title);
end

o = {pc a};


%% Routines

function r  = shape(p,n)
p = p{1};
n = n{1};
if length(p)>length(n)
    d = sum(p(1:end-1) - n) + sum(p(2:end) - n);
    r  = d/(2*length(n));
elseif length(p)<length(n)
    d = sum(p - n(1:end-1)) + sum(p - n(2:end));
    r  = d/(2*length(p));
else
    d = sum(p(2:end) - n(1:end-1)) + sum(p(1:end-1) - n(2:end));
    r  = d/(2*(length(p)-1));
end


function rc = minusmin(ac)
rc = -min(ac);


function rc = meanpeaks(ac)
rc = zeros(1,length(ac));
for j = 1:length(ac)
    if isempty(ac{j})
        rc(j) = NaN;
    else
        rc(j) = mean(ac{j});
    end
end


function rc = interf(mk,pk)
rc = zeros(size(mk));
for j = 1:size(mk,3)
    for i = 1:size(mk,2)
        pij = pk{1,i,j};
        mij = mk{1,i,j};
        if isempty(pij)
            rc(1,i,j) = 0;
        else
            high = max(pij(2:end),pij(1));
            low = min(pij(2:end),pij(1));
            quo = rem(high,low)./low;
            nomult = quo>.15 & quo<.85;
            fij = mij(2:end)/mij(1) .*nomult;
            fij(fij<0) = 0;
            rc(1,i,j) = exp(-sum(fij)/4); % Pulsations that are not in integer ratio
                                          % with dominant pulse decrease clarity
        end
    end
end


function rc = tempo(pk)
rc = zeros(size(pk));
for j = 1:size(pk,3)
    for i = 1:size(pk,2)
        pij = pk{1,i,j};
        if isempty(pij)
            rc(1,i,j) = 0;
        else
            rc(1,i,j) = exp(-pij(1)/4)/exp(-.33/4); % Fast dominant pulse
                                                    % increases clarity
        end
    end
end