


SPGRAMBW Draw spectrogram [T,F,B]=(s,fs,mode,bw,fmax,db,tinc,ann)
Usage: spgrambw(s,fs,'pJcw') % Plot spectrogram with my favourite set of options
For examples of the many options available see:
http://www.ee.ic.ac.uk/hp/staff/dmb/voicebox/tutorial/spgrambw/spgram_tut.pdf
Inputs: S speech signal, or single-sided power spectrum array, S(NT,NF), in power per Hz
FS sample fequency (Hz) or [FS T1] where T1 is the time of the first sample
or, if s is a matrix, [FS T1 FINC F1] where FS is the frame rate, T1 is
the time of the first sample, FINC is the frequency increment and F1 the
frequency of the first column.
MODE optional character string specifying options (see list below)
BW bandwidth resolution in Hz (DFT window length = 1.81/BW)[default: 200]
FMAX frequency range [Fmin Fstep Fmax]. If Fstep is omitted
it is taken to be (Fmax-Fmin)/257, if Fmin is also omitted it is taken
to be 0 (or 20Hz for mode l), if all three are omitted Fmax is taken to be FS/2.
If modes m, b, e or l are specified then the units are in mel, bark or erb or
log10(Hz); this can be over-ridden by the 'h' option.
DB either dB-range or [dB-min dB-max] [default: 40]
TINC output frame increment in seconds [0 or missing uses default=0.45/BW]
or [TFIRST TLAST] or [TFIRST TINC TLAST] where TFIRST/TLAST are the times
of first/last frames
ANN annotation cell array: each row contains either
{time 'text-string' 'font'} or {[t_start t_end] 'text-string' 'font'} where
the time value is in seconds with s(n) at time offset+n/fs. The font column can
omitted in which case the system font will be used. MATLAB cannot cope with
unicode so I recommend the SILDoulosIPA (serifed) or SILSophiaIPA (sans) fonts
for phonetic symbols; these are now a little hard to find.
Outputs: T(NT) time axis values (in seconds). Input sample s(n) is at time offset+n/fs.
F(NF) frequency axis values in Hz or, unless mode=H, other selected frequency units
according to mode: m=mel, l=log10(Hz), b=bark,e=erb-rate
B(NT,NF) spectrogram values in power (or clipped dB values if 'd' option given)
MODE: 'p' = output power per decade rather than power per Hz [preemphasis]
'P' = output power per mel/bark/erb according to y axis scaling
'd' = output B array is in dB rather than power
'D' = clip the output B array to the limits specified by the "db" input
'm' = mel scale
'b' = bark scale
'e' = erb scale
'l' = log10 Hz frequency scale
'f' = label frequency axis in Hz rather than mel/bark/...
'h' = units of the FMAX input are in Hz instead of mel/bark
[in this case, the Fstep parameter is used only to determine
the number of filters]
'H' = express the F output in Hz instead of mel/bark/...
'g' = draw a graph even if output arguments are present
'j' = jet colourmap
'J' = "thermal" colourmap that is linear in grayscale. Based on Oliver Woodford's
real2rgb at http://www.mathworks.com/matlabcentral/fileexchange/23342
'i' = inverted colourmap (white background)
'c' = include a colourbar as an intensity scale
'w' = draw the speech waveform above the spectrogram
'a' = centre-align annotations rather than left-aligning them
't' = add time markers with annotations
The BW input gives the 6dB bandwidth of the Hamming window used in the analysis.
Equal amplitude frequency components are guaranteed to give separate peaks if they
are this far apart. This value also determines the time resolution: the window length is
1.81/BW and the low-pass filter applied to amplitude modulations has a 6-dB bandwidth of
BW/2 Hz.
The units are power per Hz unless the u
option is given in which case power per displayed unit is used
or power per decade for the l option.


0001 function [t,f,b]=spgrambw(s,fs,varargin) 0002 %SPGRAMBW Draw spectrogram [T,F,B]=(s,fs,mode,bw,fmax,db,tinc,ann) 0003 % 0004 % Usage: spgrambw(s,fs,'pJcw') % Plot spectrogram with my favourite set of options 0005 % 0006 % For examples of the many options available see: 0007 % http://www.ee.ic.ac.uk/hp/staff/dmb/voicebox/tutorial/spgrambw/spgram_tut.pdf 0008 % 0009 % Inputs: S speech signal, or single-sided power spectrum array, S(NT,NF), in power per Hz 0010 % FS sample fequency (Hz) or [FS T1] where T1 is the time of the first sample 0011 % or, if s is a matrix, [FS T1 FINC F1] where FS is the frame rate, T1 is 0012 % the time of the first sample, FINC is the frequency increment and F1 the 0013 % frequency of the first column. 0014 % MODE optional character string specifying options (see list below) 0015 % BW bandwidth resolution in Hz (DFT window length = 1.81/BW)[default: 200] 0016 % FMAX frequency range [Fmin Fstep Fmax]. If Fstep is omitted 0017 % it is taken to be (Fmax-Fmin)/257, if Fmin is also omitted it is taken 0018 % to be 0 (or 20Hz for mode l), if all three are omitted Fmax is taken to be FS/2. 0019 % If modes m, b, e or l are specified then the units are in mel, bark or erb or 0020 % log10(Hz); this can be over-ridden by the 'h' option. 0021 % DB either dB-range or [dB-min dB-max] [default: 40] 0022 % TINC output frame increment in seconds [0 or missing uses default=0.45/BW] 0023 % or [TFIRST TLAST] or [TFIRST TINC TLAST] where TFIRST/TLAST are the times 0024 % of first/last frames 0025 % ANN annotation cell array: each row contains either 0026 % {time 'text-string' 'font'} or {[t_start t_end] 'text-string' 'font'} where 0027 % the time value is in seconds with s(n) at time offset+n/fs. The font column can 0028 % omitted in which case the system font will be used. MATLAB cannot cope with 0029 % unicode so I recommend the SILDoulosIPA (serifed) or SILSophiaIPA (sans) fonts 0030 % for phonetic symbols; these are now a little hard to find. 0031 % 0032 % Outputs: T(NT) time axis values (in seconds). Input sample s(n) is at time offset+n/fs. 0033 % F(NF) frequency axis values in Hz or, unless mode=H, other selected frequency units 0034 % according to mode: m=mel, l=log10(Hz), b=bark,e=erb-rate 0035 % B(NT,NF) spectrogram values in power (or clipped dB values if 'd' option given) 0036 % 0037 % MODE: 'p' = output power per decade rather than power per Hz [preemphasis] 0038 % 'P' = output power per mel/bark/erb according to y axis scaling 0039 % 'd' = output B array is in dB rather than power 0040 % 'D' = clip the output B array to the limits specified by the "db" input 0041 % 0042 % 'm' = mel scale 0043 % 'b' = bark scale 0044 % 'e' = erb scale 0045 % 'l' = log10 Hz frequency scale 0046 % 'f' = label frequency axis in Hz rather than mel/bark/... 0047 % 0048 % 'h' = units of the FMAX input are in Hz instead of mel/bark 0049 % [in this case, the Fstep parameter is used only to determine 0050 % the number of filters] 0051 % 'H' = express the F output in Hz instead of mel/bark/... 0052 % 0053 % 'g' = draw a graph even if output arguments are present 0054 % 'j' = jet colourmap 0055 % 'J' = "thermal" colourmap that is linear in grayscale. Based on Oliver Woodford's 0056 % real2rgb at http://www.mathworks.com/matlabcentral/fileexchange/23342 0057 % 'i' = inverted colourmap (white background) 0058 % 'c' = include a colourbar as an intensity scale 0059 % 'w' = draw the speech waveform above the spectrogram 0060 % 'a' = centre-align annotations rather than left-aligning them 0061 % 't' = add time markers with annotations 0062 % 0063 % The BW input gives the 6dB bandwidth of the Hamming window used in the analysis. 0064 % Equal amplitude frequency components are guaranteed to give separate peaks if they 0065 % are this far apart. This value also determines the time resolution: the window length is 0066 % 1.81/BW and the low-pass filter applied to amplitude modulations has a 6-dB bandwidth of 0067 % BW/2 Hz. 0068 % 0069 % The units are power per Hz unless the u 0070 % option is given in which case power per displayed unit is used 0071 % or power per decade for the l option. 0072 0073 %%%% BUGS %%%%%% 0074 % * allow ANN rows to be a mixture of intervals and instants 0075 % * allow multiple ANN rows 0076 % * Do not use triangular interpolation if the output frequencies are the same as an FFT 0077 % * Place as many subticks as will fit beyond the last tick with the 'f' option 0078 % * Use a special subtick pattern between ticks that are powers of 10 using the 'f' option 0079 % * Future options: 0080 % ['q' = constant q transform] 0081 % ['k' = add a piano keyboard to the frequency scale] 0082 % ['z' = use a bipolar colourmap for a matrix input with negative values] 0083 0084 % Copyright (C) Mike Brookes 1997-2011 0085 % Version: $Id: spgrambw.m 713 2011-10-16 14:45:43Z dmb $ 0086 % 0087 % VOICEBOX is a MATLAB toolbox for speech processing. 0088 % Home page: http://www.ee.ic.ac.uk/hp/staff/dmb/voicebox/voicebox.html 0089 % 0090 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 0091 % This program is free software; you can redistribute it and/or modify 0092 % it under the terms of the GNU General Public License as published by 0093 % the Free Software Foundation; either version 2 of the License, or 0094 % (at your option) any later version. 0095 % 0096 % This program is distributed in the hope that it will be useful, 0097 % but WITHOUT ANY WARRANTY; without even the implied warranty of 0098 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0099 % GNU General Public License for more details. 0100 % 0101 % You can obtain a copy of the GNU General Public License from 0102 % http://www.gnu.org/copyleft/gpl.html or by writing to 0103 % Free Software Foundation, Inc.,675 Mass Ave, Cambridge, MA 02139, USA. 0104 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 0105 persistent tcmap 0106 if isempty(tcmap) 0107 % modified thermal with better grayscale linearity 0108 tcmap=[ 0 0 0; 7 0 17; 14 0 33; 21 0 50; 29 0 67; 36 0 84; 43 0 100; 50 0 117; 0109 57 0 134; 64 0 150; 72 0 167; 80 3 164; 89 7 156; 97 11 149; 106 15 142; 114 19 134; 0110 123 23 127; 131 27 119; 140 31 112; 149 35 105; 157 39 97; 166 43 90; 174 47 82; 0111 183 51 75; 192 55 68; 200 59 60; 209 63 53; 217 67 45; 226 71 38; 234 75 31; 0112 243 79 23; 252 83 16; 255 88 12; 255 95 12; 255 102 11; 255 109 11; 255 116 10; 0113 255 123 10; 255 130 9; 255 137 9; 255 144 8; 255 151 8; 255 158 7; 255 165 7; 0114 255 172 6; 255 179 6; 255 186 5; 255 193 4; 255 200 4; 255 207 3; 255 214 3; 255 221 2; 0115 255 228 2; 255 235 1; 255 242 1; 255 249 0; 255 252 22; 255 252 55; 255 253 88; 0116 255 253 122; 255 254 155; 255 254 188; 255 255 222; 255 255 255]/255; 0117 end 0118 if nargin<2 0119 error('Usage: SPGRAMBW(s,fs,mode,bw,fmax,db,tinc)'); 0120 end 0121 %SPGRAMBW Draw grey-scale spectrogram [T,F,B]=(s,fs,mode,bw,fmax,db,tinc) 0122 % 0123 % first decode the input arguments 0124 % 0125 if size(s,1)==1 0126 s=s(:); % force to be a column vector (unless it is a matrix) 0127 end 0128 [ns1,ns2]=size(s); 0129 ap=zeros(1,6); 0130 j=2; 0131 if numel(fs)<2 0132 fs(2)=1/fs(1); % first sample or frame is at time 1/fs 0133 end 0134 for i=1:length(varargin) 0135 if ischar(varargin{i}) 0136 ap(1)=i; 0137 else 0138 ap(j)=i; 0139 j=j+1; 0140 end 0141 end 0142 if ap(1) && ~isempty(varargin{ap(1)}) 0143 mode=varargin{ap(1)}; 0144 else 0145 mode=''; % default mode 0146 end 0147 if ap(2) && ~isempty(varargin{ap(2)}) 0148 bw=varargin{ap(2)}; 0149 else 0150 bw=200; 0151 end 0152 if ap(3) && ~isempty(varargin{ap(3)}) 0153 fmax=varargin{ap(3)}; 0154 else 0155 fmax=[]; 0156 end 0157 if ap(4) && ~isempty(varargin{ap(4)}) 0158 db=varargin{ap(4)}; 0159 else 0160 db=40; 0161 end 0162 if ap(5) && ~isempty(varargin{ap(5)}) 0163 tinc=varargin{ap(5)}; 0164 else 0165 tinc=0; 0166 end 0167 switch numel(tinc) 0168 case 1 0169 tinc=[tinc -Inf Inf]; 0170 case 2 0171 tinc=[0 tinc]; 0172 otherwise 0173 tinc=tinc([2 1 3]); 0174 end 0175 if tinc(1)<=0 0176 tinc(1)=1.81/(4*bw); % default frame increment 0177 end 0178 if ap(6) 0179 ann=varargin{ap(6)}; 0180 else 0181 ann=[]; 0182 end 0183 0184 % now sort out the mode flags 0185 0186 mdsw=' '; % [yscale preemph] 0187 for i=1:length(mode) 0188 switch mode(i) 0189 case {'l','m','b','e'} 0190 mdsw(1)=mode(i); 0191 case {'p','P'} 0192 mdsw(2)=mode(i); 0193 end 0194 end 0195 if mdsw(2)=='P' 0196 mdsw(2)=mdsw(1); % preemphasis is scaling dependent 0197 end 0198 % 0199 % sort out the frequency axis 0200 % 0201 flmin=30; % min frequency for 'l' option 0202 nfrq=257; % default number of frequency bins 0203 if ns2==1 0204 fnyq=fs(1)/2; % default upper frequency limit is fs/2 0205 else % input is a power spectrum 0206 if numel(fs)<3 0207 fs(3)=fs(1)*0.25; % default increment is 0.25 times frame increment 0208 end 0209 if numel(fs)<4 0210 fs(4)=0; % first freq bin is DC by default 0211 end 0212 fnyq=fs(4)+(ns2-1)*fs(3); % default upper frequency limit is highest supplied frequency 0213 end 0214 0215 if ~numel(fmax) % no explicit frequency range 0216 switch mdsw(1) 0217 case 'l' 0218 fx=linspace(log10(flmin),log10(fnyq),nfrq); % 20 Hz to Nyquist 0219 case 'm' 0220 fx=linspace(0,frq2mel(fnyq),nfrq); % DC to Nyquist 0221 case 'b' 0222 fx=linspace(0,frq2bark(fnyq),nfrq); % DC to Nyquist 0223 case 'e' 0224 fx=linspace(0,frq2erb(fnyq),nfrq); % DC to Nyquist 0225 otherwise % linear Hz scale 0226 fx=(0:nfrq-1)*fnyq/(nfrq-1); 0227 end 0228 else 0229 if any(mode=='h') 0230 switch mdsw(1) 0231 case 'l' 0232 fmaxu=log10(fmax); % 20 Hz to Nyquist 0233 case 'm' 0234 fmaxu=frq2mel(fmax); % DC to Nyquist 0235 case 'b' 0236 fmaxu=frq2bark(fmax); % DC to Nyquist 0237 case 'e' 0238 fmaxu=frq2erb(fmax); % DC to Nyquist 0239 otherwise 0240 fmaxu=fmax; % linear Hz scale 0241 end 0242 else 0243 fmaxu=fmax; % already in the correct units 0244 end 0245 if numel(fmax)<2 % only max value specified 0246 if mdsw(1)=='l' 0247 fx=linspace(log10(flmin),fmaxu,nfrq); % 20 Hz to fmax 0248 else 0249 fx=linspace(0,fmaxu,nfrq); % DC to fmax 0250 end 0251 elseif numel(fmax)<3 % min and max values specified 0252 fx=linspace(fmaxu(1),fmaxu(2),nfrq); % fmin to fmax 0253 else 0254 fmaxu(2)=fmax(2)*(fmaxu(3)-fmaxu(1))/(fmax(3)-fmax(1)); % scale the step size appropriately 0255 fx=fmaxu(1):fmaxu(2):fmaxu(3); % fmin to fmax in steps of finc 0256 nfrq=length(fx); 0257 end 0258 end 0259 switch mdsw(1) % convert the frequency range to Hz 0260 case 'l' 0261 f=10.^fx; 0262 frlab='log_{10}Hz'; 0263 frlabf='log'; 0264 frq2y=@log10; 0265 y2frq=@(x) 10.^x; 0266 case 'm' 0267 f=mel2frq(fx); 0268 frlab='Mel'; 0269 frlabf='Mel'; 0270 frq2y=@frq2mel; 0271 y2frq=@mel2frq; 0272 case 'b' 0273 f=bark2frq(fx); 0274 frlab='Bark'; 0275 frlabf='Bark'; 0276 frq2y=@frq2bark; 0277 y2frq=@bark2frq; 0278 case 'e' 0279 f=erb2frq(fx); 0280 frlab='Erb-rate'; 0281 frlabf='Erb'; 0282 frq2y=@frq2erb; 0283 y2frq=@erb2frq; 0284 otherwise 0285 f=fx; 0286 frlab='Hz'; 0287 frq2y=@(x) x; 0288 y2frq=@(x) x; 0289 end 0290 if ~any(mode=='H') 0291 f=fx; % give output frequencies in native units instead of Hz unless 'H' is specified 0292 end 0293 % 0294 % now calculate the spectrogram 0295 % 0296 if ns2==1 % input is a speech signal vector 0297 winlen = fix(1.81*fs(1)/bw); % window length 0298 win=0.54+0.46*cos((1-winlen:2:winlen)*pi/winlen); % Hamming window 0299 ninc=max(round(tinc(1)*fs(1)),1); % window increment in samples 0300 % we need to take account of minimum freq increment + make it exact if possible 0301 fftlen=pow2(nextpow2(4*winlen)); % enough oversampling to get good interpolation 0302 win=win/sqrt(sum(win.^2)); % ensure window squared sums to unity 0303 ix1=max(round((tinc(2)-fs(2))*fs(1)-(winlen-3)/2),1); % first sample required 0304 ix2=min(ceil((tinc(3)-fs(2))*fs(1)+(winlen+1)/2),ns1); % last sample required 0305 [sf,t]=enframe(s(ix1:ix2),win,ninc); 0306 t=fs(2)+(t+ix1-2)/fs(1); % time axis 0307 b=rfft(sf,fftlen,2); 0308 b=b.*conj(b)*2/fs(1); % Power per Hz 0309 b(:,1)=b(:,1)*0.5; % correct for no negative zero frequency to double the power 0310 b(:,end)=b(:,end)*0.5; % correct for no negative nyquist frequency to double the power 0311 fb=(0:fftlen/2)*fs(1)/fftlen; % fft bin frequencies 0312 fftfs=fs(1); 0313 else 0314 0315 b=s; 0316 t=fs(2)+(0:ns1-1)/fs(1); % frame times 0317 fb=fs(4)+(0:ns2-1)*fs(3); 0318 fftlen=[ns2 fs(3) fs(4)]; % for filtbankm: ns2=# input freq bins, freq increment (Hz), first bin freq (Hz) 0319 fftfs=0; 0320 % fftlen=2*(ns2-1); % assume an even length fft 0321 % fftfs=fftlen*fs(3); 0322 end 0323 nfr=numel(t); % number of frames 0324 dblab='Power/Hz'; 0325 switch mdsw(2) 0326 case {'p','l'} 0327 b=b.*repmat(fb*log(10),nfr,1); % convert to power per decade 0328 dblab='Power/Decade'; 0329 case 'm' 0330 b=b.*repmat((1+fb/700)*log(1+1000/700)/1000,nfr,1); % convert to power per mel 0331 dblab='Power/Mel'; 0332 case 'b' 0333 b=b.*repmat((1960+fb).^2/52547.6,nfr,1); % convert to power per bark 0334 dblab='Power/Bark'; 0335 case 'e' 0336 b=b.*repmat(6.23*fb.^2 + 93.39*fb + 28.52,nfr,1); % convert to power per erb 0337 dblab='Power/Erb-rate'; 0338 end 0339 % 0340 % Now map onto the desired frequency scale 0341 % 0342 b=b*filtbankm(nfrq,fftlen,fftfs,fx(1),fx(end),['cush' mdsw(1)])'; 0343 0344 if ~nargout || any(mode=='g') || any(mode=='d') 0345 if numel(db)<2 % find clipping limits 0346 plim=max(b(:))*[0.1^(0.1*db) 1]; 0347 else 0348 plim=10.^(0.1*db(1:2)); 0349 end 0350 if plim(2)<=0 0351 plim(2)=1; 0352 end 0353 if plim(1)<=0 || plim(1)==plim(2) 0354 plim(1)=0.1*plim(2); 0355 end 0356 if ~nargout || any(mode=='g') 0357 bd=10*log10(b); % save an unclipped log version for plotting 0358 end 0359 if any(mode=='D') 0360 b=min(max(b,plim(1)),plim(2)); % clip the output 0361 end 0362 if any(mode=='d') 0363 b=10*log10(b); % output the dB version 0364 end 0365 end 0366 % now plot things 0367 if ~nargout || any(mode=='g') 0368 cla; % clear current axis 0369 imagesc(t,fx,bd'); 0370 axis('xy'); 0371 set(gca,'tickdir','out','clim',10*log10(plim)); 0372 if any(mode=='j') 0373 colormap('jet'); 0374 map=colormap; 0375 elseif any(mode=='J') 0376 map=tcmap; 0377 else 0378 map = repmat((0:63)'/63,1,3); 0379 end 0380 if any(mode=='i') % 'i' option = invert the colourmap 0381 map=map(64:-1:1,:); 0382 end 0383 colormap(map); 0384 if any(mode=='c') % 'c' option = show a colourbar 0385 colorbar; 0386 cblabel([dblab ' (dB)']); 0387 end 0388 % 0389 % Now check if annotations or a waveform are required 0390 % 0391 dotaw=[((any(mode=='t') && size(ann,2)>1) || size(ann,2)==1) size(ann,2)>1 (any(mode=='w') && ns2==1)]; 0392 ylim=get(gca,'ylim'); 0393 if any(dotaw) 0394 yrange = ylim(2)-ylim(1); 0395 zlim=ylim; 0396 toptaw=cumsum([0 dotaw.*[0.05 0.05 0.1]]*yrange)+ylim(2); 0397 zlim(2)=toptaw(4); 0398 set(gca,'ylim',zlim,'color',map(1,:)); 0399 if dotaw(3) % Plot the waveform 0400 smax=max(s(:)); 0401 smin=min(s(:)); 0402 srange=smax-smin; 0403 hold on 0404 plot(fs(2)+(0:length(s)-1)/fs(1),(s-smin)/srange*0.9*(toptaw(4)-toptaw(3))+toptaw(3),'color',map(48,:)) 0405 hold off 0406 end 0407 if dotaw(1) || dotaw(2) 0408 tmk=cell2mat(ann(:,1)); 0409 tmksel=tmk(:,1)<=t(end) & tmk(:,end)>=t(1); 0410 yix=1+[tmk(tmksel,1)<t(1) ones(sum(tmksel),2) tmk(tmksel,end)>t(end)]'; 0411 tmk(:,1)=max(tmk(:,1),t(1)); % clip to axis limits 0412 tmk(:,end)=min(tmk(:,end),t(end)); 0413 end 0414 if dotaw(1) && any(tmksel) % draw time markers 0415 ymk=toptaw(1:2)*[0.8 0.4;0.2 0.6]; 0416 switch size(tmk,2) 0417 case 0 0418 case 1 % isolated marks 0419 hold on 0420 plot([tmk(tmksel) tmk(tmksel)]',repmat(ymk',1,sum(tmksel)),'color',map(48,:)); 0421 hold off 0422 otherwise % draw durations 0423 0424 hold on 0425 plot(tmk(tmksel,[1 1 2 2])',ymk(yix),'color',map(48,:)); 0426 hold off 0427 end 0428 end 0429 if dotaw(2) && any(tmksel) % print annotations 0430 if any(mode=='a') 0431 horal='center'; 0432 tmk=(tmk(:,1)+tmk(:,end))*0.5; 0433 else 0434 horal='left'; 0435 tmk=tmk(:,1); 0436 end 0437 if size(ann,2)>2 0438 font='Arial'; 0439 for i=1:size(ann,1) 0440 if tmksel(i) 0441 if ~isempty(ann{i,3}) 0442 font = ann{i,3}; 0443 end 0444 text(tmk(i),toptaw(2),ann{i,2},'color',map(48,:),'fontname',font,'VerticalAlignment','baseline','HorizontalAlignment',horal); 0445 end 0446 end 0447 else 0448 for i=1:size(ann,1) 0449 if tmksel(i) 0450 text(tmk(i),toptaw(2),ann{i,2},'color',map(48,:),'VerticalAlignment','baseline','HorizontalAlignment',horal); 0451 end 0452 end 0453 end 0454 end 0455 end 0456 xlabel(['Time (' xticksi 's)']); 0457 if any(mode=='f') && ~strcmp(frlab,'Hz') 0458 ylabel([frlabf '-scaled frequency (Hz)']); 0459 ytickhz(frq2y,y2frq); 0460 else 0461 ylabel(['Frequency (' yticksi frlab ')']); 0462 end 0463 ytick=get(gca,'YTick'); 0464 ytickl=get(gca,'YTickLabel'); 0465 msk=ytick<=ylim(2); 0466 if any(~msk) 0467 set(gca,'YTick',ytick(msk),'YTickLabel',ytickl(msk)); 0468 end 0469 end 0470 0471 function ytickhz(frq2y,y2frq) 0472 % label non linear y frequency axis 0473 % 0474 % Bugs/Suggestions: 0475 % * Add a penalty for large numbers (e.g. 94 is less "round" than 11) 0476 % * possibly add subticks at 1:2:5 if boundaries are 1 and 10 0477 % * could treat subtick allocation specially if bounding lables are both powers of 10 0478 % and work in log spacing rather than spacing directly 0479 0480 % algorithm constants 0481 0482 seps=[0.4 1 3 6]; % spacings: (a) min subtick, (b) min tick, (c) min good tick, (d) max good tick 0483 ww=[0.5 0.6 0.8 0.1 0.3 0.3 0.2]; % weight for (a) last digit=5, (b) power of 10, (c) power of 1000, (d) equal spacing, (e) 1:2:5 labels (f) <seps(3) (g) >seps(4) 0484 nbest=10; % number of possibilities to track 0485 0486 prefix={'y','z','a','f','p','n','µ','m','','k','M','G','T','P','E','Z','Y'}; 0487 0488 ah=gca; 0489 getgca=get(ah); % Get original axis properties 0490 set(ah,'Units','points','FontUnits','points'); 0491 getgcac=get(ah); % Get axis properties in points units 0492 set(ah,'Units',getgca.Units,'FontUnits',getgca.FontUnits); % return to original values 0493 ylim=getgca.YLim; 0494 yrange=ylim*[-1;1]; 0495 chsz= yrange*getgcac.FontSize/getgcac.Position(4); % char height in Y-units 0496 % divide the y-axis up into bins containing at most one label each 0497 maxl=ceil(2*yrange/chsz); % max number of labels 0498 0499 % candidate array [cand(:,[1 2])/1000 cand(:,5) cand(:,6)/1000 cand(:,[7 8])] 0500 % 1,2=y limits, 3,4=log limits, 5=Hz, 6=cost, 7=mantissa, 8=exponent, 9=sig digits, 10=y-position 0501 cand=zeros(maxl+2,10); 0502 yinc=(yrange+chsz*0.0002)/maxl; % bin spacing (allowing for a tiny bit to ensure the ends are included) 0503 cand(2:end-1,2)=ylim(1)+yinc*(1:maxl)'-chsz*0.0001; 0504 cand(3:end-1,1)=cand(2:end-2,2); 0505 cand(2,1)=cand(2,2)-yinc; 0506 cand(2:end-1,1:2)=y2frq(max(cand(2:end-1,1:2),0)); 0507 0508 % find the "roundest" number in each interval 0509 % first deal with intervals containing zero 0510 cand([1 maxl+2],6)=-1; 0511 cand(2,9)=(cand(2,1)<=0); % mask out interval contaiing zero 0512 cand(2,6)=-cand(2,9); 0513 msk=cand(:,6)==0; % find rows without a cost yet 0514 cand(msk,3:4)=log10(cand(msk,1:2)); 0515 % find powers of 1000 0516 loglim=ceil(cand(:,3:4)/3); 0517 msk=loglim(:,2)>loglim(:,1); 0518 if any(msk) 0519 xp=loglim(msk,1); 0520 wuns=ones(length(xp),1); 0521 cand(msk,5:9)=[1000.^xp wuns-ww(3) wuns 3*xp wuns]; 0522 end 0523 % find powers of 10 0524 loglim=ceil(cand(:,3:4)); 0525 msk=~msk & (loglim(:,2)>loglim(:,1)); 0526 if any(msk) 0527 xp=loglim(msk,1); 0528 wuns=ones(length(xp),1); 0529 cand(msk,5:9)=[10.^xp wuns-ww(2) wuns xp wuns]; 0530 end 0531 % find value with fewest digits 0532 msk=cand(:,6)==0; % find rows without a cost yet 0533 maxsig=1-floor(log10(10^min(cand(msk,3:4)*[-1;1])-1)); % maximum number of significant figures to consider 0534 pten=10.^(0:maxsig-1); % row vector of powers of ten 0535 noten=10.^(-floor(cand(msk,3))); % exponent of floating point representation of lower bound 0536 sigdig=sum((ceil(cand(msk,2).*noten*pten)-ceil(cand(msk,1).*noten*pten))==0,2); % number of digits common to the interval bounds 0537 lowman=ceil(cand(msk,1).*noten.*10.^sigdig); 0538 midman=10*floor(lowman/10)+5; 0539 highman=ceil(cand(msk,2).*noten.*10.^sigdig); 0540 mskman=midman>=lowman & midman<highman; % check if we can include a manitssa ending in 5 0541 lowman(mskman)=midman(mskman); 0542 cand(msk,6:9)=[sigdig+1 lowman floor(cand(msk,3))-sigdig sigdig+1]; 0543 cand(msk,5)=cand(msk,7).*10.^cand(msk,8); 0544 cand(msk,6)=cand(msk,6)-(mod(cand(msk,7),10)==5)*ww(1); 0545 cand(2:end-1,10)=frq2y(cand(2:end-1,5)); 0546 cand([1 maxl+2],10)=ylim + seps(4)*chsz*[-1 1]; % put imaginary labels at the optimum spacing beyond the axes 0547 % [cand(:,[1 2 5])/1000 cand(:,[6 7 8 9])] 0548 0549 % Now do n-best DP to find the best sequence 0550 0551 ratint=[8/5 25/10 0 0 4/3]; 0552 costs=repmat(Inf,nbest,maxl+2); % cumulative path costs 0553 costs(1,1)=0; % starting node only has one option 0554 prev=ones(nbest,maxl+2); % previous label in path 0555 labcnt=zeros(nbest,maxl+2); % number of labels in path 0556 for i=2:maxl+2 0557 ntry=nbest*(i-1); % number of previous options 0558 prevc=reshape(repmat(1:i-1,nbest,1),ntry,1); % previous candidate 0559 prevprev=1+floor((prev(1:ntry)'-1)/nbest); % previous previous candidate 0560 msk=prevprev>1+(maxl+2)*(i==maxl+2); % mask for label triplets 0561 labcnti=labcnt(1:ntry)+1; 0562 disti=(cand(i,10)-cand(prevc,10))/chsz; % distance to previous label in characters 0563 costa=max(seps(3)-disti,0)*ww(6)+max(disti-seps(4),0)*ww(7); 0564 incri=(cand(i,5)-cand(prevc,5)); % label increment 0565 incrj=(cand(i,5)-cand(prevprev,5)); % double label increment 0566 if any(msk) 0567 costa(msk)=costa(msk)- ww(4)*(abs(incrj(msk)-2*incri(msk))<0.01*incri(msk)); 0568 if cand(i,7)==1 || cand(i,7)==2 || cand(i,7)==5 % look for labels 1:2:5 0569 costa(msk)=costa(msk)- ww(5)*(abs(incrj(msk)-ratint(cand(i,7))*incri(msk))<0.01*incri(msk)); 0570 end 0571 end 0572 costa(disti<seps(2))=Inf; 0573 costi=(costs(1:ntry).*max(labcnt(1:ntry),1)+costa'+cand(i,6))./labcnti; 0574 [sc,isc]=sort(costi); 0575 isc=isc(1:nbest); 0576 costs(:,i)=sc(1:nbest)'; 0577 prev(:,i)=isc'; 0578 labcnt(:,i)=labcnti(isc)'; 0579 end 0580 0581 % now traceback the best sequence 0582 0583 % fprintf('Traceback\n\n'); 0584 ichoose=0; 0585 labchoose=[]; 0586 for i=1:nbest 0587 if labcnt(i,maxl+2)>1 && costs(i,maxl+2)<Inf 0588 lablist=zeros(labcnt(i,maxl+2)-1,1); 0589 k=prev(i,maxl+2); 0590 for j=labcnt(i,maxl+2)-1:-1:1 0591 lablist(j)=1+floor((k-1)/nbest); 0592 k=prev(k); 0593 end 0594 % fprintf('Cost=%8.2f :',costs(i,maxl+2)); 0595 % fprintf(' %g',cand(lablist,5)) 0596 % fprintf('\n'); 0597 if ~ichoose || labcnt(ichoose,maxl+2)==1 0598 ichoose=i; 0599 labchoose=lablist; 0600 end 0601 end 0602 end 0603 0604 % now create the labels 0605 0606 ntick=length(labchoose); 0607 % sort out the subticks 0608 subpos=[]; 0609 if ntick>=2 0610 for i=1:ntick-1 0611 clj=cand(labchoose(i:i+1),:); 0612 sprec=min(clj(1,8)+100*(clj(1,7)==0),clj(2,8)); % subtick precision 0613 spos=(clj(1,7)*10^(clj(1,8)-sprec):clj(2,7)*10^(clj(2,8)-sprec))*10^sprec; 0614 nsub=length(spos); 0615 if nsub==2 0616 spos=spos*[1 0.5 0;0 0.5 1]; 0617 nsub=3; 0618 end 0619 if nsub>=3 0620 yspos=frq2y(spos); 0621 for kk=1:3 % try various subdivisions: every 1, 2 or 5 0622 k=kk+2*(kk==3); % 1, 2 and 5 0623 if 2*k<=nsub-1 && ~mod(nsub-1,k) % must divide exactly into nsub 0624 if all((yspos(1+k:k:nsub)-yspos(1:k:nsub-k))>=(seps(1)*chsz)) % check they all fit in 0625 subpos=[subpos yspos(1+k:k:nsub-k)]; 0626 if i==1 0627 spos=(ceil(cand(2,1)/10^sprec):clj(1,7)*10^(clj(1,8)-sprec))*10^sprec; 0628 nsub=length(spos); 0629 yspos=frq2y(spos); 0630 if nsub>=k+1 && all((yspos(nsub:-k:1+k)-yspos(nsub-k:-k:1))>=(seps(1)*chsz)) 0631 subpos=[subpos yspos(nsub-k:-k:1)]; 0632 end 0633 elseif i==ntick-1 0634 spos=(clj(2,7)*10^(clj(2,8)-sprec):floor(cand(end-1,2)/10^sprec))*10^sprec; 0635 nsub=length(spos); 0636 yspos=frq2y(spos); 0637 if nsub>=k+1 && all((yspos(1+k:k:nsub)-yspos(1:k:nsub-k))>=(seps(1)*chsz)) 0638 subpos=[subpos yspos(1+k:k:nsub)]; 0639 end 0640 end 0641 break; 0642 end 0643 end 0644 end 0645 end 0646 end 0647 end 0648 nsub=length(subpos); 0649 tickpos=[cand(labchoose,10); subpos']; 0650 ticklab=cell(ntick+nsub,1); 0651 sipref=min(max(floor((sum(cand(labchoose,8:9),2)-1)/3),-8),8); 0652 nzadd=cand(labchoose,8)-3*sipref; % trailing zeros to add 0653 digzer=cand(labchoose,7).*10.^max(nzadd,0); % label digits including trailing zeros 0654 ndleft=cand(labchoose,9)+nzadd; % digits to the left of the decimal point 0655 for i=1:ntick 0656 tickint=num2str(digzer(i)); 0657 if nzadd(i)<0 0658 tickint=[tickint(1:ndleft(i)) '.' tickint(1+ndleft(i):end)]; 0659 end 0660 ticklab{i} = sprintf('%s%s',tickint,prefix{sipref(i)+9}); 0661 end 0662 for i=ntick+1:ntick+nsub 0663 ticklab{i}=''; 0664 end 0665 [tickpos,ix]=sort(tickpos); 0666 ticklab=ticklab(ix); 0667 0668 set(ah,'YTick',tickpos','YTickLabel',ticklab); 0669