function varargout = mirpeaks(orig,varargin)
% p = mirpeaks(x) detect peaks in x.
% Optional argument:
% mirpeaks(...,'Total',m): only the m highest peaks are selected.
% If m=Inf, no limitation of number of peaks.
% Default value: m = Inf
% mirpeaks(...,'Order',o): specifies the ordering of the peaks.
% Possible values for o:
% 'Amplitude': orders the peaks from highest to lowest
% (Default choice.)
% 'Abscissa': orders the peaks along the abscissa axis.
% mirpeaks(...,'Contrast',cthr): a threshold value. A given local
% maximum will be considered as a peak if the difference of
% amplitude with respect to both the previous and successive
% local minima (when they exist) is higher than the threshold
% cthr. This distance is expressed with respect to the
% total amplitude of x: a distance of 1, for instance, is
% equivalent to the distance between the maximum and the minimum
% of x.
% Default value: cthr = 0.1
% mirpeaks(...,'SelectFirst',fthr): If the 'Contrast' selection has
% been chosen, this additional option specifies that when one
% peak has to be chosen out of two candidates, and if the
% difference of their amplitude is below the threshold fthr,
% then the most ancien one is selected.
% Option toggled off by default:
% Default value if toggled on: fthr = cthr/2
% mirpeaks(...,'Threshold',thr): a threshold value.
% A given local maximum will be considered as a peak if its
% normalized amplitude is higher than this threshold.
% A given local minimum will be considered as a valley if its
% normalized amplitude is lower than this threshold.
% The normalized amplitude can have value between 0 (the minimum
% of the signal in each frame) and 1 (the maximum in each
% frame).
% Default value: thr=0 for peaks thr = 1 for valleys
% mirpeaks(...,'Interpol',i): estimates more precisely the peak
% position and amplitude using interpolation. Performed only on
% data with numerical abscissae axis.
% Possible value for i:
% '', 'no', 'off', 0: no interpolation
% 'Quadratic': quadratic interpolation. (default value).
% mirpeaks(...,'Valleys'): detect valleys instead of peaks.
% mirpeaks(...,'Reso',r): removes peaks whose abscissa distance to
% one or several higher peaks is lower than a given threshold.
% Possible value for the threshold r:
% 'SemiTone': ratio between the two peak positions equal to
% 2^(1/12)
% a numerical value : difference between the two peak
% positions equal to that value
% When two peaks are distant by an interval lower than the
% resolution, the highest of them is selected by default.
% mirpeaks(...,'Reso',r,'First') specifies on the contrary that
% the first of them is selected by default.
% When a peak p1 is too close to another higher peak p2, p1 is
% removed even if p2 is removed as well. If you want to
% filter out p1 only if p2 remains in the end, add the option
% 'Loose'.
% mirpeaks(...,'Reso',r,'Loose') specifies instead that
% mirpeaks(...,'Nearest',t,s): takes the peak nearest a given abscisse
% values t. The distance is computed either on a linear scale
% (s = 'Lin') or logarithmic scale (s = 'Log'). In this case,
% only one peak is extracted.
% mirpeaks(...,'Pref',c,std): indicates a region of preference for
% the peak picking, centered on the abscisse value c, with a
% standard deviation of std.
% mirpeaks(...,'NoBegin'): does not consider the first sample as a
% possible peak candidate.
% mirpeaks(...,'NoEnd'): does not consider the last sample as a possible
% peak candidate.
% mirpeaks(...,'Normalize',n): specifies whether frames are
% normalized globally or individually.
% Possible value for n:
% 'Global': normalizes the whole frames altogether from 0 to
% 1 (default choice).
% 'Local': normalizes each frame from 0 to 1.
% mirpeaks(...,'Extract'): extracts from the initial curves all the
% positive continuous segments (or "curve portions") where peaks
% are located.
% mirpeaks(...,'Only'): keeps from the original curve only the data
% corresponding to the peaks, and zeroes the remaining data.
% mirpeaks(...,'Track',t): tracks temporal continuities of peaks. If
% a value t is specified, the variation between successive peaks
% is tolerated up to t samples.
% mirpeaks(...,'CollapseTrack',ct): collapses tracks into one single
% track, and remove small track transitions, of length shorter
% than ct samples. Default value: ct = 7
m.key = 'Total';
m.type = 'Integer';
m.default = Inf;
option.m = m;
nobegin.key = 'NoBegin';
nobegin.type = 'Boolean';
nobegin.default = 0;
option.nobegin = nobegin;
noend.key = 'NoEnd';
noend.type = 'Boolean';
noend.default = 0;
option.noend = noend;
order.key = 'Order';
order.type = 'String';
order.choice = {'Amplitude','Abscissa'};
order.default = 'Amplitude';
option.order = order;
chro.key = 'Chrono'; % obsolete, corresponds to 'Order','Abscissa'
chro.type = 'Boolean';
chro.default = 0;
option.chro = chro;
ranked.key = 'Ranked'; % obsolete, corresponds to 'Order','Amplitude'
ranked.type = 'Boolean';
ranked.default = 0;
option.ranked = ranked;
vall.key = 'Valleys';
vall.type = 'Boolean';
vall.default = 0;
option.vall = vall;
cthr.key = 'Contrast';
cthr.type = 'Integer';
cthr.default = .1;
option.cthr = cthr;
first.key = 'SelectFirst';
first.type = 'Integer';
first.default = 0;
first.keydefault = NaN;
option.first = first;
thr.key = 'Threshold';
thr.type = 'Integer';
thr.default = NaN;
option.thr = thr;
smthr.key = 'MatrixThreshold'; % to be documented in version 1.3
smthr.type = 'Integer';
smthr.default = NaN;
option.smthr = smthr;
graph.key = 'Graph';
graph.type = 'Integer';
graph.default = 0;
graph.keydefault = .25;
option.graph = graph;
interpol.key = 'Interpol';
interpol.type = 'String';
interpol.default = 'Quadratic';
interpol.keydefault = 'Quadratic';
option.interpol = interpol;
reso.key = 'Reso';
%reso.type = 'String';
%reso.choice = {0,'SemiTone'};
reso.default = 0;
option.reso = reso;
resofirst.key = 'First';
resofirst.type = 'Boolean';
resofirst.default = 0;
option.resofirst = resofirst;
resoloose.key = 'Loose';
resoloose.type = 'Boolean';
resoloose.default = 0;
option.resoloose = resoloose;
c.key = 'Pref';
c.type = 'Integer';
c.number = 2;
c.default = [0 0];
option.c = c;
near.key = 'Nearest';
near.type = 'Integer';
near.default = NaN;
option.near = near;
logsc.type = 'String';
logsc.choice = {'Lin','Log',0};
logsc.default = 'Lin';
option.logsc = logsc;
normal.key = 'Normalize';
normal.type = 'String';
normal.choice = {'Local','Global'};
normal.default = 'Global';
option.normal = normal;
extract.key = 'Extract';
extract.type = 'Boolean';
extract.default = 0;
option.extract = extract;
only.key = 'Only';
only.type = 'Boolean';
only.default = 0;
option.only = only;
delta.key = 'Track';
delta.type = 'Integer';
delta.default = 0;
delta.keydefault = Inf;
option.delta = delta;
mem.key = 'TrackMem';
mem.type = 'Integer';
mem.default = Inf;
option.mem = mem;
shorttrackthresh.key = 'CollapseTracks';
shorttrackthresh.type = 'Integer';
shorttrackthresh.default = 0;
shorttrackthresh.keydefault = 7;
option.shorttrackthresh = shorttrackthresh;
scan.key = 'ScanForward'; % specific to mironsets(..., 'Klapuri99')
scan.default = [];
option.scan = scan;
specif.option = option;
varargout = mirfunction(@mirpeaks,orig,varargin,nargout,specif,@init,@main);
function [x type] = init(x,option)
type = mirtype(x);
function p = main(x,option,postoption)
if iscell(x)
x = x{1};
end
if option.chro
option.order = 'Abscissa';
elseif option.ranked
option.order = 'Amplitude';
end
if not(isnan(option.near)) && option.m == 1
option.m = Inf;
end
x = purgedata(x);
if option.m <= 0
p = x;
return
end
d = get(x,'Data');
sr = get(x,'Sampling');
cha = 0; % Indicates when it is possible to represent as a curve along the
% Z-axis (channels) instead of the X-axis (initial abscissa).
if isnan(option.first)
option.first = option.cthr / 2;
end
if isa(x,'mirscalar')
t = get(x,'FramePos');
for i = 1:length(d)
for j = 1:length(d{i})
d{i}{j} = d{i}{j}';
if size(t{i},1) == 1
t{i}{j} = t{i}{j}(1,:,:)';
else
t{i}{j} = (t{i}{j}(1,:,:)+t{i}{j}(2,:,:))'/2;
end
end
end
elseif isa(x,'mirsimatrix')
t = get(x,'FramePos');
for i = 1:length(t)
for j = 1:length(t{i})
t{i}{j} = repmat((t{i}{j}(1,:,:)+t{i}{j}(2,:,:))'/2,...
[1 size(d{i}{j},2) 1]);
end
end
elseif isa(x,'mirhisto')
error('ERROR IN MIRPEAKS: peaks of histogram not considered yet.');
else
for i = 1:length(d)
for j = 1:length(d{i})
if iscell(d{i})
dij = d{i}{j};
if ~cha && j == 1 && size(dij,3) > 1 && size(dij,1) == 1
cha = 1;
end
if cha && j > 1 && size(dij,1) > 1
cha = -1;
end
end
end
for j = 1:length(d{i})
if iscell(d{i})
dij = d{i}{j};
if cha == 1
if iscell(dij)
for k = 1:size(dij,2)
d{i}{j}{k} = reshape(dij{k},size(dij{k},2),size(dij{k},3));
d{i}{j}{k} = d{i}{j}{k}';
end
else
d{i}{j} = reshape(dij,size(dij,2),size(dij,3));
d{i}{j} = d{i}{j}';
end
end
end
end
end
if cha == 1
t = get(x,'Channels');
else
t = get(x,'Pos');
end
end
pp = cell(1,length(d));
pv = cell(1,length(d));
pm = cell(1,length(d));
ppp = cell(1,length(d));
ppv = cell(1,length(d));
tp = cell(1,length(d));
tv = cell(1,length(d));
if isnan(option.thr)
option.thr = 0;
else
if option.vall
option.thr = 1-option.thr;
end
end
%if isnan(option.lthr)
% option.lthr = 1;
%else
% if option.vall
% option.lthr = 1-option.lthr;
% end
%end
if isnan(option.smthr)
option.smthr = option.thr - .2;
end
if not(isempty(option.scan))
pscan = get(option.scan,'PeakPos');
end
interpol = get(x,'Interpolable') && not(isempty(option.interpol)) && ...
((isnumeric(option.interpol) && option.interpol) || ...
(ischar(option.interpol) && not(strcmpi(option.interpol,'No')) && not(strcmpi(option.interpol,'Off'))));
for i = 1:length(d) % For each audio file,...
di = d{i};
if cha == 1
ti = t; %sure ?
else
ti = t{i};
end
if not(iscell(di))
di = {di};
if isa(x,'mirchromagram') && not(cha)
ti = {ti};
end
end
for h = 1:length(di) % For each segment,...
dh0 = di{h};
if option.vall
dh0 = -dh0;
end
dh2 = dh0;
[nl0 nc np nd0] = size(dh0);
if cha == 1
if iscell(ti)
%% problem here!!!
ti = ti{i}; %%%%%it seems that sometimes we need to use instead ti{i}(:);
end
th = repmat(ti,[1,nc,np,nd0]);
else
th = ti{h};
if iscell(th) % Non-numerical abscissae are transformed into numerical ones.
th = repmat((1:size(th,1))',[1,nc,np]);
else
if size(th,3)<np
th = repmat(th,[1,1,np]);
end
end
end
if option.c % If a prefered region is specified, the data is amplified accordingly
dh0 = dh0.*exp(-(th-option.c(1)).^2/2/option.c(2)^2)...
/option.c(2)/sqrt(2*pi)/2;
end
% Now the data is rescaled. the minimum is set to 0
% and the maximum to 1.
state = warning('query','MATLAB:divideByZero');
warning('off','MATLAB:divideByZero');
% Let's first normalize all frames globally:
dh0 = (dh0-repmat(min(min(min(dh0,[],1),[],2),[],4),[nl0 nc 1 nd0]))./...
repmat(max(max(max(dh0,[],1),[],2),[],4)...
-min(min(min(dh0,[],1),[],2),[],4),[nl0 nc 1 nd0]);
for l = 1:nd0
[unused lowc] = find(max(dh0(:,:,:,l))<option.thr);
dh0(:,lowc,1,l) = 0;
end
if strcmpi(option.normal,'Local')
% Normalizing each frame separately:
dh0 = (dh0-repmat(min(min(dh0,[],1),[],4),[nl0 1 1 nd0]))./...
repmat(max(max(dh0,[],1),[],4)...
-min(min(dh0,[],1),[],4),[nl0 1 1 nd0]);
end
warning(state.state,'MATLAB:divideByZero');
if option.nobegin
dh0 = [Inf(1,nc,np,nd0);dh0];
% This infinite value at the beginning
% prevents the selection of the first sample of data
dh2 = [Inf(1,nc,np,nd0);dh2];
th = [NaN(1,nc,np,1);th];
else
dh0 = [-Inf(1,nc,np,nd0);dh0];
% This infinite negative value at the beginning
% ensures the selection of the first sample of data
dh2 = [-Inf(1,nc,np,nd0);dh2];
th = [NaN(1,nc,np,1);th];
end
if option.noend
dh0 = [dh0;Inf(1,nc,np,nd0)];
% idem for the last sample of data
dh2 = [dh2;Inf(1,nc,np,nd0)];
th = [th;NaN(1,nc,np,1)];
else
dh0 = [dh0;-Inf(1,nc,np,nd0)];
dh2 = [dh2;-Inf(1,nc,np,nd0)];
th = [th;NaN(1,nc,np,1)];
end
nl0 = nl0+2;
% Rearrange the 4th dimension (if used) into the 1st one.
nl = nl0*nd0;
dh = zeros(nl,nc,np);
dh3 = zeros(nl,nc,np);
th2 = zeros(nl,nc,np);
for l = 1:nd0
dh((l-1)*nl0+(1:nl0)',:,:) = dh0(:,:,:,l);
dh3((l-1)*nl0+(1:nl0)',:,:) = dh2(:,:,:,l);
th2((l-1)*nl0+(1:nl0)',:,:) = th(:,:,:);
end
th = th2; % The X-abscissa
ddh = diff(dh);
% Let's find the local maxima
for l = 1:np
dl = dh(2:end-1,:,l);
for k = 1:nc
dk = dl(:,k);
mx{1,k,l} = find(and(and(dk >= option.cthr, ...
dk >= option.thr),...
... dk <= option.lthr)),
and(ddh(1:(end-1),k,l) > 0, ...
ddh(2:end,k,l) <= 0)))+1;
end
end
if option.cthr
for l = 1:np
for k = 1:nc
finalmxk = [];
mxk = mx{1,k,l};
if not(isempty(mxk))
wait = 0;
if length(mxk)>5000
wait = waitbar(0,['Selecting peaks... (0 out of 0)']);
end
%if option.m < Inf
% [unused,idx] = sort(dh(mxk,k,l),'descend'); % The peaks are sorted in decreasing order
% mxk = mxk(sort(idx(1:option.m)));
%end
j = 1; % Scans the peaks from begin to end.
mxkj = mxk(j); % The current peak
jj = j+1;
bufmin = Inf;
bufmax = dh(mxkj,k,l);
oldbufmin = min(dh(1:mxk(1)-1,k,l));
while jj <= length(mxk)
if wait && not(mod(jj,5000))
waitbar(jj/length(mxk),wait,['Selecting peaks... (',num2str(length(finalmxk)),' out of ',num2str(jj),')']);
end
bufmin = min(bufmin, ...
min(dh(mxk(jj-1)+1:mxk(jj)-1,k,l)));
if bufmax - bufmin < option.cthr
% There is no contrastive notch
if dh(mxk(jj),k,l) > bufmax && ...
(dh(mxk(jj),k,l) - bufmax > option.first ...
|| (bufmax - oldbufmin < option.cthr))
% If the new peak is significantly
% higher than the previous one,
% The peak is transfered to the new
% position
j = jj;
mxkj = mxk(j); % The current peak
bufmax = dh(mxkj,k,l);
oldbufmin = min(oldbufmin,bufmin);
bufmin = Inf;
elseif dh(mxk(jj),k,l) - bufmax <= option.first
bufmax = max(bufmax,dh(mxk(jj),k,l));
oldbufmin = min(oldbufmin,bufmin);
end
else
% There is a contrastive notch
if bufmax - oldbufmin < option.cthr
% But the previous peak candidate
% is too weak and therefore
% discarded
oldbufmin = min(oldbufmin,bufmin);
else
% The previous peak candidate is OK
% and therefore stored
finalmxk(end+1) = mxkj;
oldbufmin = bufmin;
end
bufmax = dh(mxk(jj),k,l);
j = jj;
mxkj = mxk(j); % The current peak
bufmin = Inf;
end
jj = jj+1;
end
if bufmax - oldbufmin >= option.cthr && ...
bufmax - min(dh(mxk(j)+1:end,k,l)) >= option.cthr
% The last peak candidate is OK and stored
finalmxk(end+1) = mxk(j);
end
if wait
waitbar(1,wait);
close(wait);
drawnow
end
end
mx{1,k,l} = finalmxk;
end
end
end
if not(isempty(option.scan)) % 'ScanForward' option, used for 'Klapuri99' in mironsets
for l = 1:np
for k = 1:nc
pscankl = pscan{i}{h}{1,k,l}; % scan seed positions
mxkl = [];
lp = length(pscankl); % number of peaks
for jj = 1:lp % for each peak
fmx = find(mx{1,k,l}>pscankl(jj),1);
% position of the next max following the
% current seed
fmx = mx{1,k,l}(fmx);
if jj<lp && (isempty(fmx) || fmx>=pscankl(jj+1))
[unused fmx] = max(dh(pscankl(jj):...
pscankl(jj+1)-1,k,l));
fmx = fmx+pscankl(jj)-1;
elseif jj==lp && isempty(fmx)
[unused fmx] = max(dh(pscankl(jj):end,k,l));
fmx = fmx+pscankl(jj)-1;
end
mxkl = [mxkl fmx];
end
mx{1,k,l} = mxkl;
end
end
end
if not(isequal(option.reso,0)) % Removing peaks too close to higher peaks
if ischar(option.reso) && strcmpi(option.reso,'SemiTone')
compar = @semitone_compar;
elseif isnumeric(option.reso)
compar = @dist_compar;
end
for l = 1:np
for k = 1:nc
[unused ind] = sort(dh(mx{1,k,l}),'descend');
mxlk = mx{1,k,l}(ind);
del = [];
j = 1;
while j < length(mxlk)
jj = j+1;
while jj <= length(mxlk)
if compar(th(mxlk(jj),k,l),th(mxlk(j),k,l),...
option.reso)
if option.resoloose
mxlk(jj) = [];
jj = jj-1;
elseif option.resofirst && mxlk(j)>mxlk(jj)
del = [del j];
else
del = [del jj];
end
end
jj = jj+1;
end
j = j+1;
end
if ~option.resoloose
mxlk(del) = [];
end
mx{1,k,l} = mxlk;
end
end
end
if not(isnan(option.near)) % Finding a peak nearest a given prefered location
for l = 1:np
for k = 1:nc
mxlk = mx{1,k,l};
if strcmp(option.logsc,'Log')
[M I] = min(abs(log(th(mxlk,k,l)/option.near)));
else
[M I] = min(abs(th(mxlk,k,l)-option.near));
end
mx{1,k,l} = mxlk(I);
end
end
end
if option.delta % Peak tracking
tp{i}{h} = cell(1,np);
if interpol
tpp{i}{h} = cell(1,np);
tpv{i}{h} = cell(1,np);
end
for l = 1:np
% mxl will be the resulting track position matrix
% and myl the related track amplitude
% In the first frame, tracks can be identified to peaks.
mxl = mx{1,1,l}(:)-1;
myl = dh(mx{1,1,l}(:),k,l);
% To each peak is associated the related track ID
tr2 = 1:length(mx{1,1,l});
grvy = []; % The graveyard...
wait = 0;
if nc-1>500
wait = waitbar(0,['Tracking peaks...']);
end
for k = 1:nc-1
% For each successive frame...
if not(isempty(grvy))
old = find(grvy(:,2) == k-option.mem-1);
grvy(old,:) = [];
end
if wait && not(mod(k,100))
waitbar(k/(nc-1),wait);
end
mxk1 = mx{1,k,l}; % w^k
mxk2 = mx{1,k+1,l}; % w^{k+1}
thk1 = th(mxk1,k,l);
thk2 = th(mxk2,k,l);
myk2 = dh(mx{1,k+1,l},k,l); % amplitude
tr1 = tr2;
tr2 = NaN(1,length(mxk2));
mxl(:,k+1) = mxl(:,k);
if isempty(thk1) || isempty(thk2)
%% IS THIS TEST NECESSARY??
myl(:,k+1) = 0;
else
for n = 1:length(mxk1)
% Let's check each track.
tr = tr1(n); % Current track.
if not(isnan(tr))
% still alive...
% Step 1 in Mc Aulay & Quatieri
[int m] = min(abs(thk2-thk1(n)));
if isinf(int) || int > option.delta
% all w^{k+1} outside matching interval:
% partial becomes dead
mxl(tr,k+1) = mxl(tr,k);
myl(tr,k+1) = 0;
grvy = [grvy; tr k]; % added to the graveyard
else
% closest w^{k+1} is tentatively selected:
% candidate match
% Step 2 in Mc Aulay & Quatieri
[best mm] = min(abs(thk2(m)-th(mx{1,k,l})));
if mm == n
% no better match to remaining w^k:
% definite match
mxl(tr,k+1) = mxk2(m)-1;
myl(tr,k+1) = myk2(m);
tr2(m) = tr;
thk1(n) = -Inf; % selected w^k is eliminated from further consideration
thk2(m) = Inf; % selected w^{k+1} is eliminated as well
if not(isempty(grvy))
zz = find ((mxl(grvy(:,1),k) >= mxl(tr,k) & ...
mxl(grvy(:,1),k) <= mxl(tr,k+1)) | ...
(mxl(grvy(:,1),k) <= mxl(tr,k) & ...
mxl(grvy(:,1),k) >= mxl(tr,k+1)));
grvy(zz,:) = [];
end
else
% let's look at adjacent lower w^{k+1}...
[int mmm] = min(abs(thk2(1:m)-thk1(n)));
if int > best || ... % New condition added (Lartillot 16.4.2010)
isinf(int) || ... % Conditions proposed in Mc Aulay & Quatieri (all w^{k+1} below matching interval)
int > option.delta
% partial becomes dead
mxl(tr,k+1) = mxl(tr,k);
myl(tr,k+1) = 0;
grvy = [grvy; tr k]; % added to the graveyard
else
% definite match
mxl(tr,k+1) = mxk2(mmm)-1;
myl(tr,k+1) = myk2(mmm);
tr2(mmm) = tr;
thk1(n) = -Inf; % selected w^k is eliminated from further consideration
thk2(mmm) = Inf; % selected w^{k+1} is eliminated as well
if not(isempty(grvy))
zz = find ((mxl(grvy(:,1),k) >= mxl(tr,k) & ...
mxl(grvy(:,1),k) <= mxl(tr,k+1)) | ...
(mxl(grvy(:,1),k) <= mxl(tr,k) & ...
mxl(grvy(:,1),k) >= mxl(tr,k+1)));
grvy(zz,:) = [];
end
end
end
end
end
end
end
% Step 3 in Mc Aulay & Quatieri
for m = 1:length(mxk2)
if not(isinf(thk2(m)))
% unmatched w^{k+1}
if isempty(grvy)
int = [];
else
% Let's try to reuse a zombie from the
% graveyard (Lartillot).
[int z] = min(abs(th(mxl(grvy(:,1),k+1)+1,k,l)-thk2(m)));
end
if isempty(int) || int > option.delta ...
|| int > min(abs(th(mxl(:,k+1)+1,k,l)-thk2(m)))
% No suitable zombie.
% birth of a new partial (Mc Aulay &
% Quatieri)
mxl = [mxl;zeros(1,k+1)];
tr = size(mxl,1);
mxl(tr,k) = mxk2(m)-1;
else
% Suitable zombie found. (Lartillot)
tr = grvy(z,1);
grvy(z,:) = [];
end
mxl(tr,k+1) = mxk2(m)-1;
myl(tr,k+1) = myk2(m);
tr2(m) = tr;
end
end
end
if wait
waitbar(1,wait);
close(wait);
drawnow
end
if size(mxl,1) > option.m
tot = sum(myl,2);
[tot ix] = sort(tot,'descend');
mxl(ix(option.m+1:end),:) = [];
myl(ix(option.m+1:end),:) = [];
end
mxl(:,not(max(myl))) = 0;
if option.shorttrackthresh
[myl bestrack] = max(myl,[],1);
mxl = mxl(bestrack + (0:size(mxl,2)-1)*size(mxl,1));
changes = find(not(bestrack(1:end-1) == bestrack(2:end)))+1;
if not(isempty(changes))
lengths = diff([1 changes nc+1]);
shorts = find(lengths < option.shorttrackthresh);
for k = 1:length(shorts)
if shorts(k) == 1
k1 = 1;
else
k1 = changes(shorts(k)-1);
end
k2 = k1 + lengths(shorts(k)) -1;
myl(1,k1:k2) = 0;
mxl(1,k1:k2) = 0;
end
end
end
tp{i}{h}{l} = mxl;
tv{i}{h}{l} = myl;
if interpol
tpv{i}{h}{l} = zeros(size(mxl));
tpp{i}{h}{l} = zeros(size(mxl));
for k = 1:size(mxl,2)
for j = 1:size(mxl,1)
mj = mxl(j,k);
if mj>2 && mj<size(dh3,1)-1
% More precise peak position
y0 = dh3(mj,k,l);
ym = dh3(mj-1,k,l);
yp = dh3(mj+1,k,l);
p = (yp-ym)/(2*(2*y0-yp-ym));
tpv{i}{h}{l}(j,k) = y0 - 0.25*(ym-yp)*p;
if p >= 0
tpp{i}{h}{l}(j,k) = (1-p)*th(mj,k,l)+p*th(mj+1,k,l);
elseif p < 0
tpp{i}{h}{l}(j,k) = (1+p)*th(mj,k,l)-p*th(mj-1,k,l);
end
elseif mj
tpv{i}{h}{l}(j,k) = dh3(mj,k,l);
tpp{i}{h}{l}(j,k) = th(mj,k,l);
end
end
end
end
end
end
if isa(x,'mirsimatrix') && option.graph
% Finding the best branch inside a graph constructed out of a
% similarity matrix
g{i}{h} = cell(1,nc,np);
% Branch info related to each peak
br{i}{h} = {};
% Info related to each branch
scog{i}{h} = cell(1,nc,np);
% Score related to each peak
scob{i}{h} = [];
% Score related to each branch
for l = 1:np
wait = waitbar(0,['Creating peaks graph...']);
for k = 1:nc
g{i}{h}{1,k,l} = cell(size(mx{1,k,l}));
scog{i}{h}{1,k,l} = zeros(size(mx{1,k,l}));
if wait && not(mod(k,50))
waitbar(k/(nc-1),wait);
end
mxk = mx{1,k,l}; % Peaks in current frame
for j = k-1:-1:max(1,k-100) % Recent frames
mxj = mx{1,j,l}; % Peaks in one recent frame
for kk = 1:length(mxk)
mxkk = mxk(kk); % For each of current peaks
for jj = 1:length(mxj)
mxjj = mxj(jj); % For each of recent peaks
sco = k-j - abs(mxkk-mxjj);
% Crossprogression from recent to
% current peak
if sco >= 0
% Negative crossprogression excluded
dist = 0;
% The distance between recent and
% current peak is the sum of all the
% simatrix values when joining the two
% peaks with a straight line.
for m = j:k
% Each point in that straight line.
mxm = mxjj + (mxkk-mxjj)*(m-j)/(k-j);
if mxm == floor(mxm)
dist = dist + 1-dh(mxm,m,l);
else
dhm0 = dh(floor(mxm),m,l);
dhm1 = dh(ceil(mxm),m,l);
dist = dist + 1-...
(dhm0 + ...
(dhm1-dhm0)*(mxm-floor(mxm)));
end
if dist > option.graph
break
end
end
if dist < option.graph
% If the distance between recent
% and current peak is not too high,
% a new edge is formed between the
% peaks, and added to the graph.
gj = g{i}{h}{1,j,l}{jj};
% Branch information associated
% with recent peak
gk = g{i}{h}{1,k,l}{kk};
% Branch information associated
% with current peak
if isempty(gk) || ...
sco > scog{i}{h}{1,k,l}(kk)
% Current peak branch to be updated
if isempty(gj)
% New branch starting
% from scratch
newsco = sco;
scob{i}{h}(end+1) = newsco;
bid = length(scob{i}{h});
g{i}{h}{1,j,l}{jj} = ...
[k kk bid newsco];
br{i}{h}{bid} = [j jj;k kk];
else
newsco = scog{i}{h}{1,j,l}(jj)+sco;
if length(gj) == 1
% Recent peak not
% associated with other
% branch
% -> Branch extension
bid = gj;
g{i}{h}{1,j,l}{jj} = ...
[k kk bid newsco];
br{i}{h}{bid}(end+1,:) = [k kk];
else
% Recent peak already
% associated with other
% branch
% -> Branch fusion
bid = length(scob{i}{h})+1;
g{i}{h}{1,j,l}{jj} = ...
[k kk bid newsco; gj];
other = br{i}{h}{gj(1,3)};
% Other branch
% info
% Let's copy its
% prefix to the new
% branch:
other(other(:,1)>j,:) = [];
br{i}{h}{bid} = [other;k kk];
end
scob{i}{h}(bid) = newsco;
end
g{i}{h}{1,k,l}{kk} = bid;
% New peak associated with
% branch
scog{i}{h}{1,k,l}(kk) = newsco;
end
end
end
end
end
end
end
[scob{i}{h} IX] = sort(scob{i}{h},'descend');
% Branch are ordered from best score to lowest
br{i}{h} = br{i}{h}(IX);
if wait
waitbar(1,wait);
close(wait);
drawnow
end
end
end
for l = 1:np % Orders the peaks and select the best ones
for k = 1:nc
mxk = mx{1,k,l};
if length(mxk) > option.m
[unused,idx] = sort(dh(mxk,k,l),'descend');
idx = idx(1:option.m);
elseif strcmpi(option.order,'Amplitude')
[unused,idx] = sort(dh(mxk,k,l),'descend');
else
idx = 1:length(dh(mxk,k,l));
end
if strcmpi(option.order,'Abscissa')
mx{1,k,l} = sort(mxk(idx));
elseif strcmpi(option.order,'Amplitude')
mx{1,k,l} = mxk(idx);
end
end
end
if option.extract % Extracting the positive part of the curve containing the peaks
if isa(x,'mirtemporal')
filn = floor(sr{i}/25);
else
filn = 10;
end
if filn>1 && size(dh3,1)>5
filn = min(filn,floor(size(dh3,1)/3));
fild = filtfilt(ones(1,filn)/2,1,dh3(2:end-1,:,:))/filn/2;
else
fild = dh3(2:end-1,:,:);
end
fild = [zeros(1,size(fild,2),size(fild,3));diff(fild)];
for l = 1:np
for k = 1:nc
idx = 1:size(dh,1);
mxlk = sort(mx{1,k,l}-1);
for j = 1:length(mxlk)
if fild(mxlk(j),k,l) < 0
bef0 = find(fild(1:mxlk(j)-1,k,l)>=0);
if isempty(bef0)
bef0 = [];
end
else
bef0 = mxlk(j)-1;
end
if isempty(bef0)
bef = 0;
else
bef = find(fild(1:bef0(end),k,l)<=0);
if isempty(bef)
bef = 0;
end
end
if j>1 && bef(end)<aft(1)+2
idx(mxlk(j-1):mxlk(j)) = 0;
[unused btw] = min(dh3(mxlk(j-1)+1:mxlk(j)+1,k,l));
btw = btw+mxlk(j-1);
idx(btw-2:btw+2) = btw-2:btw+2;
bef = btw+2;
end
if fild(mxlk(j),k,l) > 0
aft0 = find(fild(mxlk(j)+1:end,k,l)<=0)+mxlk(j);
if isempty(aft0)
aft0 = [];
end
else
aft0 = mxlk(j)+1;
end
if isempty(aft0)
aft = size(d{i}{h},1)+1;
else
aft = find(fild(aft0(1):end,k,l)>=0)+mxlk(j);
if isempty(aft)
aft = size(d{i}{h},1)+1;
end
end
idx(bef(end)+3:aft(1)-3) = 0;
end
idx = idx(find(idx));
dh3(idx,k,l) = NaN;
end
end
end
if option.vall
dh3 = -dh3;
end
mmx = cell(1,nc,np);
mmy = cell(1,nc,np);
mmv = cell(1,nc,np);
for l = 1:np
for k = 1:nc
mmx{1,k,l} = mod(mx{1,k,l}(:,:,1),nl0)-1;
mmy{1,k,l} = ceil(mx{1,k,l}/nl0);
mmv{1,k,l} = dh3(mx{1,k,l}(:,:,1),k,l);
end
end
pp{i}{h} = mmx;
pm{i}{h} = mmy;
pv{i}{h} = mmv;
if not(interpol)
ppp{i}{h} = {};
ppv{i}{h} = {};
else % Interpolate to find the more exact peak positions
pih = cell(1,nc,np);
vih = cell(1,nc,np);
for l = 1:np
for k = 1:nc
mxlk = mx{1,k,l};
vih{1,k,l} = zeros(length(mxlk),1);
pih{1,k,l} = zeros(length(mxlk),1);
for j = 1:length(mxlk)
mj = mxlk(j); % Current values
if strcmpi(option.interpol,'quadratic')
if mj>2 && mj<length(dh3)-1
% More precise peak position
y0 = dh3(mj,k,l);
ym = dh3(mj-1,k,l);
yp = dh3(mj+1,k,l);
p = (yp-ym)/(2*(2*y0-yp-ym));
vih{1,k,l}(j) = y0 - 0.25*(ym-yp)*p;
if p >= 0
pih{1,k,l}(j) = (1-p)*th(mj,k,l)+p*th(mj+1,k,l);
elseif p < 0
pih{1,k,l}(j) = (1+p)*th(mj,k,l)-p*th(mj-1,k,l);
end
else
vih{1,k,l}(j) = dh3(mj,k,l);
pih{1,k,l}(j) = th(mj,k,l);
end
end
end
end
end
ppp{i}{h} = pih;
ppv{i}{h} = vih;
end
if not(iscell(d{i})) % for chromagram
d{i} = dh3(2:end-1,:,:,:);
else
if cha == 1
d{i}{h} = zeros(1,size(dh3,2),size(dh3,1)-2);
for k = 1:size(dh3,2)
d{i}{h}(1,k,:) = dh3(2:end-1,k);
end
else
d{i}{h} = dh3(2:end-1,:,:,:);
end
end
if option.only
dih = zeros(size(d{i}{h}));
for l = 1:np
for k = 1:nc
dih(pp{i}{h}{1,k,l},k,l) = ...
d{i}{h}(pp{i}{h}{1,k,l},k,l);
end
end
d{i}{h} = dih;
end
end
end
p = set(x,'PeakPos',pp,'PeakVal',pv,'PeakMode',pm);
if interpol
p = set(p,'PeakPrecisePos',ppp,'PeakPreciseVal',ppv);
end
if option.extract
p = set(p,'Data',d);
end
empty = cell(1,length(d));
if option.only
p = set(p,'Data',d,'PeakPos',empty,'PeakVal',empty,'PeakMode',empty);
end
if option.delta
p = set(p,'TrackPos',tp,'TrackVal',tv);
if interpol
p = set(p,'TrackPrecisePos',tpp,'TrackPreciseVal',tpv);
end
end
if isa(x,'mirsimatrix') && option.graph
p = set(p,'Graph',g,'Branch',br);
end
function y = semitone_compar(p1,p2,thres)
y = max(p1,p2)/min(p1,p2) < 2^(1/12);
function y = dist_compar(p1,p2,thres)
y = abs(p1-p2) < thres;