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);