Daniel@0: function varargout = mirtempo(x,varargin) Daniel@0: % t = mirtempo(x) evaluates the tempo in beats per minute (BPM). Daniel@0: % Optional arguments: Daniel@0: % mirtempo(...,'Total',m) selects not only the best tempo, but the m Daniel@0: % best tempos. Daniel@0: % mirtempo(...,'Frame',l,h) orders a frame decomposition of window Daniel@0: % length l (in seconds) and hop factor h, expressed relatively to Daniel@0: % the window length. For instance h = 1 indicates no overlap. Daniel@0: % Default values: l = 3 seconds and h = .1 Daniel@0: % mirtempo(...,'Min',mi) indicates the lowest tempo taken into Daniel@0: % consideration, expressed in bpm. Daniel@0: % Default value: 40 bpm. Daniel@0: % mirtempo(...,'Max',ma) indicates the highest tempo taken into Daniel@0: % consideration, expressed in bpm. Daniel@0: % Default value: 200 bpm. Daniel@0: % mirtempo(...,s) selects the tempo estimation strategy: Daniel@0: % s = 'Autocor': Approach based on the computation of the Daniel@0: % autocorrelation. (Default strategy) Daniel@0: % Option associated to the mirautocor function can be Daniel@0: % passed here as well (see help mirautocor): Daniel@0: % 'Enhanced' (toggled on by default here) Daniel@0: % s = 'Spectrum': Approach based on the computation of the Daniel@0: % spectrum . Daniel@0: % Option associated to the mirspectrum function can be Daniel@0: % passed here as well (see help mirspectrum): Daniel@0: % 'ZeroPad' (set by default to 10000 samples) Daniel@0: % 'Prod' (toggled off by default) Daniel@0: % These two strategies can be combined: the autocorrelation Daniel@0: % function is translated into the frequency domain in order Daniel@0: % to be compared to the spectrum curve. Daniel@0: % tempo(...,'Autocor','Spectrum') multiplies the two curves. Daniel@0: % Alternatively, an autocorrelation function ac or a spectrum sp Daniel@0: % can be directly passed to the function tempo: Daniel@0: % mirtempo(ac) or mirtempo(sp) Daniel@0: % The options related to the onset detection phase can be specified Daniel@0: % here as well (see help mironsets): Daniel@0: % onset detection strategies: 'Envelope', 'DiffEnvelope' Daniel@0: % (corresponding to 'Envelope', 'Diff'), 'SpectralFlux, Daniel@0: % 'Pitch', 'Log', 'Mu', 'Filterbank' Daniel@0: % mironsets(...,'Sum',w) specifies when to sum the channels. Daniel@0: % Possible values: Daniel@0: % w = 'Before': sum before the autocorrelation or Daniel@0: % spectrum computation. Daniel@0: % w = 'After': autocorrelation or spectrum computed Daniel@0: % for each band, and summed into a "summary". Daniel@0: % mirenvelope options: 'HalfwaveCenter','Diff' (toggled on by Daniel@0: % default here),'HalfwaveDiff','Center','Smooth', Daniel@0: % 'Sampling' Daniel@0: % mirflux options: 'Inc','Halfwave','Complex','Median' Daniel@0: % mirtempo(...,'Resonance',r) specifies the resonance curve, which Daniel@0: % emphasizes the periods that are more easily perceived. Daniel@0: % Possible values: 'ToiviainenSnyder' (default), 0 (toggled off) Daniel@0: % Optional arguments used for the peak picking (cf. help mirpeaks) Daniel@0: % mirtempo(...,'Contrast',thr): a threshold value. A given local Daniel@0: % maximum will be considered as a peak if its distance with the Daniel@0: % previous and successive local minima (if any) is higher than Daniel@0: % this threshold. This distance is expressed with respect to the Daniel@0: % total amplitude of the autocorrelation function. Daniel@0: % if no value for thr is given, the value thr=0.1 is chosen Daniel@0: % by default. Daniel@0: % Daniel@0: % [t,p] = mirtempo(...) also displays the result of the signal analysis Daniel@0: % leading to the tempo estimation, and shows in particular the Daniel@0: % peaks corresponding to the tempo values. Daniel@0: Daniel@0: Daniel@0: sum.key = 'Sum'; Daniel@0: sum.type = 'String'; Daniel@0: sum.choice = {'Before','After','Adjacent',0}; Daniel@0: sum.default = 'Before'; Daniel@0: option.sum = sum; Daniel@0: Daniel@0: %% options related to mironsets: Daniel@0: Daniel@0: frame.key = 'Frame'; Daniel@0: frame.type = 'Integer'; Daniel@0: frame.number = 2; Daniel@0: frame.default = [0 0]; Daniel@0: frame.keydefault = [3 .1]; Daniel@0: option.frame = frame; Daniel@0: Daniel@0: fea.type = 'String'; Daniel@0: fea.choice = {'Envelope','DiffEnvelope','SpectralFlux','Pitch'}; Daniel@0: fea.default = 'Envelope'; Daniel@0: option.fea = fea; Daniel@0: Daniel@0: %% options related to 'Envelope': Daniel@0: Daniel@0: envmeth.key = 'Method'; Daniel@0: envmeth.type = 'String'; Daniel@0: envmeth.choice = {'Filter','Spectro'}; Daniel@0: envmeth.default = 'Filter'; Daniel@0: option.envmeth = envmeth; Daniel@0: Daniel@0: %% options related to 'Filter': Daniel@0: Daniel@0: fb.key = 'Filterbank'; Daniel@0: fb.type = 'Integer'; Daniel@0: fb.default = 10; Daniel@0: option.fb = fb; Daniel@0: Daniel@0: fbtype.key = 'FilterbankType'; Daniel@0: fbtype.type = 'String'; Daniel@0: fbtype.choice = {'Gammatone','Scheirer','Klapuri'}; Daniel@0: fbtype.default = 'Gammatone'; Daniel@0: option.fbtype = fbtype; Daniel@0: Daniel@0: ftype.key = 'FilterType'; Daniel@0: ftype.type = 'String'; Daniel@0: ftype.choice = {'IIR','HalfHann'}; Daniel@0: ftype.default = 'IIR'; Daniel@0: option.ftype = ftype; Daniel@0: Daniel@0: %% options related to 'Spectro': Daniel@0: Daniel@0: band.type = 'String'; Daniel@0: band.choice = {'Freq','Mel','Bark','Cents'}; Daniel@0: band.default = 'Freq'; Daniel@0: option.band = band; Daniel@0: Daniel@0: Daniel@0: chwr.key = 'HalfwaveCenter'; Daniel@0: chwr.type = 'Boolean'; Daniel@0: chwr.default = 0; Daniel@0: option.chwr = chwr; Daniel@0: Daniel@0: diff.key = 'Diff'; Daniel@0: diff.type = 'Boolean'; Daniel@0: diff.default = 1; % Different default for mirtempo Daniel@0: option.diff = diff; Daniel@0: Daniel@0: diffhwr.key = 'HalfwaveDiff'; Daniel@0: diffhwr.type = 'Integer'; Daniel@0: diffhwr.default = 0; Daniel@0: diffhwr.keydefault = 1; Daniel@0: option.diffhwr = diffhwr; Daniel@0: Daniel@0: lambda.key = 'Lambda'; Daniel@0: lambda.type = 'Integer'; Daniel@0: lambda.default = 1; Daniel@0: option.lambda = lambda; Daniel@0: Daniel@0: mu.key = 'Mu'; Daniel@0: mu.type = 'Boolean'; Daniel@0: mu.default = 0; Daniel@0: option.mu = mu; Daniel@0: Daniel@0: log.key = 'Log'; Daniel@0: log.type = 'Boolean'; Daniel@0: log.default = 0; Daniel@0: option.log = log; Daniel@0: Daniel@0: c.key = 'Center'; Daniel@0: c.type = 'Boolean'; Daniel@0: c.default = 0; Daniel@0: option.c = c; Daniel@0: Daniel@0: aver.key = 'Smooth'; Daniel@0: aver.type = 'Integer'; Daniel@0: aver.default = 0; Daniel@0: aver.keydefault = 30; Daniel@0: option.aver = aver; Daniel@0: Daniel@0: sampling.key = 'Sampling'; Daniel@0: sampling.type = 'Integer'; Daniel@0: sampling.default = 0; Daniel@0: option.sampling = sampling; Daniel@0: Daniel@0: %% options related to 'SpectralFlux' Daniel@0: Daniel@0: complex.key = 'Complex'; Daniel@0: complex.type = 'Boolean'; Daniel@0: complex.default = 0; Daniel@0: option.complex = complex; Daniel@0: Daniel@0: inc.key = 'Inc'; Daniel@0: inc.type = 'Boolean'; Daniel@0: inc.default = 1; Daniel@0: option.inc = inc; Daniel@0: Daniel@0: median.key = 'Median'; Daniel@0: median.type = 'Integer'; Daniel@0: median.number = 2; Daniel@0: median.default = [.2 1.3]; Daniel@0: option.median = median; Daniel@0: Daniel@0: hw.key = 'Halfwave'; Daniel@0: hw.type = 'Boolean'; Daniel@0: hw.default = 1; Daniel@0: option.hw = hw; Daniel@0: Daniel@0: Daniel@0: %% options related to mirautocor: Daniel@0: Daniel@0: aut.key = 'Autocor'; Daniel@0: aut.type = 'Integer'; Daniel@0: aut.default = 0; Daniel@0: aut.keydefault = 1; Daniel@0: option.aut = aut; Daniel@0: Daniel@0: nw.key = 'NormalWindow'; Daniel@0: nw.default = 0; Daniel@0: option.nw = nw; Daniel@0: Daniel@0: enh.key = 'Enhanced'; Daniel@0: enh.type = 'Integers'; Daniel@0: enh.default = 2:10; Daniel@0: enh.keydefault = 2:10; Daniel@0: option.enh = enh; Daniel@0: Daniel@0: r.key = 'Resonance'; Daniel@0: r.type = 'String'; Daniel@0: r.choice = {'ToiviainenSnyder','vanNoorden',0,'off','no'}; Daniel@0: r.default = 'ToiviainenSnyder'; Daniel@0: option.r = r; Daniel@0: Daniel@0: Daniel@0: %% options related to mirspectrum: Daniel@0: Daniel@0: spe.key = 'Spectrum'; Daniel@0: spe.type = 'Integer'; Daniel@0: spe.default = 0; Daniel@0: spe.keydefault = 1; Daniel@0: option.spe = spe; Daniel@0: Daniel@0: zp.key = 'ZeroPad'; Daniel@0: zp.type = 'Integer'; Daniel@0: zp.default = 10000; Daniel@0: zp.keydefault = Inf; Daniel@0: option.zp = zp; Daniel@0: Daniel@0: prod.key = 'Prod'; Daniel@0: prod.type = 'Integers'; Daniel@0: prod.default = 0; Daniel@0: prod.keydefault = 2:6; Daniel@0: option.prod = prod; Daniel@0: Daniel@0: Daniel@0: %% options related to the peak detection Daniel@0: Daniel@0: m.key = 'Total'; Daniel@0: m.type = 'Integer'; Daniel@0: m.default = 1; Daniel@0: option.m = m; Daniel@0: Daniel@0: thr.key = 'Contrast'; Daniel@0: thr.type = 'Integer'; Daniel@0: thr.default = 0.1; Daniel@0: option.thr = thr; Daniel@0: Daniel@0: mi.key = 'Min'; Daniel@0: mi.type = 'Integer'; Daniel@0: mi.default = 40; Daniel@0: option.mi = mi; Daniel@0: Daniel@0: ma.key = 'Max'; Daniel@0: ma.type = 'Integer'; Daniel@0: ma.default = 200; Daniel@0: option.ma = ma; Daniel@0: Daniel@0: track.key = 'Track'; Daniel@0: track.type = 'Boolean'; Daniel@0: track.default = 0; Daniel@0: option.track = track; Daniel@0: Daniel@0: pref.key = 'Pref'; Daniel@0: pref.type = 'Integer'; Daniel@0: pref.number = 2; Daniel@0: pref.default = [0 .2]; Daniel@0: option.pref = pref; Daniel@0: Daniel@0: perio.key = 'Periodicity'; Daniel@0: perio.type = 'Boolean'; Daniel@0: perio.default = 0; Daniel@0: option.perio = perio; Daniel@0: Daniel@0: specif.option = option; Daniel@0: Daniel@0: varargout = mirfunction(@mirtempo,x,varargin,nargout,specif,@init,@main); Daniel@0: Daniel@0: Daniel@0: %% INIT Daniel@0: Daniel@0: function [y type] = init(x,option) Daniel@0: if iscell(x) Daniel@0: x = x{1}; Daniel@0: end Daniel@0: if option.perio Daniel@0: option.m = 3; Daniel@0: option.enh = 2:10; Daniel@0: end Daniel@0: if not(isamir(x,'mirautocor')) && not(isamir(x,'mirspectrum')) Daniel@0: if isframed(x) && strcmpi(option.fea,'Envelope') && not(isamir(x,'mirscalar')) Daniel@0: warning('WARNING IN MIRTEMPO: The input should not be already decomposed into frames.'); Daniel@0: disp(['Suggestion: Use the ''Frame'' option instead.']) Daniel@0: end Daniel@0: if strcmpi(option.sum,'Before') Daniel@0: optionsum = 1; Daniel@0: elseif strcmpi(option.sum,'Adjacent') Daniel@0: optionsum = 5; Daniel@0: else Daniel@0: optionsum = 0; Daniel@0: end Daniel@0: if option.frame.length.val Daniel@0: x = mironsets(x,option.fea,'Filterbank',option.fb,... Daniel@0: 'FilterbankType',option.fbtype,... Daniel@0: 'FilterType',option.ftype,... Daniel@0: 'Sum',optionsum,'Method',option.envmeth,... Daniel@0: option.band,'Center',option.c,... Daniel@0: 'HalfwaveCenter',option.chwr,'Diff',option.diff,... Daniel@0: 'HalfwaveDiff',option.diffhwr,'Lambda',option.lambda,... Daniel@0: 'Smooth',option.aver,'Sampling',option.sampling,... Daniel@0: 'Complex',option.complex,'Inc',option.inc,... Daniel@0: 'Median',option.median(1),option.median(2),... Daniel@0: 'Halfwave',option.hw,'Detect',0,... Daniel@0: 'Mu',option.mu,'Log',option.log,... Daniel@0: 'Frame',option.frame.length.val,... Daniel@0: option.frame.length.unit,... Daniel@0: option.frame.hop.val,... Daniel@0: option.frame.hop.unit); Daniel@0: else Daniel@0: x = mironsets(x,option.fea,'Filterbank',option.fb,... Daniel@0: 'FilterbankType',option.fbtype,... Daniel@0: 'FilterType',option.ftype,... Daniel@0: 'Sum',optionsum,'Method',option.envmeth,... Daniel@0: option.band,'Center',option.c,... Daniel@0: 'HalfwaveCenter',option.chwr,'Diff',option.diff,... Daniel@0: 'HalfwaveDiff',option.diffhwr,'Lambda',option.lambda,... Daniel@0: 'Smooth',option.aver,'Sampling',option.sampling,... Daniel@0: 'Complex',option.complex,'Inc',option.inc,... Daniel@0: 'Median',option.median(1),option.median(2),... Daniel@0: 'Halfwave',option.hw,'Detect',0,... Daniel@0: 'Mu',option.mu,'Log',option.log); Daniel@0: end Daniel@0: end Daniel@0: if option.aut == 0 && option.spe == 0 Daniel@0: option.aut = 1; Daniel@0: end Daniel@0: if isamir(x,'mirautocor') || (option.aut && not(option.spe)) Daniel@0: y = mirautocor(x,'Min',60/option.ma,'Max',60/option.mi,... Daniel@0: 'Enhanced',option.enh,...'NormalInput','coeff',... Daniel@0: 'Resonance',option.r,'NormalWindow',option.nw); Daniel@0: elseif isamir(x,'mirspectrum') || (option.spe && not(option.aut)) Daniel@0: y = mirspectrum(x,'Min',option.mi/60,'Max',option.ma/60,... Daniel@0: 'Prod',option.prod,...'NormalInput',... Daniel@0: 'ZeroPad',option.zp,'Resonance',option.r); Daniel@0: elseif option.spe && option.aut Daniel@0: ac = mirautocor(x,'Min',60/option.ma,'Max',60/option.mi,... Daniel@0: 'Enhanced',option.enh,...'NormalInput','coeff',... Daniel@0: 'Resonance',option.r); Daniel@0: sp = mirspectrum(x,'Min',option.mi/60,'Max',option.ma/60,... Daniel@0: 'Prod',option.prod,...'NormalInput',... Daniel@0: 'ZeroPad',option.zp,'Resonance',option.r); Daniel@0: y = ac*sp; Daniel@0: end Daniel@0: if ischar(option.sum) Daniel@0: y = mirsum(y); Daniel@0: end Daniel@0: y = mirpeaks(y,'Total',option.m,'Track',option.track,... Daniel@0: 'Pref',option.pref(1),option.pref(2),... Daniel@0: 'Contrast',option.thr,'NoBegin','NoEnd',... Daniel@0: 'Normalize','Local'); Daniel@0: type = {'mirscalar',mirtype(y)}; Daniel@0: Daniel@0: Daniel@0: %% MAIN Daniel@0: Daniel@0: function o = main(p,option,postoption) Daniel@0: if iscell(p) Daniel@0: p = p{1}; Daniel@0: end Daniel@0: pt = get(p,'TrackPrecisePos'); Daniel@0: track = 1; Daniel@0: if isempty(pt) || isempty(pt{1}) Daniel@0: pt = get(p,'PeakPrecisePos'); Daniel@0: track = 0; Daniel@0: end Daniel@0: bpm = cell(1,length(pt)); Daniel@0: for j = 1:length(pt) Daniel@0: bpm{j} = cell(1,length(pt{j})); Daniel@0: for k = 1:length(pt{j}) Daniel@0: ptk = pt{j}{k}; Daniel@0: bpmk = cell(1,size(ptk,2)); Daniel@0: for h = 1:size(ptk,3) Daniel@0: for l = 1:size(ptk,2) Daniel@0: ptl = ptk{1,l,h}; Daniel@0: if isempty(ptl) Daniel@0: bpmk{1,l,h} = NaN; Daniel@0: else Daniel@0: if isa(p,'mirautocor') && not(get(p,'FreqDomain')) Daniel@0: bpmk{1,l,h} = 60./ptl; Daniel@0: else Daniel@0: bpmk{1,l,h} = ptl*60; Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: if track Daniel@0: bpmk = bpmk{1}; Daniel@0: end Daniel@0: bpm{j}{k} = bpmk; Daniel@0: end Daniel@0: end Daniel@0: t = mirscalar(p,'Data',bpm,'Title','Tempo','Unit','bpm'); Daniel@0: o = {t,p};