function varargout = mirautocor(orig,varargin)
%   a = mirautocor(x) computes the autocorrelation function related to x.
%   Optional parameters:
%       mirautocor(...,'Min',mi) indicates the lowest delay taken into
%           consideration. The unit can be precised:
%               mirautocor(...,'Min',mi,'s') (default unit)
%               mirautocor(...,'Min',mi,'Hz')
%           Default value: 0 s.
%       mirautocor(...,'Max',ma) indicates the highest delay taken into
%           consideration. The unit can be specified as for 'Min'.
%           Default value:
%               if x is a signal, the highest delay is 0.05 s
%                   (corresponding to a minimum frequency of 20 Hz).
%               if x is an envelope, the highest delay is 2 s.
%       mirautocor(...,'Resonance',r) multiplies the autocorrelation function
%           with a resonance curve:
%           Possible values:
%               'Toiviainen' from (Toiviainen & Snyder, 2003)
%               'vanNoorden' from (van Noorden & Moelants, 2001)
%           mirautocor(...,'Center',c) assigns the center value of the
%               resonance curve, in seconds.
%               Works mainly with 'Toiviainen' option.
%               Default value: c = 0.5
%       mirautocor(...,'Enhanced',a) reduces the effect of subharmonics.
%           The original autocorrelation function is half-wave rectified,
%           time-scaled by the factor a (which can be a factor list as
%           well), and substracted from the original clipped function.
%           (Tolonen & Karjalainen)
%               If the 'Enhanced' option is not followed by any value, 
%                   default value is a = 2:10
%       mirautocor(...,'Halfwave') performs a half-wave rectification on the
%           result.
%       mirautocor(...,'Freq') represents the autocorrelation function in the
%           frequency domain.
%       mirautocor(...,'NormalWindow',w): applies a window to the input 
%           signal and divides the autocorrelation by the autocorrelation of  
%           that window (Boersma 1993).
%           Possible values: any windowing function proposed in the Signal
%               Processing Toolbox (help window) plus 'rectangle' (no
%               windowing)
%           Default value:  w = 'hanning'
%           mirautocor(...,'NormalWindow',0): toggles off this normalization
%               (which is on by default).
%   All the parameters described previously can be applied to an
%       autocorrelation function itself, in order to arrange the results
%       after the actual computation of the autocorrelation computations.
%       For instance: a = mirautocor(a,'Resonance','Enhanced')
%   Other optional parameter:
%       mirautocor(...,'Compres',k) computes the autocorrelation in the
%           frequency domain and includes a magnitude compression of the
%           spectral representation. A normal autocorrelation corresponds
%           to the value k=2, but values lower than 2 are suggested by
%           (Tolonen & Karjalainen, 2000).
%           Default value: k = 0.67
%       mirautocor(...,'Normal',n) or simply mirautocor(...,n) specifies
%           the normalization strategy. Accepted values are 'biased',
%           'unbiased', 'coeff' (default  value) and 'none'.
%           See help xcorr for an explanation. 

        min.key = 'Min';
        min.type = 'Integer';
        min.unit = {'s','Hz'};
        if isamir(orig,'mirspectrum')
            min.defaultunit = 'Hz';
        else
            min.defaultunit = 's';
        end
        min.default = 0;
        min.opposite = 'max';
    option.min = min;
        
        max.key = 'Max';
        max.type = 'Integer';
        max.unit = {'s','Hz'};
        if isamir(orig,'mirspectrum')
            max.defaultunit = 'Hz';
        else
            max.defaultunit = 's';
        end
        if isamir(orig,'mirenvelope') || isamir(orig,'mirdiffenvelope')
            max.default = 2;             % for envelopes, longest period: 2 seconds.
        elseif isamir(orig,'miraudio')  || ischar(orig)  % for audio signal,lowest frequency: 20 Hz.
            max.default = 1/20; 
        else
            max.default = Inf;
        end
        max.opposite = 'min';
    option.max = max;
        
        scaleoptbw.key = 'Normal'; %'Normal' keyword optional
        scaleoptbw.key = 'Boolean';
    option.scaleoptbw = scaleoptbw;
        scaleopt.type = 'String';
        scaleopt.choice = {'biased','unbiased','coeff','none'};
        scaleopt.default = 'coeff';
    option.scaleopt = scaleopt;
            
        gener.key = {'Generalized','Compres'};
        gener.type = 'Integer';
        gener.default = 2;
        gener.keydefault = .67;
    option.gener = gener;
        
        ni.key = 'NormalInput';  %% Normalize before frame or chunk??
        ni.type = 'Boolean';
        ni.default = 0;
    option.ni = ni;
    
        reso.key = 'Resonance';
        reso.type = 'String';
        reso.choice = {'ToiviainenSnyder','Toiviainen','vanNoorden','no','off',0};
        reso.keydefault = 'Toiviainen';
        reso.when = 'After';
        reso.default = 0;
    option.reso = reso;
        
        resocenter.key = {'Center','Centre'};
        resocenter.type = 'Integer';
        resocenter.when = 'After';
    option.resocenter = resocenter;

        h.key = 'Halfwave';
        h.type = 'Boolean';
        h.when = 'After';
        h.default = 0;
    option.h = h;
        
        e.key = 'Enhanced';
        e.type = 'Integers';
        e.default = [];
        e.keydefault = 2:10;
        e.when = 'After';
    option.e = e;
        
        fr.key = 'Freq';
        fr.type = 'Boolean';
        fr.default = 0;
        fr.when = 'After';
    option.fr = fr;
        
        nw.key = 'NormalWindow';
        nw.when = 'Both';
        if isamir(orig,'mirspectrum')
            nw.default = 0;
        elseif isamir(orig,'mirenvelope')
            nw.default = 'rectangular';
        else
            nw.default = 'hanning';
        end
    option.nw = nw;
    
        win.key = 'Window';
        win.type = 'String';
        win.default = NaN;
    option.win = win;
    
specif.option = option;

specif.defaultframelength = 0.05;
specif.defaultframehop = 0.5;
specif.eachchunk = @eachchunk;
specif.combinechunk = @combinechunk;

if isamir(orig,'mirscalar') || isamir(orig,'mirenvelope')
    specif.nochunk = 1;
end

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


function [x type] = init(x,option)
type = 'mirautocor';


function a = main(orig,option,postoption)
if iscell(orig)
    orig = orig{1};
end
if isa(orig,'mirautocor')
    a = orig;
    if not(isempty(option)) && ...
            (option.min || iscell(option.max) || option.max < Inf)
        coeff = get(a,'Coeff');
        delay = get(a,'Delay');
        for h = 1:length(coeff)
            if a.freq
                mi = 1/option.max;
                ma = 1/option.min;
            else
                mi = option.min;
                ma = option.max;
            end
            for k = 1:length(coeff{h})
                range = find(and(delay{h}{k}(:,1,1) >= mi,...
                                 delay{h}{k}(:,1,1) <= ma));
                coeff{h}{k} = coeff{h}{k}(range,:,:);
                delay{h}{k} = delay{h}{k}(range,:,:);
            end
        end
        a = set(a,'Coeff',coeff,'Delay',delay);
    end
    if not(isempty(postoption)) && not(isequal(postoption,0))
        a = post(a,postoption);
    end
elseif ischar(orig)
    a = mirautocor(miraudio(orig),option,postoption);
else
    if nargin == 0
        orig = [];
    end
    a.freq = 0;
    a.ofspectrum = 0;
    a.window = {};
    a.normalwindow = 0;
    a = class(a,'mirautocor',mirdata(orig));
    a = purgedata(a);
    a = set(a,'Ord','coefficients');
    sig = get(orig,'Data');
    if isa(orig,'mirspectrum')
        a = set(a,'Title','Spectrum autocorrelation','OfSpectrum',1,...
                  'Abs','frequency (Hz)');
        pos = get(orig,'Pos');
    else
        if isa(orig,'mirscalar')
            a = set(a,'Title',[get(orig,'Title') ' autocorrelation']);
            pos = get(orig,'FramePos');
            for k = 1:length(sig)
                for l = 1:length(sig{k})
                    sig{k}{l} = sig{k}{l}';
                    pos{k}{l} = pos{k}{l}(1,:,:)';
                end
            end
        else
            if isa(orig,'mirenvelope')
                a = set(a,'Title','Envelope autocorrelation');
            elseif not(isa(orig,'mirautocor'))
                a = set(a,'Title','Waveform autocorrelation');
            end
            pos = get(orig,'Pos');
        end
        a = set(a,'Abs','lag (s)');
    end
    f = get(orig,'Sampling');
    
    if isnan(option.win) 
        if isequal(option.nw,0) || ...
                strcmpi(option.nw,'Off') || strcmpi(option.nw,'No')
            option.win = 0;
        elseif isequal(option.nw,1) || strcmpi(option.nw,'On') || ...
                                     strcmpi(option.nw,'Yes')
            option.win = 'hanning';
        else
            option.win = postoption.nw;
        end
    end

    coeff = cell(1,length(sig));
    lags = cell(1,length(sig));
    wind = cell(1,length(sig));
    for k = 1:length(sig)
        s = sig{k};
        p = pos{k};
        fk = f{k};
        if iscell(option.max)
            mi = option.min{k};
            ma = option.max{k};
        else
            mi = option.min;
            ma = option.max;
        end
        coeffk = cell(1,length(s));
        lagsk = cell(1,length(s));
        windk = cell(1,length(s));
        for l = 1:length(s)
            sl = s{l};
            sl(isnan(sl)) = 0;
            if option.ni
                mxsl = repmat(max(sl),[size(sl,1),1,1]);
                mnsl = repmat(min(sl),[size(sl,1),1,1]);
                sl = (sl-mnsl)./(mxsl-mnsl);
            end
            pl = p{l};
            pl = pl-repmat(pl(1,:,:),[size(pl,1),1,1]);
            ls = size(sl,1);
            if mi
                misp = find(pl(:,1,1)>=mi);
                if isempty(misp)
                    warning('WARNING IN MIRAUTOCOR: The specified range of delays exceeds the temporal length of the signal.');
                    disp('Minimum delay set to zero.')
                    misp = 1;  % misp is the lowest index of the lag range
                    mi = 0;
                else
                    misp = misp(1);
                end
            else
                misp = 1;
            end
            if ma
                masp = find(pl(:,1,1)>=ma);
                if isempty(masp)
                    masp = Inf;
                else
                    masp = masp(1);
                end
            else
                masp = Inf;
            end
            masp = min(masp,ceil(ls/2));
            if masp <= misp
                if size(sl,2) > 1
                    warning('WARNING IN MIRAUTOCOR: Frame length is too small.');    
                else
                    warning('WARNING IN MIRAUTOCOR: The audio sequence is too small.');    
                end
                display('The autocorrelation is not defined for this range of delays.');
            end
            sl = center(sl);
            if not(ischar(option.win)) || strcmpi(option.win,'Rectangular')
                kw = ones(size(sl));
            else
                N = size(sl,1);
                winf = str2func(option.win);
                try
                    w = window(winf,N);
                catch
                    if strcmpi(option.win,'hamming')
                        disp('Signal Processing Toolbox does not seem to be installed. Recompute the hamming window manually.');
                        w = 0.54 - 0.46 * cos(2*pi*(0:N-1)'/(N-1));
                    else
                        warning(['WARNING in MIRAUTOCOR: Unknown windowing function ',option.win,' (maybe Signal Processing Toolbox is not installed).']);
                        disp('No windowing performed.')
                        w = ones(size(sl,1),1);
                    end
                end
                kw = repmat(w,[1,size(sl,2),size(sl,3)]);
                sl = sl.* kw;
            end

            if strcmpi(option.scaleopt,'coeff')
                scaleopt = 'none';
            else
                scaleopt = option.scaleopt;
            end
            c = zeros(masp,size(sl,2),size(sl,3));
            for i = 1:size(sl,2)
                for j = 1:size(sl,3)
                    if option.gener == 2
                        cc = xcorr(sl(:,i,j),masp-1,scaleopt);
                        c(:,i,j) = cc(masp:end);
                    else
                        ss = abs(fft(sl(:,i,j)));
                        ss = ss.^option.gener;
                        cc = ifft(ss);
                        ll = (0:masp-1);
                        c(:,i,j) = cc(ll+1);
                    end
                end
                if strcmpi(option.scaleopt,'coeff') && option.gener == 2
                    % to be adapted to generalized autocor
                    c(:,i,:) = c(:,i,:)/xcorr(sum(sl(:,i,:),3),0);
                    % This is a kind of generalization of the 'coeff'
                    % normalization for multi-channels signals. In the
                    % original 'coeff' option, the autocorrelation at zero
                    % lag is identically 1.0. In this multi-channels
                    % version, the autocorrelation at zero lag is such that
                    % the sum over channels becomes identically 1.0. 
                end
            end
            coeffk{l} = c(misp:end,:,:);
            pl = pl(find(pl(:,1,1) >=mi),:,:);
            lagsk{l} = pl(1:min(size(coeffk{l},1),size(pl,1)),:,:);
            windk{l} = kw;
        end
        coeff{k} = coeffk;
        lags{k} = lagsk;
        wind{k} = windk;
    end
    a = set(a,'Coeff',coeff,'Delay',lags,'Window',wind);
    if not(isempty(postoption))
        a = post(a,postoption);
    end
end


function a = post(a,option)
debug = 0;
coeff = get(a,'Coeff');
lags = get(a,'Delay');
wind = get(a,'Window');
freq = option.fr && not(get(a,'FreqDomain'));
if isequal(option.e,1)
    option.e = 2:10;
end
if max(option.e) > 1
    pa = mirpeaks(a,'NoBegin','NoEnd','Contrast',.01);
    va = mirpeaks(a,'Valleys','Contrast',.01);
    pv = get(pa,'PeakVal');
    vv = get(va,'PeakVal');
end
for k = 1:length(coeff)
    for l = 1:length(coeff{k})
        c = coeff{k}{l};  % Coefficients of autocorrelation
        t = lags{k}{l};   % Delays of autocorrelation
        if not(isempty(c))
            if not(isequal(option.nw,0) || strcmpi(option.nw,'No') || ...
                   strcmpi(option.nw,'Off') || a.normalwindow) % 'NormalWindow' option
                xw = zeros(size(c));
                lc = size(c,1);
                for j = 1:size(c,3)
                    for i = 1:size(c,2)
                        xwij = xcorr(wind{k}{l}(:,i,j),lc,'coeff');
                        xw(:,i,j) = xwij(lc+2:end);
                    end
                end
                c = c./ xw;
                a.normalwindow = 1;
            end
            if ischar(option.reso) && ...
                    (strcmpi(option.reso,'ToiviainenSnyder') || ...
                    strcmpi(option.reso,'Toiviainen') || ...
                    strcmpi(option.reso,'vanNoorden'))
                if isa(a,'mirautocor') && get(a,'FreqDomain')
                    ll = 1./t;
                else
                    ll = t;
                end
                if not(option.resocenter)
                    option.resocenter = .5;
                end
                if strcmpi(option.reso,'ToiviainenSnyder') || ...
                    strcmpi(option.reso,'Toiviainen')
                    w = max(1 - 0.25*(log2(max(ll,1e-12)/option.resocenter)).^2, 0);
                elseif strcmpi(option.reso,'vanNoorden')
                    f0=2.193; b=option.resocenter; 
                    f=1./ll; a1=(f0*f0-f.*f).^2+b*f.^2; a2=f0^4+f.^4;
                    w=(1./sqrt(a1))-(1./sqrt(a2));
                end
                if max(w) == 0
                    warning('The resonance curve, not defined for this range of delays, will not be applied.')
                else
                    w = w/max(w);
                    c = c.* repmat(w,[1,size(c,2),size(c,3)]);
                end
            end
            if option.h
                c = hwr(c);
            end
            if max(option.e) > 1
                if a.freq
                    freq = 1;
                    for i = 1:size(c,3)
                        c(:,:,i) = flipud(c(:,:,i));
                    end
                    t = flipud(1./t);
                end
                
                for g = 1:size(c,2)
                    for h = 1:size(c,3)
                        cgh = c(:,g,h);
                        if length(cgh)>1
                            pvk = pv{k}{l}{1,g,h};
                            mv = [];
                            if not(isempty(pvk))
                                mp = min(pv{k}{l}{1,g,h}); %Lowest peak
                                vvv = vv{k}{l}{1,g,h}; %Valleys
                                mv = vvv(find(vvv<mp,1,'last'));
                                    %Highest valley below the lowest peak

                                if not(isempty(mv))
                                    cgh = cgh-mv;
                                end
                            end
                            cgh2 = cgh;
                            tgh2 = t(:,g,1);
                            coef = cgh(2)-cgh(1); % initial slope of the autocor curve
                            tcoef = tgh2(2)-tgh2(1);
                            deter = 0;
                            inter = 0;

                            repet = find(not(diff(tgh2)));  % Avoid bug if repeated x-values
                            if repet
                                warning('WARNING in MIRAUTOCOR: Two successive samples have exactly same temporal position.');
                                tgh2(repet+1) = tgh2(repet)+1e-12;
                            end

                            if coef < 0
                                % initial descending slope removed
                                deter = find(diff(cgh2)>0,1)-1;
                                    % number of removed points
                                if isempty(deter)
                                    deter = 0;
                                end
                                cgh2(1:deter) = [];
                                tgh2(1:deter) = [];
                                coef = cgh2(2)-cgh2(1);
                            end

                            if coef > 0
                                % initial ascending slope prolonged to the left
                                % until it reaches the x-axis
                                while cgh2(1) > 0
                                    coef = coef*1.1;
                                        % the further to the left, ...
                                        % the more ascending is the slope
                                        % (not sure it always works, though...)
                                    inter = inter+1;
                                        % number of added points
                                    cgh2 = [cgh2(1)-coef; cgh2];
                                    tgh2 = [tgh2(1)-tcoef; tgh2];
                                end
                                cgh2(1) = 0;
                            end

                            for i = option.e  % Enhancing procedure
                                % option.e is the list of scaling factors
                                % i is the scaling factor
                                if i
                                    be = find(tgh2 & tgh2/i >= tgh2(1),1);
                                        % starting point of the substraction
                                        % on the X-axis

                                    if not(isempty(be))
                                        ic = interp1(tgh2,cgh2,tgh2/i);
                                            % The scaled autocorrelation
                                        ic(1:be-1) = 0;
                                        ic(find(isnan(ic))) = Inf;
                                            % All the NaN values are changed
                                            % into 0 in the resulting curve
                                        ic = max(ic,0);

                                        if debug
                                           hold off,plot(tgh2,cgh2)
                                        end

                                        cgh2 = cgh2 - ic;       
                                            % The scaled autocorrelation
                                            % is substracted to the initial one

                                        cgh2 = max(cgh2,0);
                                            % Half-wave rectification

                                        if debug
                                           hold on,plot(tgh2,ic,'r')
                                           hold on,plot(tgh2,cgh2,'g')
                                           drawnow
                                           figure
                                        end
                                    end
                                end
                            end

                            % The  temporary modifications are
                            % removed from the final curve
                            if inter>=deter
                                c(:,g,h) = cgh2(inter-deter+1:end);
                                if not(isempty(mv))
                                    c(:,g,h) = c(:,g,h) + mv;
                                end
                            else
                                c(:,g,h) = [zeros(deter-inter,1);cgh2];
                            end
                        end
                    end
                end
            end
            if freq
                if t(1,1) == 0
                    c = c(2:end,:,:);
                    t = t(2:end,:,:);
                end
                for i = 1:size(c,3)
                    c(:,:,i) = flipud(c(:,:,i));
                end
                t = flipud(1./t);
            end
            coeff{k}{l} = c;
            lags{k}{l} = t;
        end
    end
end
a = set(a,'Coeff',coeff,'Delay',lags,'Freq');
if freq
    a = set(a,'FreqDomain',1,'Abs','frequency (Hz)');
end


function [y orig] = eachchunk(orig,option,missing,postchunk)
option.scaleopt = 'none';
y = mirautocor(orig,option);


function y = combinechunk(old,new)
do = get(old,'Data');
do = do{1}{1};
dn = get(new,'Data');
dn = dn{1}{1};
if abs(size(dn,1)-size(do,1)) <= 2 % Probleme of border fluctuation
    mi = min(size(dn,1),size(do,1));
    dn = dn(1:mi,:,:);
    do = do(1:mi,:,:);
elseif length(dn) < length(do)
    dn(length(do),:,:) = 0; % Zero-padding
end
y = set(old,'ChunkData',do+dn);
