v_spgrambw

PURPOSE ^

V_SPGRAMBW Draw spectrogram [T,F,B]=(s,fs,mode,bw,fmax,db,tinc,ann)

SYNOPSIS ^

function [t,f,b]=v_spgrambw(s,fs,varargin)

DESCRIPTION ^

V_SPGRAMBW Draw spectrogram [T,F,B]=(s,fs,mode,bw,fmax,db,tinc,ann)

  Usage: (1) v_spgrambw(s,fs,'pJcw')     % Plot spectrogram with my favourite set of options

         (2) v_spgrambw(s,fs,'PJcwm',50,[100 2000])    % Plot narrow-band spectrogram on mel scale
                                                       from 100 to 2000 mel in power/mel units

         (3)     [t,f,b]=v_spgrambw(s,fs,'p');        % calculate the spectrogram without plotting
                 imagesc(t,f,10*log10(b'));         % plot it manually
                 axis('xy');

         (4) ninc=0.0045*fs;           % Frame increment for BW=200 Hz (in samples)
             nwin=2*ninc;              % Frame length (in samples)
             win=hamming(nwin);        % Analysis window
             k=0.5*fs*sum(win.^2);     % Scale factor to convert to power/Hz
             sf=abs(v_rfft(v_enframe(s,win,ninc),nwin,2)).^2/k;           % Calculate spectrum array                
             v_spgrambw(sf,[fs/ninc 0.5*(nwin+1)/fs fs/nwin],'Jc',bw);  % Plot spectrum array

         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 relative to peak or [dB-min dB-max] for plotting (and B output if 'D' given [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 per x where x depends on the 'pPmbel' options
                        clipped to DB range if 'D' option and in dB if 'd' option.

 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

        'n' = use nearest frequency bin instead of interpolating

        '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 'P' option is given in which case power
 per displayed unit is used or power per decade for the 'l' option.

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

SOURCE CODE ^

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

Generated by m2html © 2003