├── .gitignore ├── +iosr ├── +statistics │ ├── boxPlot.m │ ├── trirnd.m │ ├── laprnd.m │ ├── getRmse.m │ ├── kernelDensity.m │ ├── tab2box.m │ └── qqPlot.m ├── +auditory │ ├── erbRate2hz.m │ ├── hz2erbRate.m │ ├── makeErbCFs.m │ ├── freqMulti.m │ ├── azimuth2itd.m │ ├── itd2azimuth.m │ ├── calcIld.m │ ├── meddisHairCell.m │ ├── dupWeight.m │ ├── binSearch.m │ ├── lindemannInh.m │ ├── xcorrLindemann.m │ ├── gammatoneFast.m │ ├── loudWeight.m │ ├── xcorrLindemann_c.c │ ├── chXcorr2.m │ ├── chXcorr2_c.c │ ├── createWindow.m │ └── iso226.m ├── +dsp │ ├── rcoswin.m │ ├── rms.m │ ├── lapwin.m │ ├── autocorr.m │ ├── localpeaks.m │ ├── convFft.m │ ├── istft.m │ ├── vsmooth.m │ ├── smoothSpectrum.m │ ├── sincFilter.m │ ├── stft.m │ ├── ltas.m │ └── audio.m ├── +bss │ ├── calcImr.m │ ├── calcSnr.m │ ├── cfs2fcs.m │ ├── idealMasks.m │ ├── applyMask.m │ ├── getFullMask.m │ ├── example.m │ ├── applyIdealMasks.m │ ├── resynthesise.m │ └── source.m ├── +figures │ ├── chMap.m │ ├── cmrMap.m │ └── subfigrid.m ├── +acoustics │ └── rtEst.m ├── +general │ ├── cell2csv.m │ ├── urn.m │ ├── checkMexCompiled.m │ ├── updateContents.m │ └── getContents.m ├── install.m ├── +svn │ ├── readSvnKeyword.m │ ├── headRev.m │ └── buildSvnProfile.m └── Contents.m ├── LICENSE ├── README.md └── release-notes.md /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled binary 2 | *.mex* 3 | 4 | # temp files 5 | *.m~ 6 | *.c~ 7 | -------------------------------------------------------------------------------- /+iosr/+statistics/boxPlot.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoSR-Surrey/MatlabToolbox/HEAD/+iosr/+statistics/boxPlot.m -------------------------------------------------------------------------------- /+iosr/+auditory/erbRate2hz.m: -------------------------------------------------------------------------------- 1 | function y=erbRate2hz(x) 2 | %ERBRATE2HZ Convert ERB rate to Hz. 3 | % 4 | % Y = IOSR.AUDITORY.ERBRATE2HZ(X) converts the ERB number X to the 5 | % eqivalent frequency Y (in Hz). 6 | % 7 | % See also IOSR.AUDITORY.HZ2ERBRATE. 8 | 9 | % Copyright 2016 University of Surrey. 10 | 11 | y=(10.^(x/21.4)-1)/4.37e-3; 12 | 13 | end 14 | -------------------------------------------------------------------------------- /+iosr/+auditory/hz2erbRate.m: -------------------------------------------------------------------------------- 1 | function y=hz2erbRate(x) 2 | %HZ2ERBRATE Convert Hz to ERB rate 3 | % 4 | % Y = IOSR.AUDITORY.HZ2ERBRATE(X) converts the frequency X (in Hz) to the 5 | % eqivalent ERB number Y. 6 | % 7 | % See also IOSR.AUDITORY.ERBRATE2HZ, IOSR.AUDITORY.MAKEERBCFS. 8 | 9 | % Copyright 2016 University of Surrey. 10 | 11 | y=(21.4*log10(4.37e-3*x+1)); 12 | 13 | end 14 | -------------------------------------------------------------------------------- /+iosr/+dsp/rcoswin.m: -------------------------------------------------------------------------------- 1 | function w = rcoswin(N) 2 | %RCOSWIN Raised cosine window. 3 | % 4 | % IOSR.DSP.RCOSWIN(N) returns an N-point raised cosine window. 5 | % 6 | % See also GAUSSWIN, CHEBWIN, KAISER, TUKEYWIN, WINDOW. 7 | 8 | % Copyright 2016 University of Surrey. 9 | 10 | assert(isscalar(N) && round(N)==N, 'iosr:rcoswin:invalidN', 'N must be a scalar and whole number.') 11 | assert(N > 0, 'iosr:rcoswin:invalidN', 'N must be greater than 0.') 12 | 13 | switch N 14 | case 1 15 | w = 1; 16 | case 2 17 | w = [.5; .5]; 18 | otherwise 19 | r = linspace(-pi,pi,N)'; 20 | w = (cos(r)+1)/2; 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /+iosr/+auditory/makeErbCFs.m: -------------------------------------------------------------------------------- 1 | function cfs = makeErbCFs(mincf,maxcf,numchans) 2 | %MAKEERBCFS Make a series of center frequencies equally spaced in ERB-rate. 3 | % 4 | % This function makes a vector of center frequenies equally spaced on the 5 | % ERB-rate scale. 6 | % 7 | % CFS = IOSR.AUDITORY.MAKEERBCFS(MINCF,MAXCF,NUMCHANS) creates NUMCHANS 8 | % centre frequencies between MINCF and MAXCF. 9 | % 10 | % Adapted from code written by: Guy Brown, University of Sheffield, and 11 | % Martin Cooke. 12 | % 13 | % See also IOSR.AUDITORY.ERBRATE2HZ, IOSR.AUDITORY.HZ2ERBRATE. 14 | 15 | % Copyright 2016 University of Surrey. 16 | 17 | cfs = iosr.auditory.erbRate2hz(... 18 | linspace(iosr.auditory.hz2erbRate(mincf),... 19 | iosr.auditory.hz2erbRate(maxcf),numchans)); 20 | 21 | end 22 | -------------------------------------------------------------------------------- /+iosr/+dsp/rms.m: -------------------------------------------------------------------------------- 1 | function rms = rms(x,dim) 2 | %RMS Calculate the rms of a vector or matrix 3 | % 4 | % RMS = IOSR.DSP.RMS(X) calculates the Root Mean Square of X along the 5 | % first non-singleton dimension of vector or matrix X. For vectors, 6 | % IOSR.DSP.RMS(X) is the RMS value of the elements in x. For matrices, 7 | % IOSR.DSP.RMS(X) is a row vector containing the RMS value of each 8 | % column. For N-D arrays, IOSR.DSP.RMS(X) is the RMS value of the 9 | % elements along the first non-singleton dimension of X. 10 | % 11 | % RMS = IOSR.DSP.RMS(X,DIM) calculates the RMS of X along the dimension 12 | % DIM. 13 | 14 | % Copyright 2016 University of Surrey. 15 | 16 | if nargin == 1 17 | dim = find(size(x)~=1,1,'first'); 18 | end 19 | 20 | rms = sqrt(mean(x.^2,dim)); 21 | 22 | end 23 | -------------------------------------------------------------------------------- /+iosr/+auditory/freqMulti.m: -------------------------------------------------------------------------------- 1 | function PI = freqMulti(f) 2 | %FREQMULTI Calculate frequency coefficient for ITD-azimuth warping 3 | % 4 | % PI = IOSR.AUDITORY.FREQMULTI(F) calculates the coefficient PI for 5 | % frequency F for use in converting between ITD and azimuth in Kuhn's 6 | % model [1]. 7 | % 8 | % References 9 | % 10 | % [1] Kuhn, G.F. (1977), Model for the interaural time differences in the 11 | % azimuthal plane, The Journal of the Acoustical Society of America 12 | % 62, 1, 157-167. 13 | % 14 | % See also IOSR.AUDITORY.AZIMUTH2ITD, IOSR.AUDITORY.ITD2AZIMUTH. 15 | 16 | % Copyright 2016 University of Surrey. 17 | 18 | PI = zeros(size(f)); 19 | PI(f<=500) = 3; 20 | PI(f>=3000) = 2; 21 | IX = f>500 & f<3000; 22 | PI(IX) = 2.5+0.5.*cos(pi.*((log2((sqrt(6).*f(IX))./1250))./(log2(6)))); 23 | 24 | end 25 | -------------------------------------------------------------------------------- /+iosr/+dsp/lapwin.m: -------------------------------------------------------------------------------- 1 | function w = lapwin(L,b) 2 | %LAPWIN Laplace window. 3 | % 4 | % IOSR.DSP.LAPWIN(N) returns an N-point Laplace window. The window w(x) is 5 | % calculated for -10<=x<=10. 6 | % 7 | % IOSR.DSP.LAPWIN(N,B) returns an N-point Laplace window using scale parameter B. 8 | % If omitted, B is 2. 9 | % 10 | % See also GAUSSWIN, CHEBWIN, KAISER, TUKEYWIN, WINDOW. 11 | 12 | % Copyright 2016 University of Surrey. 13 | 14 | assert(isscalar(L) && round(L)==L, 'iosr:lapwin:invalidL', 'L must be a scalar and whole number.') 15 | 16 | if nargin<2 17 | b = 2; 18 | else 19 | assert(isscalar(b), 'iosr:lapwin:invalidB', 'b must be a scalar.') 20 | end 21 | 22 | w = zeros(L,1); 23 | N = L-1; 24 | x = 10.*((0:N)'-N/2)./(N/2); 25 | w(x<0) = exp(-(-x(x<0)./b)); 26 | w(x>=0) = exp(-(x(x>=0)./b)); 27 | 28 | end 29 | -------------------------------------------------------------------------------- /+iosr/+auditory/azimuth2itd.m: -------------------------------------------------------------------------------- 1 | function itd = azimuth2itd(azimuth,f) 2 | %AZIMUTH2ITD Convert azimuth in degrees to ITD 3 | % 4 | % ITD = IOSR.AUDITORY.AZIMUTH2ITD(AZIMUTH,F) converts the azimuth AZIMUTH 5 | % in degrees at frequency F to interaural time difference according to 6 | % Kuhn's model [1]. 7 | % 8 | % References 9 | % 10 | % [1] Kuhn, G.F. (1977), Model for the interaural time differences in the 11 | % azimuthal plane, The Journal of the Acoustical Society of America 12 | % 62, 1, 157-167. 13 | % 14 | % See also IOSR.AUDITORY.ITD2AZIMUTH, IOSR.AUDITORY.FREQMULTI. 15 | 16 | % Copyright 2016 University of Surrey. 17 | 18 | assert(isnumeric(azimuth), 'iosr:azimuth2itd:invalidInput', 'AZIMUTH must be numeric') 19 | assert((isscalar(f) | isscalar(azimuth)) | numel(f)==numel(azimuth),... 20 | 'iosr:azimuth2itd:invalidInput', ... 21 | 'F or ITD must be a scalar, or F and AZIMUTH must be the same size') 22 | 23 | czero = 344; 24 | itd = (iosr.auditory.freqMulti(f).*0.091.*sind(azimuth))./czero; 25 | 26 | end 27 | -------------------------------------------------------------------------------- /+iosr/+dsp/autocorr.m: -------------------------------------------------------------------------------- 1 | function a = autocorr(x,Q,dim) 2 | %AUTOCORR Perform autocorrelation via FFT 3 | % 4 | % IOSR.DSP.AUTOCORR(X) performs autocorrelation via FFT on vector X. For 5 | % matrices, the autocorrelation is performed on each column. For N-D 6 | % arrays, the autocorrelation is performed on the first non-singleton 7 | % dimension. 8 | % 9 | % IOSR.DSP.AUTOCORR(X,Q) allows the sharpness of the autocorrelation to 10 | % be controlled. Q=2 will give a true autocorrelation; smaller values 11 | % will lead to sharper peaks. 12 | % 13 | % IOSR.DSP.AUTOCORR(X,[],DIM) or autocorr(X,Q,DIM) performs the 14 | % autocorrelation along the dimension DIM. 15 | % 16 | % See also XCORR. 17 | 18 | % Copyright 2016 University of Surrey. 19 | 20 | if nargin < 2 || isempty(Q) 21 | Q = 2; 22 | end 23 | 24 | if nargin < 3 25 | nsdim = find(size(x)>1,1,'first'); 26 | if isempty(nsdim) 27 | dim = 1; 28 | else 29 | dim = nsdim; 30 | end 31 | end 32 | 33 | a = ifft(abs(fft(x,[],dim)).^Q,[],dim); 34 | 35 | end 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Institute of Sound Recording 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /+iosr/+auditory/itd2azimuth.m: -------------------------------------------------------------------------------- 1 | function azimuth = itd2azimuth(itd,f) 2 | %ITD2AZIMUTH Convert ITD to azimuth 3 | % 4 | % AZIMUTH = IOSR.AUDITORY.ITD2AZIMUTH(ITD,F) converts the interaural time 5 | % difference ITD (in seconds) at frequency F (in Hz) to AZIMUTH (in 6 | % degrees) according to Kuhn's model [1]. 7 | % 8 | % References 9 | % 10 | % [1] Kuhn, G.F. (1977), Model for the interaural time differences in the 11 | % azimuthal plane, The Journal of the Acoustical Society of America 12 | % 62, 1, 157-167. 13 | % 14 | % See also IOSR.AUDITORY.AZIMUTH2ITD, IOSR.AUDITORY.FREQMULTI. 15 | 16 | % Copyright 2016 University of Surrey. 17 | 18 | assert(isnumeric(itd), 'iosr:itd2azimuth:invalidItd', 'ITD must be numeric') 19 | assert((isscalar(f) | isscalar(itd)) | numel(f)==numel(itd),... 20 | 'iosr:itd2azimuth:invalidInputs', ... 21 | 'F or ITD must be a scalar, or F and ITD must be the same size') 22 | 23 | % Check sanity of ITDs 24 | assert(all(itd<=iosr.auditory.azimuth2itd(90,f)),... 25 | 'iosr:itd2azimuth:invalidItd', ... 26 | 'ITDs greater than maximum ITD [=iosr.auditory.azimuth2itd(90,f)] have been specified.') 27 | 28 | czero = 344; 29 | azimuth = asind((itd.*czero)./(iosr.auditory.freqMulti(f).*0.091)); 30 | 31 | end 32 | -------------------------------------------------------------------------------- /+iosr/+bss/calcImr.m: -------------------------------------------------------------------------------- 1 | function IMR = calcImr(m,im) 2 | %CALCIMR Calculates the Ideal Mask Ratio (IMR) 3 | % 4 | % IMR = IOSR.BSS.CALCIMR(M,IM) calculates the ideal mask ratio (IMR) from 5 | % the calculated mask M and ideal mask IM. Masks can be logical or 6 | % double, but must only contain values in the interval [0,1]. 7 | 8 | % Copyright 2016 University of Surrey. 9 | 10 | % check input 11 | assert(nargin>1, 'iosr:calcImr:invalidInput', 'You need 2 masks to calculate IMR!') 12 | assert(size(m,1)>1 & size(m,2)>1, 'iosr:calcImr:invalidMask', 'm must be a two-dimensional matrix') 13 | assert(size(im,1)>1 & size(im,2)>1, 'iosr:calcImr:invalidMask', 'im must be a two-dimensional matrix') 14 | assert(all(size(m)==size(im)), 'iosr:calcImr:invalidMask', 'Masks must be the same size!') 15 | 16 | % validate masks 17 | m = check_mask(m); 18 | im = check_mask(im); 19 | 20 | % calculate IMR 21 | lamda = sum(sum(m.*im)); 22 | rho = sum(sum(abs(m-im))); 23 | 24 | IMR = lamda/(lamda+rho); 25 | 26 | end 27 | 28 | function m = check_mask(m) 29 | %CHECK_MASK validate mask 30 | 31 | if ~islogical(m) 32 | if any(m(:)<0) || any(m(:)>1); 33 | error('iosr:calcImr:maskValuesOutOfRange','Values outside of [0,1] were found.') 34 | end 35 | else % make numeric 36 | m = +m; 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /+iosr/+figures/chMap.m: -------------------------------------------------------------------------------- 1 | function cmap = chMap(M) 2 | %CHMAP Create a monochrome-compatible colour map 3 | % 4 | % CMAP = IOSR.FIGURES.CHMAP returns a colour map CMAP (varying black - 5 | % blue - green - yellow - white) that is monochrome-compatible, i.e. it 6 | % produces a linear greyscale colour map. CMAP is size Mx3, where M is 7 | % the length of the current figure's colormap. If no figure exists, 8 | % MATLAB creates one. 9 | % 10 | % CMAP = IOSR.FIGURES.CHMAP(M) returns a colormap of length M. 11 | % 12 | % EXAMPLE 13 | % 14 | % figure; 15 | % imagesc(sin(linspace(0,2*pi,1000))'*... 16 | % sin(linspace(0,2*pi,1000))); 17 | % colormap(iosr.figures.chMap(256)); 18 | % axis image; 19 | % colorbar 20 | % 21 | % See also IOSR.FIGURES.CMRMAP, GRAY. 22 | 23 | % Copyright 2016 University of Surrey. 24 | 25 | % default colormap size 26 | if nargin < 1, M = size(get(gcf,'colormap'),1); end 27 | 28 | N = linspace(0,1,M)'; 29 | 30 | % Define red and blue components 31 | R = (sin(((2.*N.^1.5)-1).*(pi/2))+1)./2; 32 | B = ((N.^1.25)+((sin((N.^1.0).*(2*pi)))./1.75)); 33 | % Calculate green to ensure monotonic luminance 34 | G = (N - 0.2989.*R - 0.1140.*B)./0.5870; 35 | 36 | % Ensure map is in range [0,1] 37 | cmap = [R G B]; 38 | cmap = cmap-min(cmap(:)); 39 | cmap = cmap./max(cmap(:)); 40 | 41 | end 42 | -------------------------------------------------------------------------------- /+iosr/+statistics/trirnd.m: -------------------------------------------------------------------------------- 1 | function r = trirnd(varargin) 2 | %TRIRND Pseudorandom numbers drawn from the triangular distribution 3 | % 4 | % R = IOSR.STATISTICS.TRIRND(N) returns an N-by-N matrix containing 5 | % pseudorandom values drawn from the triangular distribution constrained 6 | % to (-1,1) and mode = 0. 7 | % 8 | % IOSR.STATISTICS.TRIRND(M,N) or IOSR.STATISTICS.TRIRND([M,N]) returns an 9 | % M-by-N matrix. 10 | % 11 | % IOSR.STATISTICS.TRIRND(M,N,P,...) or 12 | % IOSR.STATISTICS.TRIRND([M,N,P,...]) returns an M-by-N-by-P-by-... 13 | % array. 14 | % 15 | % IOSR.STATISTICS.TRIRND returns a scalar. 16 | % 17 | % TRIRND(SIZE(A)) returns an array the same size as A. 18 | % 19 | % Note: The size inputs M, N, P, ... should be nonnegative integers. 20 | % Negative integers are treated as 0. 21 | % 22 | % The sequence of numbers produced by TRIRND is determined by the 23 | % settings of the uniform random number generator that underlies RAND, 24 | % RANDI, and RANDN. Control that shared random number generator using 25 | % RNG. 26 | % 27 | % See also IOSR.STATISTICS.LAPRND, RAND, RANDN, RANDI, RNG. 28 | 29 | % Based on code (Matlab FE File ID: #13705) written by Elvis Chen, 2007. 30 | 31 | % Copyright 2016 University of Surrey. 32 | 33 | % Generate traingular noise 34 | u1 = rand(varargin{:})-0.5; 35 | u2 = rand(varargin{:})-0.5; 36 | r = u1+u2; 37 | 38 | end 39 | -------------------------------------------------------------------------------- /+iosr/+dsp/localpeaks.m: -------------------------------------------------------------------------------- 1 | function peaks = localpeaks(x,mode) 2 | %LOCALPEAKS Find local peaks and troughs in a vector 3 | % 4 | % This function returns the indices of local peaks and/or troughs in a 5 | % vector. 6 | % 7 | % PEAKS = IOSR.DSP.LOCALPEAKS(X) locates the local peaks in vector X. 8 | % 9 | % PEAKS = IOSR.DSP.LOCALPEAKS(X,MODE) locates local features specified by 10 | % mode, which can be set to 'peaks' (default), 'troughs' in order to 11 | % identify local troughs, or 'both' in order to identify both local peaks 12 | % and troughs. 13 | 14 | % Copyright 2016 University of Surrey. 15 | 16 | assert(isvector(x), 'iosr:localpeaks:invalidX', 'Input must be a vector') 17 | 18 | if nargin < 2 19 | mode = 'peaks'; 20 | end 21 | 22 | switch lower(mode) 23 | case 'peaks' 24 | % do nothing 25 | peaks = find_peaks(x); 26 | case 'troughs' 27 | peaks = find_peaks(-x); 28 | case 'both' 29 | peaks = find_peaks(x) | find_peaks(-x); 30 | otherwise 31 | error('iosr:localpeaks:unknownMode','Unknown localpeak mode. Please specify ''peaks'', ''troughs'' or ''both'''); 32 | end 33 | 34 | end 35 | 36 | function peaks = find_peaks(x) 37 | %FIND_PEAKS find local peaks in a vector 38 | 39 | peaks = false(size(x)); 40 | peaks(2:end-1) = sign(x(2:end-1)-x(1:end-2)) + sign(x(2:end-1)-x(3:end)) > 1; 41 | 42 | end 43 | -------------------------------------------------------------------------------- /+iosr/+statistics/laprnd.m: -------------------------------------------------------------------------------- 1 | function r = laprnd(varargin) 2 | %LAPRND Pseudorandom numbers drawn from the Laplace distribution 3 | % 4 | % R = IOSR.STATISTICS.LAPRND(N) returns an N-by-N matrix containing 5 | % pseudorandom values drawn from the Laplace distribution with mean = 0 6 | % and standard deviation = 1. 7 | % 8 | % IOSR.STATISTICS.LAPRND(M,N) or IOSR.STATISTICS.LAPRND([M,N]) returns an 9 | % M-by-N matrix. 10 | % 11 | % IOSR.STATISTICS.LAPRND(M,N,P,...) or 12 | % IOSR.STATISTICS.LAPRND([M,N,P,...]) returns an M-by-N-by-P-by-... 13 | % array. 14 | % 15 | % IOSR.STATISTICS.LAPRND returns a scalar. 16 | % 17 | % IOSR.STATISTICS.LAPRND(SIZE(A)) returns an array the same size as A. 18 | % 19 | % Note: The size inputs M, N, P, ... should be nonnegative integers. 20 | % Negative integers are treated as 0. 21 | % 22 | % The sequence of numbers produced by LAPRND is determined by the 23 | % settings of the uniform random number generator that underlies RAND, 24 | % RANDI, and RANDN. Control that shared random number generator using 25 | % RNG. 26 | % 27 | % See also IOSR.STATISTICS.TRIRND, RAND, RANDN, RANDI, RNG. 28 | 29 | % Based on code (Matlab FE File ID: #13705) written by Elvis Chen, 2007. 30 | 31 | % Copyright 2016 University of Surrey. 32 | 33 | % Generate Laplacian noise 34 | u = rand(varargin{:})-0.5; 35 | b = 1/sqrt(2); 36 | r = -b*sign(u).*log(1-(2*abs(u))); 37 | 38 | end 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IoSR Matlab Toolbox 2 | 3 | A general purpose Matlab toolbox containing functions and classes for: auditory modelling, signal processing, sound source separation, statistics, plotting, etc. See [Contents.m](https://github.com/IoSR-Surrey/Toolbox/blob/master/+iosr/Contents.m) for a full list of functions/classes. 4 | 5 | ## Installation 6 | 7 | Basic installation only requires you to add the install directory to the Matlab search path. 8 | 9 | If you wish to perform certain audio / signal processing tasks (especially spatialisation), please navigate to the install directory and type 10 | 11 | ``` 12 | iosr.install 13 | ``` 14 | 15 | This will automatically download the toolbox's dependencies for these tasks, and add the necessary paths to your search path. 16 | 17 | ## Usage 18 | 19 | Use these functions as: 20 | 21 | ``` 22 | iosr..() 23 | ``` 24 | 25 | (Ignoring the '+' in the folder name.) Alternatively, use the `import` directive to add one or more namespaces, e.g.: 26 | 27 | ``` 28 | import iosr.auditory 29 | import iosr.* 30 | ``` 31 | 32 | If using `import`, note that some function names may conflict with built-in Matlab function names (e.g. `quantile`). One method of resolving the conflict and shortening the function call is to create a handle to any functions with conflicting names, e.g. 33 | 34 | ``` 35 | qntl = @iosr.statistics.quantile; 36 | ``` 37 | 38 | Type 39 | 40 | ``` 41 | help iosr 42 | ``` 43 | 44 | for more information. -------------------------------------------------------------------------------- /+iosr/+acoustics/rtEst.m: -------------------------------------------------------------------------------- 1 | function rt = rtEst(abs_coeff,room,formula) 2 | %RTEST Estimate reverberation time based on room size and absorption 3 | % 4 | % RT = IOSR.ACOUSTICS.RTEST(ABS_COEFF,ROOM) estimates the reverberation 5 | % time (RT60) for a shoebox-shaped room, with average absorption 6 | % coefficient ABS_COEFF, and dimenions ROOM=[L W H] (in metres). An RT 7 | % estimate is returned for each element of ABS_COEFF. 8 | % 9 | % RT = IOSR.ACOUSTICS.RTEST(ABS_COEFF,ROOM,FORMULA) allows the formula to 10 | % be specified. The options are 'sabine' (default), or 'eyring'. 11 | 12 | % Copyright 2016 University of Surrey. 13 | 14 | assert(isnumeric(abs_coeff), 'iosr:rtEst:invalidCoeff', 'abs_coeff should be numeric') 15 | assert(isnumeric(room) & numel(room)==3 & isvector(room), 'iosr:rtEst:invalidRoom', 'room should be a 3-element numeric vector') 16 | 17 | if nargin<3 18 | formula = 'sabine'; 19 | end 20 | 21 | assert(ischar(formula), 'iosr:rtEst:invalidFormula', 'formula should be a character array (string)') 22 | 23 | l = room(1); 24 | w = room(2); 25 | h = room(3); 26 | 27 | vol = prod(room); 28 | surf_area = (2*l*w) + (2*l*h) + (2*w*h); 29 | 30 | switch lower(formula) 31 | case 'sabine' 32 | rt = (0.161*vol)./(surf_area.*abs_coeff); 33 | case 'eyring' 34 | rt = (0.161*vol)./(-surf_area.*log(1-abs_coeff)); 35 | otherwise 36 | error('iosr:rtEst:unknownFormula','Unknown formula') 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /+iosr/+bss/calcSnr.m: -------------------------------------------------------------------------------- 1 | function snr = calcSnr(output,target) 2 | %CALCSNR Calculate the separation SNR 3 | % 4 | % SNR = IOSR.BSS.CALCSNR(OUTPUT,TARGET) calculate the separation 5 | % signal-to-noise ratio in dB for a re-synthesised output compared to the 6 | % ideal target. OUTPUT and TARGET should be vectors of equal length. 7 | 8 | % Copyright 2016 University of Surrey. 9 | 10 | % check input 11 | if ~isvector(output) 12 | error('iosr:calcSnr:invalidOutput','''output'' must be a vector') 13 | end 14 | if ~isvector(target) 15 | error('iosr:calcSnr:invalidTarget','''target'' must be a vector') 16 | end 17 | if numel(output)~=numel(target) 18 | error('iosr:calcSnr:inputLengths','inputs must be the same length') 19 | end 20 | if size(output,1)==1 21 | output = output'; 22 | end 23 | if size(target,1)==1 24 | target = target'; 25 | end 26 | 27 | % remove delay caused by convolution 28 | cc = xcorr(output.^2,target.^2); 29 | 30 | delay = find(cc==max(cc))-length(output); 31 | 32 | if delay > 0 33 | target = [zeros(delay,1); target]; 34 | output = [output; zeros(delay,1)]; 35 | elseif delay < 0 36 | delay = -delay; 37 | output = [zeros(delay,1); output]; 38 | target = [target; zeros(delay,1)]; 39 | end 40 | 41 | % account for arbitrary gain 42 | if sum(abs(output(:))) > 0 43 | G = output\target; 44 | output = output.*G; 45 | end 46 | 47 | snr = 10*log10(sum(target.^2)/sum((output-target).^2)); 48 | 49 | end 50 | -------------------------------------------------------------------------------- /+iosr/+general/cell2csv.m: -------------------------------------------------------------------------------- 1 | function cell2csv(C,filename) 2 | %CELL2CSV Output a cell array to a CSV file 3 | % 4 | % IOSR.GENERAL.CELL2CSV(C,FILENAME) writes the cell array C to a CSV file 5 | % specified by filename. 6 | 7 | % Copyright 2016 University of Surrey. 8 | 9 | %% make sure filename has a .csv extension 10 | 11 | [pathstr, name, ext] = fileparts(filename); 12 | if ~strcmp(ext,'.csv') 13 | filename = [pathstr filesep name '.csv']; 14 | end 15 | 16 | %% write data 17 | 18 | [nrows,ncols]= size(C); 19 | 20 | % trap and remove cell arrays of strings 21 | IX = cell2mat(cellfun(@iscell,C,'UniformOutput',false)); 22 | C(IX) = cellfun(@char,C(IX),'UniformOutput',false); 23 | 24 | % create file 25 | fid = fopen(filename, 'w'); 26 | 27 | % create format string 28 | format = cell(1,ncols); 29 | ind = cellfun(@ischar,C(2,:)); 30 | format(ind) = {'%s,'}; 31 | format(~ind) = {'%f,'}; 32 | format = wrap(format); 33 | 34 | if any(cellfun(@ischar,C(1,:))~=cellfun(@ischar,C(2,:))) 35 | % the cell array has a heading line 36 | header = repmat({'%s,'},[1,ncols]); 37 | header = wrap(header); 38 | start = 2; 39 | fprintf(fid, header, C{1,:}); 40 | else 41 | % the cell array does not have a heading line 42 | start = 1; 43 | end 44 | 45 | for row=start:nrows 46 | fprintf(fid, format, C{row,:}); 47 | end 48 | 49 | fclose(fid); 50 | 51 | end 52 | 53 | function y = wrap(x) 54 | %WRAP Concatenate cells and add end EOL 55 | 56 | y = cell2mat(x); 57 | i = strfind(y,','); 58 | y = [y(1:i(end)-1) '\n']; 59 | 60 | end 61 | -------------------------------------------------------------------------------- /+iosr/+bss/cfs2fcs.m: -------------------------------------------------------------------------------- 1 | function fcs = cfs2fcs(cfs,fs) 2 | %CFS2FCS Calculate gammatone crossover frequencies. 3 | % 4 | % FCS = IOSR.BSS.CFS2FCS(CFS,FS) calculates the crossover frequencies of a 5 | % gammatone filterbank. The output fcs is the same size as CFS, with the 6 | % last element being equal to fs/2. 7 | % 8 | % The crossover frequencies are determined empirically by measuring the 9 | % impulse responses of the gammatone filters. 10 | % 11 | % See also IOSR.AUDITORY.GAMMATONEFAST, IOSR.AUDITORY.MAKEERBCFS. 12 | 13 | % Copyright 2016 University of Surrey. 14 | 15 | % Pre-allocate cut-off frequencies 16 | fcs = zeros(size(cfs)); 17 | fcs(end) = fs/2; 18 | 19 | % Create impulse to derive cut-offs empirically 20 | imp_length = fs; 21 | imp = zeros(imp_length,1); 22 | imp(1) = 1; 23 | 24 | % Pre-allocate gammatone transfer functions 25 | if mod(imp_length,2)==0 26 | z_length = (imp_length/2)+1; 27 | else 28 | z_length = (imp_length+1)/2; 29 | end 30 | gamma_TF = zeros(z_length,length(cfs)); 31 | 32 | % Filter impulses and calculate transfer functions. 33 | for i = 1:length(cfs) 34 | % filter 35 | gamma_imp = iosr.auditory.gammatoneFast(imp,cfs,fs); 36 | % transfer function 37 | temp = abs(fft(gamma_imp)); 38 | gamma_TF(:,i) = temp(1:z_length); 39 | end 40 | 41 | f = ((0:z_length-1)./imp_length).*fs; 42 | 43 | % Find cross point of transfer functions. 44 | for i = 1:length(cfs)-1 45 | IX = f>cfs(i) & f0 47 | f_temp = f(IX); 48 | low_TF = gamma_TF(IX,i); 49 | high_TF = gamma_TF(IX,i+1); 50 | diff_TF = abs(high_TF-low_TF); 51 | fcs(i) = f_temp(find(diff_TF==min(diff_TF),1,'first')); 52 | else 53 | fcs(i) = mean([cfs(i) cfs(i+1)]); 54 | end 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /+iosr/+figures/cmrMap.m: -------------------------------------------------------------------------------- 1 | function cmap = cmrMap(M) 2 | %CMRMAP Create a monochrome-compatible colour map 3 | % 4 | % CMAP = IOSR.FIGURES.CMRMAP returns a colour map CMAP (varying black - 5 | % purple - red - yellow - white) that is monochrome- compatible, i.e. it 6 | % produces a monotonic greyscale colour map. CMAP is size M-by-3, where M 7 | % is the length of the current figure's colormap. If no figure exists, 8 | % MATLAB creates one. 9 | % 10 | % CMAP = IOSR.FIGURES.CMRMAP(M) returns a colormap of length M. 11 | % 12 | % The map is a slight modification to that suggested in [1]. 13 | % 14 | % EXAMPLE 15 | % 16 | % figure; 17 | % imagesc(peaks(1000)); 18 | % colormap(iosr.figures.cmrMap(256)); 19 | % axis image; 20 | % colorbar 21 | % 22 | % REFERENCE 23 | % 24 | % [1] Rappaport, C. 2002: "A Color Map for Effective Black-and-White 25 | % Rendering of Color Scale Images", IEEE Antenna's and Propagation 26 | % Magazine, Vol.44, No.3, pp.94-96 (June). 27 | % 28 | % See also IOSR.FIGURES.CHMAP, GRAY. 29 | 30 | % Copyright 2016 University of Surrey. 31 | 32 | % default colormap size 33 | if nargin < 1, M = size(get(gcf,'colormap'),1); end 34 | 35 | % reference colour map 36 | % adapted from article to produce more linear luminance 37 | CMRref=... 38 | [0 0 0; 39 | 0.1 0.1 0.35; 40 | 0.3 0.15 0.65; 41 | 0.6 0.2 0.50; 42 | 1 0.25 0.15; 43 | 0.9 0.55 0; 44 | 0.9 0.75 0.1; 45 | 0.9 0.9 0.5; 46 | 1 1 1]; 47 | 48 | % Interpolate colormap to colormap size 49 | cmap = zeros(M,3); 50 | for c = [1,3] 51 | cmap(:,c) = interp1((1:9)',CMRref(:,c),linspace(1,9,M)','spline'); 52 | end 53 | % calculate green to ensure linear luminance 54 | cmap(:,2) = (linspace(0,1,M)' - 0.2989.*cmap(:,1) - 0.1140.*cmap(:,3))./0.5870; 55 | 56 | % Limit to range [0,1] 57 | cmap = cmap-min(cmap(:)); 58 | cmap = cmap./max(cmap(:)); 59 | 60 | end 61 | -------------------------------------------------------------------------------- /+iosr/install.m: -------------------------------------------------------------------------------- 1 | function mypath = install 2 | %INSTALL Set search paths, and download and install dependencies. 3 | % 4 | % IOSR.INSTALL downloads and installs the toolbox dependencies. The 5 | % function also adds the required paths to the Matlab search path. 6 | % 7 | % MYPATH = IOSR.INSTALL returns the old Matlab search path MYPATH. 8 | 9 | % Copyright 2016 University of Surrey. 10 | 11 | %% download and install SOFA 12 | 13 | % install dir 14 | currdir = cd; 15 | cd([fileparts(which(mfilename('fullpath'))) filesep '..']); 16 | directory = pwd; 17 | sofa_folder = [directory filesep 'deps' filesep 'SOFA_API']; 18 | 19 | if ~(exist(sofa_folder,'dir') == 7) 20 | % download and install 21 | sofa_filename = 'sofa-api.zip'; 22 | try % Sourceforge location changes from time to time 23 | websave(sofa_filename,'http://vorboss.dl.sourceforge.net/project/sofacoustics/sofa-api-mo-1.0.2.zip'); 24 | catch % Fall back to development release on GitHub 25 | display('Warning: Failed to download SOFA v1.0.2 from Sourceforge, downloading development release...') 26 | websave(sofa_filename,'https://github.com/sofacoustics/API_MO/archive/master.zip'); 27 | end 28 | unzip(sofa_filename,sofa_folder); 29 | movefile([sofa_folder filesep 'API_MO' filesep '*'],[sofa_folder filesep]); 30 | 31 | % clean up 32 | delete(sofa_filename) 33 | rmdir([sofa_folder filesep 'doc' filesep],'s') 34 | rmdir([sofa_folder filesep 'HRTFs' filesep],'s') 35 | rmdir([sofa_folder filesep 'API_MO' filesep],'s') 36 | else 37 | display(strcat('Found existing SOFA directory: ', sofa_folder)) 38 | end 39 | 40 | %% Add directories to path 41 | 42 | cd(directory); 43 | mypath = addpath(directory,... 44 | [directory filesep 'deps' filesep 'SOFA_API']); 45 | 46 | %% start SOFA 47 | 48 | SOFAstart(0); 49 | 50 | cd(currdir); % return to original directory 51 | 52 | end 53 | -------------------------------------------------------------------------------- /+iosr/+bss/idealMasks.m: -------------------------------------------------------------------------------- 1 | function [irm,ibm] = idealMasks(st,si,q,a) 2 | %IDEALMASKS Calculate ideal time-frequency masks from STFTs 3 | % 4 | % IRM = IOSR.BSS.IDEALMASKS(ST,SI) calculates the ideal ratio mask (IRM) 5 | % using target STFT ST and interferer STFT SI. ST and SI must be the same 6 | % size; IRM is the same size as ST and SI. 7 | % 8 | % [IRM,IBM] = IOSR.BSS.IDEALMASKS(...) returns the ideal binary mask 9 | % (IBM). 10 | % 11 | % IRM = IOSR.BSS.IDEALMASKS(ST,SI,Q) uses the exponent Q to create an 12 | % ideal sigmoidal mask by raising each of the time-frequency powers to Q. 13 | % With Q=1 (default) the mask is the IRM. As Q->Inf the IRM will tend 14 | % towards a binary mask. As Q->0 the mask will tend towards 0.5. 15 | % 16 | % [IRM,IBM] = IOSR.BSS.IDEALMASKS(ST,SI,Q,A) uses the threshold A to 17 | % calculate the IBM. The threshold is in terms of the ratio of time 18 | % frequency powers. With A=1 (default), the IBM is 1 when the target 19 | % power is greater than the interferer power. Setting A=2, for example, 20 | % requires the target power to be 6dB greater than the interference 21 | % power. 22 | % 23 | % See also IOSR.BSS.APPLYMASK, IOSR.BSS.APPLYIDEALMASKS. 24 | 25 | % Copyright 2016 University of Surrey. 26 | 27 | %% check input 28 | 29 | assert(isequal(size(st),size(si)), 'iosr:idealMasks:invalidInputs', 'ST and SI must be the same size') 30 | 31 | % check sigmoid 32 | if nargin<3 33 | q = 1; 34 | else 35 | assert(isscalar(a), 'iosr:idealMasks:invalidQ', 'Q must be an scalar') 36 | end 37 | 38 | % check threshold 39 | if nargin<4 40 | a = 1; 41 | else 42 | assert(isscalar(a), 'iosr:idealMasks:invalidA', 'A must be an scalar') 43 | end 44 | 45 | %% calculate masks 46 | 47 | % powers 48 | St = abs(st).^2; 49 | Si = abs(si).^2; 50 | 51 | % ideal ratio mask 52 | irm = (St.^q)./((St.^q)+(Si.^q)); 53 | irm(isnan(irm)) = 0; 54 | 55 | % ideal binary mask 56 | if nargout>1 57 | ibm = +(St./Si>a); 58 | ibm(isnan(ibm)) = 0; 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /+iosr/+auditory/calcIld.m: -------------------------------------------------------------------------------- 1 | function ild = calcIld(L,R,method) 2 | %CALCILD Calculate normalised interaural level difference 3 | % 4 | % ILD = IOSR.AUDITORY.CALCILD(L,R) calculates the ILD of vectors L and R. 5 | % Rather than a logarithmic ratio, the returned ILD is in the range 6 | % [-1,1]. The algorithm has the following steps: 7 | % 8 | % 1. Calculate total power for each L and R vectors. 9 | % 2. Calculate difference of powers and divide by power sum. 10 | % 3. Square result, ensuring sign is retained. This improves contrast 11 | % when the signals are of a similar level. 12 | % 13 | % ILD = IOSR.AUDITORY.CALCILD(L,R,METHOD) determines the nature of the 14 | % ILD that is returned, and consequently how it is calculated. When 15 | % method is 'overall' (default), the ILD for the entire signal is 16 | % returned, based on the power carried by the fine structure. When method 17 | % is 'vector', the ILD is calculated using the instaneous Hilbert 18 | % envelope and the function returns a vector the same size as L or R. 19 | % 20 | % The function is derived from Ben Supper's thesis "An onset-guided 21 | % spatial analyser for binaural audio" 22 | 23 | % Copyright 2016 University of Surrey. 24 | 25 | assert(isvector(L) & isvector(R), 'iosr:calcIld:invalidInput', 'L and R must be vectors') 26 | assert(all(size(L)==size(R)), 'iosr:calcIld:invalidInput', 'L and R must be the same size') 27 | 28 | if nargin<3 29 | method='overall'; 30 | else 31 | assert(ischar(method), 'iosr:calcIld:invalidMethod', 'METHOD must be a string.') 32 | end 33 | 34 | switch lower(method) 35 | case 'vector' 36 | envL = calc_env(L); 37 | envR = calc_env(R); 38 | p_L = envL.^2; 39 | p_R = envR.^2; 40 | case 'overall' 41 | p_L = sum(L.^2); 42 | p_R = sum(R.^2); 43 | otherwise 44 | error('iosr:calcIld:unknownMethod',['Unknown method ''' method '''.']) 45 | end 46 | ild = (p_L-p_R)./(p_L+p_R); 47 | ild = sign(ild).*(ild.^2); 48 | 49 | end 50 | 51 | function y = calc_env(x) 52 | %CALC_ENV return signal Hilbert envelope 53 | 54 | y = abs(hilbert(x)); 55 | 56 | end 57 | -------------------------------------------------------------------------------- /+iosr/+bss/applyMask.m: -------------------------------------------------------------------------------- 1 | function [z,t] = applyMask(s,m,nfft,hop,fs) 2 | %APPLYMASK Apply a time-frequency mask to an STFT 3 | % 4 | % Z = IOSR.BSS.APPLYMASK(S,M) applies the time-frequency mask M to STFT 5 | % S. S has dimensions [N,K] where N is the number of frequency bins and K 6 | % is the number of time frames. The ISTFT is calculated using 1024-point 7 | % FFTs and hop sizes of 512 points. 8 | % 9 | % Z = IOSR.BSS.APPLYMASK(S,M,NFFT) uses NFFT-length segments in the 10 | % ISTFT. 11 | % 12 | % Z = IOSR.BSS.APPLYMASK(S,M,NFFT,HOP) uses hop size HOP for the ISTFT. 13 | % 14 | % [Z,T] = IOSR.BSS.APPLYMASK(S,M,NFFT,HOP,FS) uses sampling frequency FS 15 | % to return the corresponding time T of each element in Z. 16 | % 17 | % See also IOSR.DSP.STFT, IOSR.DSP.ISTFT, IOSR.BSS.IDEALMASKS, 18 | % IOSR.BSS.APPLYIDEALMASKS. 19 | 20 | % Copyright 2016 University of Surrey. 21 | 22 | %% check input 23 | 24 | % check input 25 | if nargin<2 26 | error('iosr:applyMask:nargin','Not enough input arguments') 27 | end 28 | 29 | % check compulsory inputs 30 | assert(isequal(size(s),size(m)), 'iosr:applyMask:invalidInput', 'S and M must be the same size') 31 | 32 | % check nfft 33 | if nargin<3 34 | nfft = 1024; 35 | else 36 | assert(isscalar(nfft) && round(nfft)==nfft && nfft>0, 'iosr:applyMask:invalidNfft', 'NFFT must be a positive scalar integer') 37 | end 38 | 39 | % determine hop 40 | if nargin<4 41 | hop = fix(nfft/2); 42 | else 43 | assert(isscalar(hop) & round(hop)==hop, 'iosr:applyMask:invalidHop', 'HOP must be an integer') 44 | assert(hop<=nfft && hop>0, 'iosr:applyMask:invalidHop', 'HOP must be less than or equal to NFFT, and greater than 0') 45 | end 46 | 47 | % determine fs 48 | if nargin<5 49 | fs = 1; 50 | else 51 | assert(isscalar(fs), 'iosr:applyMask:invalidFs', 'FS must be an scalar') 52 | end 53 | 54 | %% calculate outputs 55 | 56 | % apply mask and return signal 57 | z = iosr.dsp.istft(s.*m,nfft,hop); 58 | 59 | % return time 60 | if nargout>1 61 | t = (0:length(z)-1)./fs; 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /+iosr/+general/urn.m: -------------------------------------------------------------------------------- 1 | function r = urn(varargin) 2 | %URN Generate random number sequence without duplicates 3 | % 4 | % IOSR.GENERAL.URN(N) returns an N-by-N matrix containing a random 5 | % sequence of integers in the interval 1:N without duplicates. The 6 | % integers are unique down each column. 7 | % 8 | % IOSR.GENERAL.URN(M,N) and IOSR.GENERAL.URN([M,N]) return an M-by-N 9 | % matrix. 10 | % 11 | % IOSR.GENERAL.URN(M,N,P,...) and IOSR.GENERAL.URN([M,N,P,...]) return an 12 | % M-by-N-by-P-by-... array. The integers are unique along the first 13 | % non-singleton dimension. 14 | % 15 | % IOSR.GENERAL.URN(...,[],DIM) creates the random sequence along the 16 | % dimension dim. 17 | % 18 | % This function uses a simple algorithm whereby a series or 19 | % uniformly-distributed random numbers are generated and sorted. The 20 | % returned array r is simply the array of indices specifying the sort 21 | % order. 22 | % 23 | % The function was inspired by an object of the same name in Cycling 74's 24 | % Max/MSP. 25 | 26 | % Copyright 2016 University of Surrey. 27 | 28 | % determines whether dimension specified 29 | dimcheck = false; 30 | 31 | % find an empty matrix in input 32 | empties = find(cellfun(@isempty,varargin)); 33 | if ~isempty(empties) 34 | % if there is an empty matrix (dim specified) 35 | range = 1:empties-1; % input array-size-data range 36 | if length(varargin)>empties % check extra dim input specified 37 | dimcheck = true; % dim has been specified 38 | dim = varargin{empties+1}; % get dim 39 | else 40 | warning('iosr:urn:nodim','Empty array but no dim specified. Using default.') 41 | end 42 | else 43 | % input array-size-data range if no dim specified 44 | range = 1:length(varargin); 45 | end 46 | 47 | % input array-size-data at input 48 | n = cell2mat(varargin(range)); 49 | 50 | % random numbers 51 | c = rand(n); 52 | 53 | % If dim unspecified, find first non-singleton dimension 54 | if ~dimcheck 55 | nsdim = find(size(c)>1,1,'first'); 56 | if isempty(nsdim) 57 | dim = 1; 58 | else 59 | dim = nsdim; 60 | end 61 | end 62 | 63 | % sort the sequence, keeping sort indices as random integers 64 | [~,r] = sort(c,dim); 65 | 66 | end 67 | -------------------------------------------------------------------------------- /+iosr/+bss/getFullMask.m: -------------------------------------------------------------------------------- 1 | function m_full = getFullMask(m,frame_d,delay,kernel) 2 | %GETFULLMASK Convert frame rate mask to a sample-by-sample mask 3 | % 4 | % M_FULL = IOSR.BSS.GETFULLMASK(M,FRAME_D) expands the time-frequency 5 | % mask M, which has one unit for each frequency channel and frame of 6 | % length FRAME_D (in samples), to a sample-by-sample mask. The mask M is 7 | % a time-frequency mask, with one column for each frequency channel, and 8 | % one row for each time frame. The resulting mask will have dimensions 9 | % [FRAME_D*size(M,1) size(M,2)]. 10 | % 11 | % M_FULL = IOSR.BSS.GETFULLMASK(M,FRAME_D,DELAY) removes a delay from 12 | % each frequency channel in the mask. DELAY is a vector, with the same 13 | % number of elements as M has columns, containing a delay (in samples) 14 | % that is removed from the corresponding frequency channel. The mask is 15 | % subsequently zero-padded. 16 | % 17 | % M_FULL = IOSR.BSS.GETFULLMASK(M,FRAME_D,DELAY,KERNEL) allows smoothing 18 | % to be applied to the full mask. By default, the full mask contains 19 | % rectangular transitions at unit boundaries. Specifying KERNEL allows 20 | % the transitions to be smoother, by convolving the full mask with a 21 | % two-dimensional kernel (dimensions [frequency time]). The central part 22 | % of the convolution is returned, so the centre of the KERNEL should be 23 | % in the centre of the matrix. The kernel is normalised in order to 24 | % ensure zero gain at DC. 25 | % 26 | % See also IOSR.BSS.RESYNTHESISE. 27 | 28 | % Copyright 2016 University of Surrey. 29 | 30 | 31 | if nargin < 2 32 | error('iosr:getFullMask:nargin','Not enough input arguments') 33 | end 34 | 35 | numchans = size(m,2); 36 | frameCount = size(m,1); 37 | 38 | if nargin < 3 39 | delay = zeros(1,numchans); 40 | end 41 | if nargin < 4 42 | kernel = 1; 43 | end 44 | 45 | % Create the sample-by-sample mask 46 | m_full = zeros(frameCount*frame_d,numchans); 47 | for i = 1:numchans 48 | for j = 1:frameCount 49 | m_full(((j-1)*frame_d+1):((j-1)*frame_d+1)+frame_d-1,i) = m(j,i); 50 | end 51 | m_full(:,i) = [m_full(delay(i)+1:end,i); zeros(delay(i),1)]; 52 | end 53 | 54 | % convolve with kernel 55 | kernel = kernel./sum(abs(kernel(:))); 56 | m_full = conv2(m_full,kernel,'same'); 57 | 58 | end 59 | -------------------------------------------------------------------------------- /+iosr/+auditory/meddisHairCell.m: -------------------------------------------------------------------------------- 1 | function y = meddisHairCell(data,sampleRate,subtractSpont) 2 | % Calculate Ray Meddis' hair cell model for a number of channels. 3 | % 4 | % y = iosr.auditory.meddisHairCell(data,sampleRate) 5 | % 6 | % This function calculates Ray Meddis' hair cell model for a 7 | % number of channels. Data is arrayed as one channel per 8 | % row. All channels are done in parallel (but each time step 9 | % is sequential) so it will be much more efficient to 10 | % process lots of channels at once. 11 | % 12 | % (c) 1998 Interval Research Corporation 13 | % 12/11/98: Changed h and added comment at suggestion of 14 | % Alain de Cheveigne 15 | 16 | if (nargin<3), subtractSpont=0; end 17 | 18 | % Parameters from Meddis' April 1990 JASA paper. 19 | M = 1; 20 | A = 5; 21 | B = 300; 22 | g = 2000; 23 | y = 5.05; 24 | l = 2500; 25 | r = 6580; 26 | x = 66.31; 27 | h = 50000; % This parameter scales the discharge rate. Adjust as necessary. 28 | % In combination with the gammatone filterbank (ERBFilterBank), 29 | % h=50000 will produce a steady-state average discharge 30 | % probability of about 135 spikes/s within the 1kHz channel, 31 | % for an input consisting of a 1 kHz sinewave at 60 dB SPL 32 | % (0 dB SPL corresponds to an RMS level of 1.0 at the 33 | % input of the gammatone filter). Scaling and constant 34 | % courtesy of Alain de Cheveigne' 35 | 36 | 37 | % Internal constants 38 | dt = 1/sampleRate; 39 | gdt = g*dt; 40 | ydt = y*dt; 41 | ldt = l*dt; 42 | rdt = r*dt; 43 | xdt = x*dt; 44 | [numChannels, dataLength] = size(data); 45 | 46 | % Initial values 47 | kt = g*A/(A+B); 48 | spont = M*y*kt/(l*kt+y*(l+r)); 49 | c = spont * ones(numChannels,1); 50 | q = c*(l+r)/kt; 51 | w = c*r/x; 52 | zeroVector = zeros(numChannels,1); 53 | 54 | % Now iterate through each time slice of the data. Use the 55 | % max function to implement the "if (0>" test. 56 | y = zeros(numChannels, dataLength); 57 | for i = 1:dataLength 58 | limitedSt = max(data(:,i)+A,0); 59 | kt = gdt*limitedSt./(limitedSt+B); 60 | replenish = max(ydt*(M-q),zeroVector); 61 | eject = kt.*q; 62 | loss = ldt.*c; 63 | reuptake = rdt.*c; 64 | reprocess = xdt.*w; 65 | 66 | q = q + replenish - eject + reprocess; 67 | c = c + eject - loss - reuptake; 68 | w = w + reuptake - reprocess; 69 | y(:,i) = c; 70 | end 71 | 72 | y = h .* y; 73 | 74 | if (subtractSpont > 0) 75 | y=max(0,y-spont); 76 | end 77 | -------------------------------------------------------------------------------- /+iosr/+bss/example.m: -------------------------------------------------------------------------------- 1 | %% determine STFT parameters 2 | 3 | hop = 128; 4 | nfft = 1024; 5 | win = hann(nfft); 6 | 7 | %% load signals 8 | 9 | load('handel.mat','y','Fs') 10 | xt = y; 11 | load('laughter.mat','y','Fs') 12 | xi = y; 13 | 14 | % crop xt 15 | xt = xt(1:length(xi)); 16 | 17 | %% calculate and plot STFTs 18 | 19 | % calculate STFTs 20 | [st,f,t] = iosr.dsp.stft(xt,win,hop,Fs); 21 | si = iosr.dsp.stft(xi,win,hop,Fs); 22 | 23 | % calculate spectrograms 24 | stlog = 20.*log10(abs(st)); 25 | silog = 20.*log10(abs(si)); 26 | 27 | % calculate colormap limits 28 | clim = [min([stlog(:); silog(:)]) max([stlog(:); silog(:)])]; 29 | 30 | % draw figures 31 | figure 32 | % 33 | subplot(8,4,[1 2 5 6]) 34 | imagesc(t,f,stlog) 35 | set(gca,'ydir','normal','xticklabel',[],'clim',clim) 36 | title('Target spectrogram') 37 | ylabel('Frequency [Hz]') 38 | ch = colorbar; 39 | ylabel(ch,'Magnitude [dB]') 40 | % 41 | subplot(8,4,[9 10 13 14]) 42 | imagesc(t,f,silog) 43 | set(gca,'ydir','normal','clim',clim) 44 | title('Interference spectrogram') 45 | xlabel('Time [s]') 46 | ylabel('Frequency [Hz]') 47 | ch = colorbar; 48 | ylabel(ch,'Magnitude [dB]') 49 | 50 | %% calculate and plot ideal masks 51 | 52 | % calculate ideal masks 53 | [irm,ibm] = iosr.bss.idealMasks(st,si); 54 | 55 | % draw figures 56 | subplot(8,4,[3 4 7 8]) 57 | imagesc(t,f,irm) 58 | set(gca,'ydir','normal','xticklabel',[],'yticklabel',[],'clim',[0 1]) 59 | title('Ideal ratio mask (IRM)') 60 | ch = colorbar; 61 | ylabel(ch,'Mask value') 62 | % 63 | subplot(8,4,[11 12 15 16]) 64 | imagesc(t,f,ibm) 65 | set(gca,'ydir','normal','yticklabel',[],'clim',[0 1]) 66 | title('Ideal binary mask (IBM)') 67 | xlabel('Time [s]') 68 | ch = colorbar; 69 | ylabel(ch,'Mask value') 70 | 71 | %% resynthesise and plot output 72 | 73 | % calculate and apply ideal masks in one step 74 | [z_irm,z_ibm,t] = iosr.bss.applyIdealMasks(xt,xi,win,hop,Fs); 75 | 76 | % draw figures 77 | subplot(8,4,21:24) 78 | plot(t,xt) 79 | set(gca,'xticklabel',[],'xlim',[0 max(t)],'ylim',[-1 1]) 80 | title('Clean target signal') 81 | ylabel('Amplitude') 82 | % 83 | subplot(8,4,25:28) 84 | plot(t,z_irm) 85 | set(gca,'xticklabel',[],'xlim',[0 max(t)],'ylim',[-1 1]) 86 | title('Target resynthesised from IRM') 87 | ylabel('Amplitude') 88 | % 89 | subplot(8,4,29:32) 90 | plot(t,z_ibm) 91 | set(gca,'xlim',[0 max(t)],'ylim',[-1 1]) 92 | title('Target resynthesised from IBM') 93 | xlabel('Time [s]') 94 | ylabel('Amplitude') 95 | -------------------------------------------------------------------------------- /+iosr/+svn/readSvnKeyword.m: -------------------------------------------------------------------------------- 1 | function keydata = readSvnKeyword(filename,keyword,crop) 2 | %READSVNKEYWORD Read data from a file tagged with an SVN keyword 3 | % 4 | % The function takes an input filename and searches that file for a 5 | % Subversion keyword. The data associated with the keyword are returned 6 | % in a character array. Keyword data are placed in files automatically by 7 | % Subversion at each commit using the 'keyword' function. The data can be 8 | % useful in maintaining an audit trail, or establishing the version used 9 | % to generate a particular set of results. 10 | % 11 | % KEYDATA = IOSR.SVN.READSVNKEYWORD(FILENAME,KEYWORD) returns a string 12 | % KEYDATA containing data associated with the SVN keyword KEYWORD in a 13 | % file specified by FILENAME. 14 | % 15 | % FILENAME and KEYWORD must be strings specifying a single file and 16 | % keyword respectively. To read multiple files or use multiple keywords, 17 | % use BUILD_SVN_PROFILE instead. The function returns an empty string if 18 | % the keyword is not found. 19 | % 20 | % KEYDATA = IOSR.SVN.READSVNKEYWORD(FILENAME,KEYWORD,CROP), with CROP = 21 | % true (default is false), removes the keyword, and leading and trailing 22 | % spaces, from the returned string. 23 | % 24 | % See also IOSR.SVN.BUILDSVNPROFILE. 25 | 26 | % Copyright 2016 University of Surrey. 27 | 28 | 29 | assert(ischar(filename) & ischar(keyword), 'iosr:readSvnKeyword:invalidInputs', 'FILENAME and KEYWORD must be char arrays') 30 | 31 | if nargin < 3 32 | crop = false; 33 | end 34 | 35 | keydata = ''; 36 | 37 | fid = fopen(filename); % open file 38 | assert(fid~=-1, 'iosr:readSvnKeyword:invalidFile', ['read_svn_keyword: ''' filename ''' not found']) 39 | 40 | tline = fgetl(fid); % read first line 41 | while ischar(tline) 42 | k1 = strfind(tline,['$' keyword ':']); % find keyword 43 | if ~isempty(k1) % if it is found 44 | k2 = strfind(tline,'$'); % find the end of the keyword data 45 | k2 = k2(k2>k1); 46 | keydata = tline(k1+1:k2-2); % extract the data from the line 47 | tline = -1; % set tline to numeric 48 | else 49 | tline = fgetl(fid); % read next line 50 | end 51 | end 52 | 53 | if crop 54 | keydata = keydata(find(isspace(keydata),1,'first')+1:end); 55 | end 56 | 57 | fclose(fid); % close file 58 | 59 | end 60 | -------------------------------------------------------------------------------- /+iosr/+general/checkMexCompiled.m: -------------------------------------------------------------------------------- 1 | function checkMexCompiled(varargin) 2 | %CHECKMEXCOMPILED Check if mex file is compiled for system 3 | % 4 | % IOSR.GENERAL.CHECKMEXCOMPILED(SOURCE_FILE) checks whether a mex source 5 | % file SOURCE_FILE is compiled for the current operating system OR 6 | % whether the source file has been modified since it was compiled. It is 7 | % compiled if it does not pass these tests (to the same directory as the 8 | % source file). SOURCE_FILE must be a string that is the name of a source 9 | % file on the MATLAB search path. 10 | % 11 | % IOSR.GENERAL.CHECKMEXCOMPILED(OPTIONS,...,SOURCE_FILE) passes the 12 | % script switches in OPTIONS to the mex compiler, one argument per 13 | % switch. 14 | % 15 | % Example 16 | % 17 | % % check function compiled, with debugging info, and 18 | % % with large-array-handling API 19 | % iosr.general.checkMexCompiled('-g','-largeArrayDims','myfun.c') 20 | % 21 | % See also MEX. 22 | 23 | % Copyright 2016 University of Surrey. 24 | 25 | source_file = varargin{end}; 26 | 27 | % Check input filename 28 | assert(ischar(source_file), 'iosr:checkMexCompiled:invalidFile', 'source_file must be a string') 29 | 30 | % Check extension is specified 31 | assert(~isempty(strfind(source_file,'.')), 'iosr:checkMexCompiled:invalidFile', 'source_file: no file extension specified') 32 | 33 | % Locate source file 34 | [pathstr,name,ext] = fileparts(which(source_file)); 35 | 36 | filename = [pathstr filesep name ext]; % Create filename 37 | mexfilename = [pathstr filesep name '.' mexext]; % Deduce mex file name based on current platform 38 | 39 | if strcmp(pathstr,'') % source file not found 40 | error('iosr:checkMexCompiled:fileNotFound',[source_file ': not found']) 41 | elseif exist(mexfilename,'file')~=3 || get_mod_date(mexfilename)1 48 | options = varargin{1:end-1}; 49 | mex(options,source_file) 50 | else 51 | mex(source_file) 52 | end 53 | disp('Done.') 54 | cd(d) 55 | end 56 | 57 | end 58 | 59 | function datenum = get_mod_date(file) 60 | %GET_MOD_DATE get file modified date 61 | 62 | d = dir(file); 63 | datenum = d.datenum; 64 | 65 | end 66 | -------------------------------------------------------------------------------- /+iosr/+auditory/dupWeight.m: -------------------------------------------------------------------------------- 1 | function [w_itd,w_ild] = dupWeight(f) 2 | %DUPWEIGHT Calculate duplex weighting coefficients for ITD and ILD 3 | % 4 | % [W_ITD,W_ILD] = IOSR.AUDITORY.DUPWEIGHT(F) returns the weighting 5 | % coefficients for ITD W_ITD and ILD W_ILD for frequency F (Hz). 6 | % 7 | % The function is derived from Ben Supper's thesis "An onset-guided 8 | % spatial analyser for binaural audio", with some necessary 9 | % modifications. Specifically, his work accounts for the ITD dominance 10 | % condition. This function does not, as this cannot be derived from the 11 | % work, since the work assumes an upper frequency limit of 13750 Hz 12 | % (which cannot be assumed in this function). The quadratic ramp in ITD 13 | % weighting is approximated linearly here. 14 | % 15 | % The input f may be an array of any size. The outputs will be the same 16 | % size as f, with coefficients calculated for each element. 17 | % 18 | % See also IOSR.AUDITORY.LOUDWEIGHT. 19 | 20 | % Copyright 2016 University of Surrey. 21 | 22 | %% Check input 23 | 24 | assert(all(f(:)>=0), 'iosr:functionalBoxPlot:invalidF', 'f should be greater than or equal to zero!') 25 | 26 | if any(f(:)>20000) 27 | warning('iosr:dupWeight:frequencyRange','Humans cannot generally hear above 20 kHz. Weighting coefficients will be set to zero.') 28 | end 29 | 30 | %% Calculate original weights using Ben's numbers 31 | 32 | % Frequency scale in Ben's thesis 33 | freq_scale = [60 150 250 350 455 570 700 845 1000 1175 ... 34 | 1375 1600 1860 2160 2510 2925 3425 4050 4850 5850 ... 35 | 7050 8600 10750 13750]; 36 | 37 | % Corresponding bin numbers 38 | b = 1:24; 39 | 40 | % frequency ranges (indices) 41 | low = b<=8; % below cross-over region 42 | mid = b>8 & b<14; % cross-over region 43 | high = b>=14; % above cross-over region 44 | 45 | % pre-allocate outputs 46 | w_itd_orig = zeros(size(freq_scale)); 47 | w_ild_orig = zeros(size(freq_scale)); 48 | 49 | % Do maths 50 | w_itd_orig(low) = 1.597-(0.047*b(low)); 51 | w_itd_orig(mid) = (0.0102.*(b(mid).^2))-(0.437.*b(mid))+4.06; 52 | w_itd_orig(high) = 0.11; 53 | % 54 | w_ild_orig(low) = 0.2; 55 | w_ild_orig(mid) = (0.152.*b(mid))-1.016; 56 | w_ild_orig(high) = 0.96; 57 | 58 | %% Calculate weights for input frequencies 59 | 60 | % ...via interpolation 61 | w_itd = interp1(freq_scale,w_itd_orig,f,'pchip','extrap'); 62 | w_ild = interp1(freq_scale,w_ild_orig,f,'pchip','extrap'); 63 | 64 | w_itd(f>20000) = 0; 65 | w_ild(f>20000) = 0; 66 | 67 | end 68 | -------------------------------------------------------------------------------- /+iosr/+dsp/convFft.m: -------------------------------------------------------------------------------- 1 | function c = convFft(a,b,shape) 2 | %CONVFFT Convolve two vectors using FFT multiplication 3 | % 4 | % Convolution using traditional overlapping methods can be slow for very 5 | % long signals. A more efficient method is to multiply the FFTs of the 6 | % signals and take the inverse FFT of the result. However, this comes at 7 | % a cost: FFT-based convolution is subject to floating-point round-off 8 | % errors, and requires more memory [1]. 9 | % 10 | % C = IOSR.DSP.CONVFFT(A,B) convolves vectors A and B. The resulting 11 | % vector is length: length(A)+length(B)-1. 12 | % 13 | % C = IOSR.DSP.CONVFFT(A,B,SHAPE) returns a subsection of the convolution 14 | % with size specified by SHAPE: 15 | % 'full' - (default) returns the full convolution, 16 | % 'same' - returns the central part of the convolution that is the 17 | % same size as a. 18 | % 'valid' - returns only those parts of the convolution that are 19 | % computed without the zero-padded edges. length(c) is 20 | % L-2*(min(P,Q)-1) where P = numel(a), Q = numel(b), 21 | % L = P+Q-1. 22 | % 23 | % Based on code written by Steve Eddins, 2009. 24 | % 25 | % References 26 | % 27 | % [1] http://blogs.mathworks.com/steve/2009/11/03/ 28 | % the-conv-function-and-implementation-tradeoffs/ 29 | % 30 | % See also CONV. 31 | 32 | % Copyright 2016 University of Surrey. 33 | 34 | assert(isvector(a) & isvector(b), 'iosr:convFft:invalidInput', 'a and b must be vectors') 35 | 36 | if nargin<3 37 | shape = 'full'; 38 | end 39 | assert(ischar(shape), 'iosr:convFft:invalidShape', 'Unknown shape parameter') 40 | 41 | P = numel(a); 42 | Q = numel(b); 43 | L = P + Q - 1; 44 | K = 2^nextpow2(L); 45 | 46 | % do the convolution 47 | afft = fft(a, K); 48 | bfft = fft(b, K); 49 | c = ifft(afft(:).*bfft(:)); 50 | 51 | % specify index range for shape 52 | switch lower(shape) 53 | case 'full' 54 | range = [1, L]; 55 | case 'same' 56 | range = [floor(length(b)/2)+1, L-ceil(length(b)/2)+1]; 57 | case 'valid' 58 | range = [min(P,Q), L-min(P,Q)+1]; 59 | otherwise 60 | error('iosr:convFft:unknownShape',['shape ''' shape ''' is invalid. The options are ''full'' (default), ''same'', or ''valid''.']) 61 | end 62 | 63 | % crop to shape 64 | c = c(range(1):range(2)); 65 | 66 | % restore orientation 67 | if shape(1) == 'f' 68 | if length(a) > length(b) 69 | if size(a,1) == 1 %row vector 70 | c = c.'; 71 | end 72 | else 73 | if size(b,1) == 1 %row vector 74 | c = c.'; 75 | end 76 | end 77 | else 78 | if size(a,1) == 1 %row vector 79 | c = c.'; 80 | end 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /+iosr/+dsp/istft.m: -------------------------------------------------------------------------------- 1 | function [x,t] = istft(s,nfft,hop,fs) 2 | %ISTFT Calculate the Inverse Short-Time Fourier Transform 3 | % 4 | % X = IOSR.DSP.ISTFT(S) calculates the inverse short-time Fourier 5 | % transform (ISTFT), from the 1024-point one-sided FFTs in each column of 6 | % S, using the overlap-add method. It is assumed that FFTs are calculated 7 | % in steps of HOP=512. X is a column vector of length (HOP*K)+(NFFT-HOP) 8 | % where K is SIZE(X,2). 9 | % 10 | % X = IOSR.DSP.ISTFT(S,NFFT) uses FFT-length NFFT and HOP=FIX(NFFT/2). 11 | % 12 | % X = IOSR.DSP.ISTFT(S,NFFT,HOP) uses the hop size HOP. 13 | % 14 | % [X,T] = IOSR.DSP.ISTFT(S,NFFT,HOP,FS) returns the corresponding time T 15 | % (seconds) for each element of X based on sampling frequency FS. 16 | % 17 | % Example 18 | % 19 | % % create a spectrogram using a Hann window 20 | % % and convert back to the time domain 21 | % load handel.mat 22 | % nfft = 1024; 23 | % hop = 128; 24 | % Y = iosr.dsp.stft(y,hann(nfft),hop); 25 | % z = iosr.dsp.istft(Y,nfft,hop); 26 | % 27 | % See also IOSR.DSP.STFT. 28 | 29 | % Copyright 2016 University of Surrey. 30 | 31 | %% check input 32 | 33 | assert(ismatrix(s), 'iosr:istft:invalidS', 'S must be a matrix') 34 | 35 | % check nfft 36 | if nargin<2 37 | nfft = 1024; 38 | else 39 | assert(isscalar(nfft) && round(nfft)==nfft && nfft>0, 'iosr:istft:invalidNfft', 'NFFT must be a positive scalar integer') 40 | end 41 | 42 | % determine hop size 43 | if nargin<3 44 | hop = fix(nfft/2); 45 | else 46 | assert(isscalar(hop) & round(hop)==hop, 'iosr:istft:invalidHop', 'hop must be an integer') 47 | assert(hop<=nfft && hop>0, 'iosr:istft:invalidHop', 'hop must be less than or equal to nfft, and greater than 0') 48 | end 49 | 50 | % determine fs 51 | if nargin<4 52 | fs = 1; 53 | else 54 | assert(isscalar(fs), 'iosr:istft:invalidFs', 'FS must be an scalar') 55 | end 56 | 57 | %% calculate outputs 58 | 59 | % calculate number of frames and samples 60 | K = size(s,2); 61 | M = (hop*K)+(nfft-hop); 62 | 63 | % calculate highest bin and correction for one-sided FFT 64 | if mod(nfft,2)==0 65 | Nout = (nfft/2)+1; 66 | ec = 1; 67 | else 68 | Nout = (nfft+1)/2; 69 | ec = 0; 70 | end 71 | 72 | % check FFT size 73 | assert(size(s,1)==Nout, 'iosr:istft:invalidS', sprintf('SIZE(S,1) is not correct for an FFT-length of %d',nfft)) 74 | 75 | % calculate ISTFTs 76 | x = zeros(M,1); 77 | for k = 1:K 78 | samples = ((k-1)*hop)+1:((k-1)*hop)+nfft; 79 | Xtemp = [s(:,k); conj(s(end-ec:-1:2,k))]; 80 | x(samples) = x(samples)+ifft(Xtemp); 81 | end 82 | 83 | % calculate time 84 | if nargout>1 85 | t = (0:M-1)./fs; 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /+iosr/+auditory/binSearch.m: -------------------------------------------------------------------------------- 1 | % Conduct a binary search 2 | % 3 | % binSearch: Conducts a binary search and continues until the score is 4 | % close enough to the target. An upper limit should be set on the number of 5 | % iterations 6 | % 7 | % [SearchPoint, Step, ReLoop] = iosr.auditory.binSearch(Score, ... 8 | % TargetScore, ... 9 | % CloseEnough, ... 10 | % NextSearchPointa, ... 11 | % Step, ... 12 | % LoopCount, ... 13 | % MaxLoops); 14 | % end 15 | % 16 | % inputs: 17 | % - Score: the test value 18 | % - Target: the 'finished' test value 19 | % - CloseEnough: the distance from the Target at which it is acceptable to 20 | % discontinue the binary search 21 | % - SearchPoint: the next work input value to try 22 | % - Step: the distance which the SearchPoint can move by 23 | % - LoopCount: a counter for the number of iterations completed so far 24 | % - MaxLoops: The upper limit on the number of iterations allowed 25 | % 26 | % outputs: 27 | % - SearchPoint: the next work input value you should try 28 | % - Step: the distance which can be stepped on the NEXT iteration. You 29 | % should store this value. 30 | % - ReLoop: If this flag is set to 0, the upper level while loop will be 31 | % terminated 32 | % 33 | % 34 | % example: you wish to use an audibility model to find the level at which 35 | % you can be 50% confident of detecting the signal (-+ 1%). You choose to 36 | % start with an input level of 20dB and take no more than 10 steps of 37 | % 40,20,10 dB etc. 38 | % 39 | % You would implement this in the following way: 40 | % 41 | % TargetPercent = 0.5; 42 | % CloseEnough = 0.01; 43 | % Signal Level = 40; 44 | % Step = 40; 45 | % MaxLoops = 10; 46 | % LoopCount = 0; 47 | % 48 | % while ReLoop = 1 49 | % 50 | % LoopCount = LoopCount + 1; 51 | % 52 | % Percent = RunAudibilityModel(SignalLevel) 53 | % 54 | % [SignalLevel Step ReLoop] = iosr.auditory.binSearch(Percent, ... 55 | % TargetPercent, ... 56 | % CloseEnough, ... 57 | % SignalLevel, ... 58 | % Step, ... 59 | % LoopCount, ... 60 | % MaxLoops); 61 | % 62 | % end 63 | % 64 | 65 | % Copyright 2016 University of Surrey. 66 | 67 | function [SearchPoint, Step, ReLoop] = binSearch(Score, Target, CloseEnough, SearchPoint, Step, LoopCount, MaxLoops) 68 | 69 | % input tests 70 | % test input types 71 | assert(isnumeric(Score) ... 72 | &isnumeric(Target) ... 73 | &isnumeric(CloseEnough) ... 74 | &isnumeric(SearchPoint) ... 75 | &isnumeric(Step) ... 76 | &isnumeric(LoopCount) ... 77 | &isnumeric(MaxLoops),'input arguments must be numeric!'); 78 | 79 | 80 | % Define next search point 81 | if Score > Target 82 | SearchPoint = SearchPoint - Step; 83 | else 84 | SearchPoint = SearchPoint + Step; 85 | end 86 | 87 | % half the distance of the next step 88 | Step = Step / 2; 89 | 90 | % if we got close enough to our taget, or it was the last iteration 91 | if (abs((Score-Target))=MaxLoops) 92 | ReLoop = 0; 93 | else 94 | ReLoop = 1; 95 | end 96 | 97 | end 98 | -------------------------------------------------------------------------------- /+iosr/+svn/headRev.m: -------------------------------------------------------------------------------- 1 | function [rev,file] = headRev(folders,strs,rec) 2 | %HEADREV Retrieve the head revision for specified files 3 | % 4 | % This function finds the most recently committed file in the specified 5 | % folders(s) and returns its revision and filename. The files must 6 | % contain the 'Revision' and 'Date' keywords. 7 | % 8 | % REV = IOSR.SVN.HEADREV(FOLDERS) returns the head revision (i.e. the 9 | % highest revision) for all files in FOLDERS. 10 | % 11 | % REV = IOSR.SVN.HEADREV(FOLDERS,STRS) allows additional filter strings 12 | % STRS to be specified. See GETCONTENTS for details of permitted filter 13 | % strings. 14 | % 15 | % REV = IOSR.SVN.HEADREV(FOLDERS,STRS,REC), with REC = true, allows 16 | % folders to be searched recursively (default is false). 17 | % 18 | % [REV,FILE] = IOSR.SVN.HEADREV(...) returns the filename to FILE of the 19 | % file with the highest revision. 20 | % 21 | % FOLDERS and STRS may be a string specifying a single occurrence, or a 22 | % cell array of strings specifying multiple occurrences. This function 23 | % requires that the 'Revision' keyword is used in the searched functions. 24 | % 25 | % Note that if the folders include files from an externally-defined 26 | % repository (which has been updated more recently than the native 27 | % repository), a misleading revision number may be presented. 28 | % 29 | % See also IOSR.GENERAL.GETCONTENTS, IOSR.SVN.BUILDSVNPROFILE, 30 | % IOSR.SVN.READSVNKEYWORD. 31 | 32 | % Copyright 2016 University of Surrey. 33 | 34 | keyword = 'Date'; % use this data to sort files (doubtful that any others would work, without considerable parsing) 35 | 36 | if ischar(folders) 37 | folders = cellstr(folders); 38 | end 39 | 40 | if nargin > 1 41 | if ischar(strs) 42 | strs = cellstr(strs); 43 | end 44 | if isempty(strs) 45 | strs = {'files'}; 46 | end 47 | else 48 | strs = {'files'}; 49 | end 50 | 51 | % add subfolders if recursion is requested 52 | if nargin > 2 53 | if rec 54 | for f = folders 55 | folders = [folders; iosr.general.getContents(char(f),'filter','folders','rec',true,'path','full')]; %#ok 56 | end 57 | end 58 | end 59 | 60 | % get revision info for files 61 | svn_profile = iosr.svn.buildSvnProfile(folders,keyword,strs); 62 | 63 | % remove the keyword and convert revision strings to numbers 64 | svn_profile(:,2) = cellfun(@(x) (x(length(keyword)+3:end)),svn_profile(:,2),'uni',false); 65 | 66 | % remove NaN (files that don't have the revision keyword) 67 | IX1 = cellfun(@isempty,svn_profile(:,2)); 68 | svn_profile = svn_profile(~IX1,:); 69 | 70 | % sort by revision number 71 | [~,IX2] = sort(svn_profile(:,2)); 72 | svn_profile = svn_profile(IX2,:); 73 | 74 | % get highest revision number 75 | rev = iosr.svn.readSvnKeyword(svn_profile{end,1},'Revision'); 76 | 77 | % return corresponding filename, if requested 78 | if nargout>1 79 | file = svn_profile{end,1}; 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /+iosr/+dsp/vsmooth.m: -------------------------------------------------------------------------------- 1 | function y = vsmooth(x,frame,mode) 2 | %VSMOOTH Smooth a vector using mathematical functions 3 | % 4 | % Y = IOSR.DSP.VSMOOTH(X,FRAME) smooths the input vector X by calculating 5 | % the running RMS over a series of frames. FRAME specifies the frame 6 | % characteristics; it can be set to: 7 | % 8 | % a scalar - this will be used as the length of the frame, the window 9 | % will be rectangular 10 | % a vector - this specifies the shape of the analysis window, the 11 | % frame length will be length(frame). 12 | % 13 | % Y = IOSR.DSP.VSMOOTH(X,FRAME,MODE) allows the user to specify a 14 | % different mathematical smoothing function in MODE. The options are: 15 | % 16 | % 'rms' - calculates the running rms (default) 17 | % 'mean' - calculates the running mean (moving average filter) 18 | % 'median' - calculates the running median 19 | % 20 | % NOTE: vsmooth uses a vectorized implementation that may be slow when x 21 | % and/or frame are very large. The number of elements that are used for 22 | % calculation is length(X)*max(FRAME,length(FRAME)). The algorithm 23 | % vectorizes the operation by creating a matrix of indexes and extracting 24 | % its diagonals. E.g. for a vector of length 4 and frame_length of 2, the 25 | % algorithm creates a temporary zero-padded matix x2 from which it 26 | % creates a set of indexes: 27 | % 28 | % 1 1 29 | % 2 2 30 | % 3 3 31 | % 4 4 32 | % 5 5 33 | % 6 6 34 | % 35 | % It then extracts the diagonals where -length(x2) + frame_length <= k <= 36 | % 0, yielding: 37 | % 38 | % 1 2 39 | % 2 3 40 | % 3 4 41 | % 4 5 42 | % 43 | % this is used to index x2; operations are then performed along the rows. 44 | 45 | % Copyright 2016 University of Surrey. 46 | 47 | %% Gather inputs 48 | 49 | assert(isvector(x), 'iosr:vsmooth:vectorInput', '''x'' must be a vector') 50 | 51 | if isscalar(frame) 52 | frame_length = frame; 53 | window = ones(frame_length,1); 54 | elseif isvector(frame) 55 | window = frame; 56 | frame_length = length(frame); 57 | else 58 | error('iosr:vsmooth:frameInvalid','''frame'' must be a vector or a scalar') 59 | end 60 | 61 | if nargin<3 62 | mode = 'rms'; 63 | end 64 | 65 | %% Smooth 66 | 67 | % zero pad 68 | x2 = [zeros(ceil((frame_length)/2)-1,1); x(:); zeros(floor(frame_length/2),1)]; 69 | 70 | samples = (1:length(x2))'; 71 | samples = samples(:,ones(frame_length,1)); 72 | 73 | % get indexes 74 | index = spdiags(samples,0:-1:-length(x2)+frame_length); 75 | 76 | window2 = window(:,ones(1,length(x))); 77 | 78 | % do calculations 79 | switch lower(mode) 80 | case 'rms' 81 | y = sqrt(mean((window2.*x2(index)).^2)); 82 | case 'median' 83 | y = median((window2.*x2(index))); 84 | case 'mean' 85 | y = mean((window2.*x2(index))); 86 | otherwise 87 | error('iosr:vsmooth:unknownMode','Unknown ''mode'' specified') 88 | end 89 | 90 | % transpose if necessary 91 | if size(y,1)~=size(x,1) 92 | y = y'; 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /+iosr/+dsp/smoothSpectrum.m: -------------------------------------------------------------------------------- 1 | function x_oct = smoothSpectrum(X,f,Noct) 2 | %SMOOTHSPECTRUM Apply 1/N-octave smoothing to a frequency spectrum 3 | % 4 | % X_OCT = IOSR.DSP.SMOOTHSPECTRUM(X,F,NOCT) applies 1/NOCT-octave 5 | % smoothing to the frequency spectrum contained in vector X sampled at 6 | % frequencies in vector F. X can be a log-, magnitude-, or 7 | % power-spectrum. Setting Noct to 0 results in no smoothing. 8 | % 9 | % Algorithm 10 | % 11 | % The function calculates the i-th smoothed spectral coefficient X_OCT(i) 12 | % as the sum of the windowed spectrum. The window is a Gaussian whose 13 | % centre frequency is F(i), and whose standard deviation is proportional 14 | % to F(i)/NOCT. 15 | % 16 | % Example 17 | % 18 | % % Calculate the 1/3-octave-smoothed power spectral density of the 19 | % % Handel example. 20 | % 21 | % % load signal 22 | % load handel.mat 23 | % 24 | % % take fft 25 | % Y = fft(y); 26 | % 27 | % % keep only meaningful frequencies 28 | % NFFT = length(y); 29 | % if mod(NFFT,2)==0 30 | % Nout = (NFFT/2)+1; 31 | % else 32 | % Nout = (NFFT+1)/2; 33 | % end 34 | % Y = Y(1:Nout); 35 | % f = ((0:Nout-1)'./NFFT).*Fs; 36 | % 37 | % % put into dB 38 | % Y = 20*log10(abs(Y)./NFFT); 39 | % 40 | % % smooth 41 | % Noct = 3; 42 | % Z = iosr.dsp.smoothSpectrum(Y,f,Noct); 43 | % 44 | % % plot 45 | % figure 46 | % semilogx(f,Y,f,Z) 47 | % grid on 48 | % 49 | % See also IOSR.DSP.LTAS, FFT. 50 | 51 | % Copyright 2016 University of Surrey. 52 | 53 | %% Input checking 54 | 55 | assert(isvector(X), 'iosr:smoothSpectrum:invalidX', 'X must be a vector.'); 56 | assert(isvector(f), 'iosr:smoothSpectrum:invalidF', 'F must be a vector.'); 57 | assert(isscalar(Noct), 'iosr:smoothSpectrum:invalidNoct', 'NOCT must be a scalar.'); 58 | assert(isreal(X), 'iosr:smoothSpectrum:invalidX', 'X must be real.'); 59 | assert(all(f>=0), 'iosr:smoothSpectrum:invalidF', 'F must contain positive values.'); 60 | assert(Noct>=0, 'iosr:smoothSpectrum:invalidNoct', 'NOCT must be greater than or equal to 0.'); 61 | assert(isequal(size(X),size(f)), 'iosr:smoothSpectrum:invalidInput', 'X and F must be the same size.'); 62 | 63 | %% Smoothing 64 | 65 | % calculates a Gaussian function for each frequency, deriving a 66 | % bandwidth for that frequency 67 | 68 | x_oct = X; % initial spectrum 69 | if Noct > 0 % don't bother if no smoothing 70 | for i = find(f>0,1,'first'):length(f) 71 | g = gauss_f(f,f(i),Noct); 72 | x_oct(i) = sum(g.*X); % calculate smoothed spectral coefficient 73 | end 74 | % remove undershoot when X is positive 75 | if all(X>=0) 76 | x_oct(x_oct<0) = 0; 77 | end 78 | end 79 | 80 | end 81 | 82 | function g = gauss_f(f_x,F,Noct) 83 | % GAUSS_F calculate frequency-domain Gaussian with unity gain 84 | % 85 | % G = GAUSS_F(F_X,F,NOCT) calculates a frequency-domain Gaussian function 86 | % for frequencies F_X, with centre frequency F and bandwidth F/NOCT. 87 | 88 | sigma = (F/Noct)/pi; % standard deviation 89 | g = exp(-(((f_x-F).^2)./(2.*(sigma^2)))); % Gaussian 90 | g = g./sum(g); % normalise magnitude 91 | 92 | end 93 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | # v2.8 - 17th June 2017 2 | 3 | - Added kernelDensity function. 4 | - boxPlot: Added ‘violin’ methods and properties for making violin plots. 5 | - boxPlot: Added various violin-related options. Also modified the scatter offset to use kernel density rather than histogram. 6 | - boxPlot: Added themeColor property, allowing the various colours for the theme to be changed via a single property. 7 | - calcSnr: Prevent rank deficient warning when output is zeros. 8 | 9 | # v2.7 - 28th March 2017 10 | 11 | - Added true functional boxplot. 12 | - Added alternative perceptual centroid function. 13 | - Added caching of various dependent properties to iosr.bss.mixture class. 14 | - Added identifiers to all warnings and errors. 15 | - Updated loudness weighting calculations for A and C to better match IEC 61672. Added normalisation ISO 226 curve at 1kHz to bring it in to line with other weighting functions. 16 | - Improved extrapolation of ISO 226 function. Added params output to return reference values. 17 | - Updated SOFA API version in installer. 18 | - Bug fixes and documentation improvements. 19 | 20 | # v2.6 - 6th March 2017 21 | 22 | Added a number of properties and methods to the iosr.bss.mixture class. Added whiskers property to iosr.statistics.functionalSpreadPlot. Other minor bug fixes and documentation improvements. 23 | 24 | # v2.5 - 20th February 2017 25 | 26 | Generalised iosr.bss.mixture class to support arbitrary spatial configurations, as well as none. Added decomposition properties to allow direct calculation of ideal masks, and application of masks. Currently, only STFT is supported; will add gammatone in due course. Added a number of of measures related to the mask and to signal overlap. 27 | 28 | A few bug fixes. 29 | 30 | # v2.4.1 - 18th January 2017 31 | 32 | Added optional first argument to boxPlot and functionalSpreadPlot to specify axes in which to plot. 33 | 34 | # v2.4 - 15th December 2016 35 | 36 | Added functionalSpreadPlot for making functional box plots and related plots. Also added statsPlot class and moved some boxPlot methods to the class, as they are shared with functionalSpreadPlot. 37 | 38 | # v2.3.2 - 13th October 2016 39 | 40 | * Added check for OCTAVE in irStats, and a basic check for a unique peak. 41 | * Corrected mistake in boxPlot documentation (pulled from TGabor). 42 | * Moved important bits of documentation into chXcorr.m (the only separately-documented portion of the code). 43 | 44 | # v2.3.1 - 25th July 2016 45 | 46 | * Minor tweak to magnitude calculation in matchEQ. 47 | * Redefined TIR in mixture class as target w.r.t. sum of interfering sources. Updated documentation. 48 | 49 | # v2.3 - 19th July 2016 50 | 51 | * Corrected code to restore current directory when installation is complete. 52 | * Added function to generate BSS mixtures by combining sources in various ways. 53 | * Added property to iosr.bss.mixture to return interferer filenames as char array. Also corrected bug setting properties when interferer comprises multiple sources. 54 | 55 | # v2.2.3 - 12th July 2016 56 | 57 | Corrected boxPlot bug whereby x-separator line would disappear when setting y-axis limits to inf. 58 | 59 | # v2.2.2 - 10th July 2016 60 | 61 | Corrected calls to other toolbox functions. 62 | 63 | # v2.2.1 - 8th July 2016 64 | 65 | Fixed erroneous default 'method' in boxPlot. 66 | 67 | # v2.2 - 7th July 2016 68 | 69 | Added install function that downloads dependencies and sets path. Fixed bug in boxPlot where some set functions check the old value rather than the new value. 70 | 71 | # v2.1 - 22nd June 2016 72 | 73 | Added match EQ function. 74 | 75 | # v2 - 6th June 2016 76 | 77 | Restructured toolbox into a Matlab package in order to appropriately restrict namespace. Consolidated some file/function names into a consistent format. 78 | -------------------------------------------------------------------------------- /+iosr/+statistics/getRmse.m: -------------------------------------------------------------------------------- 1 | function RMSE = getRmse(X, y, varargin) 2 | %GETRMSE Calculate the root-mean-square error between input data 3 | % 4 | % RMSE = IOSR.STATISTICS.GETRMSE(X, Y) calculates the RMSE of the inputs 5 | % data where 6 | % 7 | % RMSE = sqrt( 1/N-D * SUM(err^2) ) 8 | % err = abs(X-Y) 9 | % 10 | % The input data X and Y can be vectors or matrices; D=1. X and Y must 11 | % have equal size. 12 | % 13 | % RMSE = IOSR.STATISTICS.GETRMSE(X, Y, D) allows the degrees of freedom D 14 | % to be specified. The default is D=1 (you may wish to set it to 0, or 15 | % the degrees of freedom). 16 | % 17 | % RMSEEPS = IOSR.STATISTICS.GETRMSE(X, Y, D, EPSILON) calculates 18 | % epsilon-insensitive RMSE (RMSE*). The parameter EPSILON is a threshold 19 | % for err values: err values less than the respective EPSILON value are 20 | % set to 0; err values greater than EPSILON have EPSILON subtracted from 21 | % them. This allows for the calculation of error 'after' a certain effect 22 | % (such as subjective error). RMSEEPS will ALWAYS be lower than RMSE. The 23 | % value of EPSILON will generally be set to half the 95% confidence 24 | % interval or similar 25 | 26 | % Copyright 2016 University of Surrey. 27 | 28 | % test input types 29 | assert(isnumeric(X)&isnumeric(y),'Input data must be numeric!'); 30 | assert(length(size(X))==length(size(y)),'Input data X and y must be the same size'); 31 | assert(all(size(X)==size(y)),'Input data X and y must be the same size'); 32 | 33 | % extract inputs 34 | nInputs = (numel(varargin) + 2); 35 | d = -1; 36 | epsilon = -1; 37 | epsFlag = 0; 38 | 39 | if nInputs == 2 40 | if numel(X)>1 41 | d = 1; 42 | else 43 | error('you should not calculate RMSE for a singular') 44 | end 45 | elseif nInputs == 3 46 | if ( varargin{1} < numel(X) ) 47 | d = varargin{1}; 48 | else 49 | error('d cannot be larger than or equal to N'); 50 | end 51 | elseif nInputs == 4 52 | if ( varargin{1} < numel(X) ) 53 | d = varargin{1}; 54 | else 55 | error('d cannot be larger than or equal to N'); 56 | end 57 | if ( varargin{2} >= 0) 58 | epsilon = varargin{2}; 59 | else 60 | error('epsilon should not be negative!'); 61 | end 62 | epsFlag = 1; 63 | elseif (nInputs<2) || (nInputs>4) 64 | error('should be >1 and <5 input arguments') 65 | end 66 | 67 | % Test RMSE == 0 when input vectors identical 68 | assert(Calc(1:10,1:10,1,0,0)==0,'RMSE of x=Y for equal vectors does not compute correctly'); 69 | assert(Calc(ones(3,3,3),ones(3,3,3),1,0,0)==0,'RMSE of x=Y for equal matrices does not compute correctly'); 70 | 71 | % Test RMSE calculated properly for dummy data 72 | assert(Calc([1 2 3; 1 2 3; 1 2 3],[3 2 1; 3 2 1; 3 2 1],1,0,0)==(sqrt(3)),'RMSE of x=Y for dummy data does not compute correctly'); 73 | 74 | % Test RMSEeps calculated properly for dummy data 75 | assert(single(Calc([1 2 3],[1.4 2.4 3.4],1,0.3,1))==single(sqrt(0.015)),'RMSE epsilon insensitive for dummy data does not compute correctly'); 76 | assert(single(Calc([1 2 3],[1.4 2.4 3.4],1,[0.3 0.4 0.4],1))==single(sqrt(0.005)),'RMSE epsilon insensitive for dummy data does not compute correctly'); 77 | 78 | RMSE = Calc(X,y,d,epsilon,epsFlag); 79 | 80 | end 81 | 82 | function RMSE = Calc(X,y,d,epsilon,epsFlag) 83 | %CALC Calculate RMSE & RMSE* 84 | 85 | % Calculate RMSE 86 | err = abs(X-y); 87 | if epsFlag 88 | err = max(err-epsilon,0); 89 | end 90 | RMSE = sqrt((1/(numel(err)-d)) * sum(err(:).^2)); 91 | 92 | end 93 | -------------------------------------------------------------------------------- /+iosr/+dsp/sincFilter.m: -------------------------------------------------------------------------------- 1 | function y = sincFilter(x,Wn,N,dim) 2 | %SINCFILTER Apply a near-ideal low-pass or band-pass brickwall filter 3 | % 4 | % Y = IOSR.DSP.SINCFILTER(X,WN) applies a near-ideal low-pass or 5 | % band-pass brickwall filter to the array X, operating along the first 6 | % non-singleton dimension (e.g. down the columns of a matrix). The 7 | % cutoff frequency/frequencies are specified in WN. If WN is a scalar, 8 | % then WN specifies the low-pass cutoff frequency. If WN is a two-element 9 | % vector, then WN specifies the band-pass interval. WN must be 0.0 < WN < 10 | % 1.0, with 1.0 corresponding to half the sample rate. 11 | % 12 | % The filtering is performed by FFT-based convolution of X with the sinc 13 | % kernel. 14 | % 15 | % Y = IOSR.DSP.SINCFILTER(X,WN,N) allows the filter length to be 16 | % specified. The default value is N=1025. The filter length is doubled in 17 | % the band-pass case. In either case, if N is even the final filter 18 | % length will be N+1. 19 | % 20 | % Y = IOSR.DSP.SINCFILTER(X,WN,N,DIM) applies the specified filter along 21 | % the dimension DIM. 22 | % 23 | % Y = IOSR.DSP.SINCFILTER(X,WN,[],DIM) applies the specified filter along 24 | % the dimension dim using the default filter length. 25 | % 26 | % See also IOSR.DSP.CONVFFT. 27 | 28 | % Copyright 2016 University of Surrey. 29 | 30 | %% test input 31 | 32 | assert(nargin>=2, 'iosr:sincFilter:nargin', 'Not enough input arguments') 33 | assert((numel(Wn)==1 || numel(Wn)==2) & isnumeric(Wn), 'iosr:sincFilter:invalidWn', 'Wn must be a scalar or two-element vector.') 34 | assert(isnumeric(x), 'iosr:sincFilter:invalidX', 'x must be a numeric array.') 35 | assert(all(Wn<=1) & all(Wn>=0), 'iosr:sincFilter:invalidWn', 'Wn must be 0.0 < Wn < 1.0, with 1.0 corresponding to half the sample rate.') 36 | dims = size(x); 37 | if nargin<4 38 | dim = []; 39 | else 40 | assert(isint(dim) & numel(dim)==1, 'iosr:sincFilter:invalidDim', 'dim must be an integer') 41 | assert(dim<=length(dims), 'iosr:sincFilter:invalidDim', 'dim must be less than or equal to the number of dimensions in x') 42 | end 43 | if nargin<3 44 | N = []; 45 | elseif ~isempty(N) 46 | assert(isscalar(N) & isint(N), 'iosr:sincFilter:invalidN', 'N must be an integer scalar.') 47 | end 48 | 49 | %% assign defaults 50 | 51 | if isempty(N) 52 | N = 1025; 53 | end 54 | if isempty(dim) 55 | dim = find(dims>1,1,'first'); 56 | end 57 | 58 | %% reshape input to matrix with requested dim made as first dimension 59 | 60 | % reshape data so function works down columns 61 | order = mod(dim-1:dim+length(dims)-2,length(dims))+1; 62 | x = permute(x,order); 63 | dims_shift = dims(order); 64 | x = reshape(x,dims_shift(1),numel(x)/dims_shift(1)); 65 | y = zeros(size(x)); 66 | 67 | %% create filter kernel 68 | 69 | if numel(Wn)==1 % low-pass 70 | n = -floor(N/2):floor(N/2); % create time base 71 | B = sinc_kernel(Wn,n); % make kernel 72 | else % band-pass 73 | n = -N:N; % create time base 74 | B = sinc_kernel(Wn(2),n)-sinc_kernel(Wn(1),n); % make kernel 75 | end 76 | 77 | %% apply filter 78 | 79 | for N = 1:size(y,2) 80 | y(:,N) = iosr.dsp.convFft(x(:,N),B,'same'); 81 | end 82 | 83 | %% reshape out to match input 84 | 85 | y = reshape(y,dims_shift); 86 | y = ipermute(y,order); 87 | 88 | end 89 | 90 | function k = sinc_kernel(Wn,n) 91 | % SINC_KERNEL Make sinc kernel 92 | 93 | k = sinc(Wn*n).*Wn; 94 | 95 | end 96 | 97 | function y = isint(x) 98 | % ISINT Test whether x is integer value (not type) 99 | 100 | y = x==round(x); 101 | 102 | end 103 | -------------------------------------------------------------------------------- /+iosr/+auditory/lindemannInh.m: -------------------------------------------------------------------------------- 1 | function [L_l,R_l] = lindemannInh(L,R,fs,c_inh,dim) 2 | %LINDEMANNINH Signal pre-processing for Lindemann's cross-correlation 3 | % 4 | % [L_L,R_L] = IOSR.AUDITORY.LINDEMANNINH(L,R,FS) pre-processes left L and 5 | % right R signals for the cross-correlation function based on Lindemann's 6 | % precedence model [1,2]. A crucial parameter for Lindemann's model is 7 | % the "operating point", which controls the amount of inhibition. The 8 | % parameter is actually a function of the input related to its RMS level. 9 | % After half-wave rectifying and filtering the input signals L and R 10 | % (sampled at FS Hz) along the first non-singleton dimension, this 11 | % function applies a gain related to the inhibition parameter C_INH 12 | % (default is 0.3). The gain is identical for all rows, columns, etc. 13 | % Lastly, values outside of the interval [0,1] are not permitted in 14 | % Lindemann's model and hence these values are clipped. 15 | % 16 | % [L_L,R_L] = IOSR.AUDITORY.LINDEMANNINH(L,R,FS,C_INH) uses the specified 17 | % inhibition parameter C_INH. The value must be in the interval [0,1]. 18 | % 19 | % [L_L,R_L] = IOSR.AUDITORY.LINDEMANNINH(L,R,FS,C_INH,DIM) pre-processes 20 | % L and R along the dimension DIM. 21 | % 22 | % References 23 | % 24 | % [1] Lindemann, W. (1986), Extension of a binaural cross-correlation 25 | % model by contralateral inhibition. I. Simulation of lateralization 26 | % for stationary signals, The Journal of the Acoustical Society of 27 | % America 80, 6, 1608-1622. 28 | % 29 | % [2] Lindemann, W. (1986), Extension of a binaural cross-correlation 30 | % model by contralateral inhibition. II. The law of the first wave 31 | % front, The Journal of the Acoustical Society of America 80, 6, 32 | % 1623-1630. 33 | % 34 | % Further reading 35 | % 36 | % Hummersone, C., Mason, R., Brookes, T. (2013), A comparison of 37 | % computational precedence models for source separation in 38 | % reverberant environments, The Journal of the Audio Engineering 39 | % Society 61, 7/8, 508-520. 40 | % 41 | % See also IOSR.AUDITORY.XCORRLINDEMANN. 42 | 43 | % Copyright 2016 University of Surrey. 44 | 45 | %% check input 46 | 47 | assert(isequal(size(L),size(R)), 'iosr:lindemannInh:invalidInputs', 'L and R arrays must be the same size') 48 | assert(isscalar(fs), 'iosr:lindemannInh:invalidFs', 'FS must be a scalar') 49 | 50 | % default dim 51 | if nargin<5 52 | dim = find(size(L)>1,1,'first'); 53 | end 54 | 55 | %% process 56 | 57 | % half-wave rectify 58 | L_l = hwr(L); 59 | R_l = hwr(R); 60 | 61 | % filter 62 | cutofffreq=800; 63 | [b,a] = butter(1,cutofffreq*2/fs); 64 | L_l = filter(b,a,L_l,[],dim); 65 | R_l = filter(b,a,R_l,[],dim); 66 | 67 | % inhibition parameter 68 | if nargin < 4 69 | c_inh = .3; 70 | else 71 | assert(isscalar(c_inh) & isnumeric(c_inh), 'iosr:lindemannInh:invalidCinh', 'c_inh must be a scalar'); 72 | assert(c_inh>=0 || c_inh<=1, 'iosr:lindemannInh:invalidX', 'c_inh must be in the interval (0,1].') 73 | end 74 | 75 | % gain 76 | c_gamma_L = calc_gamma(L,dim); 77 | c_gamma_R = calc_gamma(R,dim); 78 | c_gamma = max([c_gamma_L(:); c_gamma_R(:)]); 79 | 80 | % apply parameters 81 | L_l = apply_gamma(L_l,c_inh,c_gamma); 82 | R_l = apply_gamma(R_l,c_inh,c_gamma); 83 | 84 | % restrict range 85 | L_l = clip(L_l); 86 | R_l = clip(R_l); 87 | 88 | end 89 | 90 | function y = hwr(x) 91 | %HWR half-wave rectify 92 | 93 | y = max(x,0); 94 | 95 | end 96 | 97 | function gamma = calc_gamma(x,dim) 98 | %CALC_GAMMA calculate the gamma 99 | 100 | gamma = sqrt(2).*iosr.dsp.rms(x,dim); 101 | 102 | end 103 | 104 | function y = apply_gamma(x,c_inh,c_gamma) 105 | %APPLY_GAMMA apply gamma to achieve inhibition parameter 106 | 107 | y = (c_inh/c_gamma).*x; 108 | 109 | end 110 | 111 | function y = clip(x) 112 | %CLIP clip input data to [0,1] interval 113 | 114 | y = x; 115 | y(y<0) = 0; 116 | y(y>1) = 1; 117 | 118 | end 119 | -------------------------------------------------------------------------------- /+iosr/+bss/applyIdealMasks.m: -------------------------------------------------------------------------------- 1 | function [z_irm,z_ibm,t] = applyIdealMasks(xt,xi,nfft,hop,fs) 2 | %APPLYIDEALMASKS Calculate and apply ideal masks via STFT 3 | % 4 | % Z_IRM = IOSR.BSS.APPLYIDEALMASKS(XT,XI) calculates the ideal ratio mask 5 | % (IRM) and applies it to the mixture XT+XI, where XT is the target 6 | % signal and XI is the interference signal. The IRM is calculated via the 7 | % STFT using 1024-point windows with 512-point overlap. Z_IRM, XT, and XI 8 | % are vectors. If XT and XI are of different lengths then the shorter 9 | % signal is zero-padded in order to make them the same length; Z_IRM is 10 | % the same length as XT and XI. 11 | % 12 | % Z_IRM = IOSR.BSS.APPLYIDEALMASKS(XT,XI,NFFT) uses NFFT-length segments 13 | % in the STFT. 14 | % 15 | % Z_IRM = IOSR.BSS.APPLYIDEALMASKS(XT,XI,WINDOW) uses 16 | % LENGTH(WINDOW)-length segments in the STFT and applies WINDOW to each 17 | % segment. 18 | % 19 | % Z_IRM = IOSR.BSS.APPLYIDEALMASKS(XT,XI,WINDOW,HOP) uses hop size HOP 20 | % for the STFT. 21 | % 22 | % [Z_IRM,Z_IBM] = IOSR.BSS.APPLYIDEALMASKS(...) calculates the ideal 23 | % binary mask (IBM) and applies it to the mixture, returning the result 24 | % to Z_IBM. 25 | % 26 | % [Z_IRM,Z_IBM,T] = IOSR.BSS.APPLYIDEALMASKS(XT,XI,WINDOW,HOP,FS) uses 27 | % sampling frequency FS to return the corresponding time T of each 28 | % element in Z_IRM and Z_IBM. 29 | % 30 | % See also IOSR.DSP.STFT, IOSR.DSP.ISTFT, IOSR.BSS.IDEALMASKS, 31 | % IOSR.BSS.APPLYMASKS. 32 | 33 | % Copyright 2016 University of Surrey. 34 | 35 | %% check input 36 | 37 | % check signals 38 | assert(isvector(xt) && numel(xt)>1, 'iosr:applyIdealMasks:invalidXt', 'XT must be a vector') 39 | assert(isvector(xi) && numel(xi)>1, 'iosr:applyIdealMasks:invalidXi', 'XI must be a vector') 40 | 41 | % make equal length 42 | maxlength = max([length(xi) length(xt)]); 43 | xt = pad(xt,maxlength); 44 | xi = pad(xi,maxlength); 45 | 46 | % check nfft 47 | if nargin<3 48 | nfft = 1024; 49 | end 50 | 51 | % determine window 52 | if numel(nfft)>1 53 | win = nfft; 54 | assert(isvector(win), 'iosr:applyIdealMasks:invalidWin', 'WINDOW must be a vector') 55 | nfft = length(win); 56 | else 57 | assert(round(nfft)==nfft && nfft>0, 'iosr:applyIdealMasks:invalidNfft', 'NFFT must be a positive integer') 58 | win = hamming(nfft); 59 | end 60 | 61 | % check x length 62 | assert(length(xt)>=nfft, 'iosr:applyIdealMasks:invalidXt', 'XT must have at least NFFT samples') 63 | assert(length(xi)>=nfft, 'iosr:applyIdealMasks:invalidXi', 'XI must have at least NFFT samples') 64 | 65 | % determine hop 66 | if nargin<4 67 | hop = fix(nfft/2); 68 | else 69 | assert(isscalar(hop) & round(hop)==hop, 'iosr:applyIdealMasks:invalidHop', 'HOP must be an integer') 70 | assert(hop<=nfft && hop>0, 'iosr:applyIdealMasks:invalidHop', 'HOP must be less than or equal to NFFT, and greater than 0') 71 | end 72 | 73 | % determine fs 74 | if nargin<5 75 | fs = 1; 76 | else 77 | assert(isscalar(fs), 'iosr:applyIdealMasks:invalidFs', 'FS must be an scalar') 78 | end 79 | 80 | %% calculate outputs 81 | 82 | % STFTs of signals and mixture 83 | st = iosr.dsp.stft(xt,win,hop); 84 | si = iosr.dsp.stft(xi,win,hop); 85 | mix = iosr.dsp.stft(xt+xi,win,hop); 86 | 87 | % return ideal masks 88 | [irm,ibm] = iosr.bss.idealMasks(st,si); 89 | 90 | % apply IRM 91 | z_irm = iosr.bss.applyMask(mix,irm,nfft,hop,fs); 92 | z_irm = pad(z_irm,maxlength); 93 | 94 | % apply IBM 95 | if nargout>1 96 | z_ibm = iosr.bss.applyMask(mix,ibm,nfft,hop,fs); 97 | z_ibm = pad(z_ibm,maxlength); 98 | end 99 | 100 | % calculate t 101 | if nargout>2 102 | t = (0:length(z_irm)-1)./fs; 103 | end 104 | 105 | end 106 | 107 | function y = pad(x,dur) 108 | %PAD Zero-pad a vector 109 | 110 | if length(x)1, 'iosr:stft:invalidX', 'X must be a vector') 54 | 55 | % check nfft 56 | if nargin<2 57 | nfft = 1024; 58 | end 59 | 60 | % determine window 61 | if numel(nfft)>1 62 | win = nfft; 63 | assert(isvector(win), 'iosr:stft:invalidNfft', 'WINDOW must be a vector') 64 | nfft = length(win); 65 | else 66 | assert(round(nfft)==nfft && nfft>0, 'iosr:stft:invalidNfft', 'NFFT must be a positive integer') 67 | win = hamming(nfft); 68 | end 69 | 70 | % check x length 71 | assert(length(x)>=nfft, 'iosr:stft:invalidInput', 'X must have at least NFFT samples') 72 | 73 | % determine hop 74 | if nargin<3 75 | hop = fix(nfft/2); 76 | else 77 | assert(isscalar(hop) & round(hop)==hop, 'iosr:stft:invalidHop', 'HOP must be an integer') 78 | assert(hop<=nfft && hop>0, 'iosr:stft:invalidHop', 'HOP must be less than or equal to NFFT, and greater than 0') 79 | end 80 | 81 | % normalise window 82 | win = hop.*win./sum(win); 83 | 84 | % determine fs 85 | if nargin<4 86 | fs = 1; 87 | else 88 | assert(isscalar(fs), 'iosr:stft:invaldFs', 'FS must be an scalar') 89 | end 90 | 91 | %% calculate outputs 92 | 93 | % calculate highest bin for one-sided FFT 94 | if mod(nfft,2)==0 95 | Nout = (nfft/2)+1; 96 | else 97 | Nout = (nfft+1)/2; 98 | end 99 | 100 | % calculate number of frames 101 | K = fix((length(x)-(nfft-hop))/hop); 102 | 103 | % calculate STFTs 104 | s = zeros(Nout,K); 105 | for k = 1:K 106 | samples = ((k-1)*hop)+1:((k-1)*hop)+nfft; 107 | temp = fft(win.*x(samples)); 108 | s(:,k) = temp(1:Nout); 109 | end 110 | 111 | % calculate frequency and time 112 | if nargout>1 113 | f = fs.*((0:Nout-1)./nfft)'; 114 | end 115 | if nargout>2 116 | t = (0:K-1).*(hop/fs); 117 | end 118 | 119 | end 120 | -------------------------------------------------------------------------------- /+iosr/+auditory/xcorrLindemann.m: -------------------------------------------------------------------------------- 1 | function [c,lags] = xcorrLindemann(L,R,fs,maxlag,dim) 2 | %XCORRLINDEMANN Cross-correlation based on Lindemann's precedence model 3 | % 4 | % C = IOSR.AUDITORY.XCORRLINDEMANN(L,R,FS) calculates the 5 | % cross-correlation of vectors L and R, sampled at FS Hz, using a maximum 6 | % cross-correlation lag of 0.001*fs samples (1 ms). The cross-correlation 7 | % function is based on Lindemann's [1,2] precedence model. C is the same 8 | % size as L or R, except that size(C,1) = 2*0.001*fs+1. 9 | % 10 | % C = IOSR.AUDITORY.XCORRLINDEMANN(L,R,FS,MAXLAG) calculates the 11 | % cross-correlation using a maximum cross-correlation lag of MAXLAG 12 | % samples. 13 | % 14 | % C = IOSR.AUDITORY.XCORRLINDEMANN(L,R,FS,MAXLAG,DIM) performs the 15 | % cross-correlation along the dimension DIM. C is the same size as L or 16 | % R, except that size(C,DIM) = 2*MAXLAG+1. 17 | % 18 | % [C,LAGS] = IOSR.AUDITORY.XCORRLINDEMANN(...) returns the lags, in 19 | % seconds, over which the cross-correlation was calculated. 20 | % 21 | % References 22 | % 23 | % [1] Lindemann, W. (1986), Extension of a binaural cross-correlation 24 | % model by contralateral inhibition. I. Simulation of lateralization 25 | % for stationary signals, The Journal of the Acoustical Society of 26 | % America 80, 6, 1608-1622. 27 | % 28 | % [2] Lindemann, W. (1986), Extension of a binaural cross-correlation 29 | % model by contralateral inhibition. II. The law of the first wave 30 | % front, The Journal of the Acoustical Society of America 80, 6, 31 | % 1623-1630. 32 | % 33 | % See also IOSR.AUDITORY.LINDEMANNINH, XCORR. 34 | 35 | % Copyright 2016 University of Surrey. 36 | 37 | %% check input 38 | assert(isequal(size(L),size(R)), 'iosr:xcorrLindemann:invalidSignals', 'L and R must be the same size'); 39 | assert(isscalar(fs) & isnumeric(fs), 'iosr:xcorrLindemann:invalidFs', 'FS must be a scalar'); 40 | 41 | % check for maxlag 42 | if nargin<4 43 | maxlag = 0.001*fs; 44 | else 45 | assert(isscalar(maxlag) & isnumeric(maxlag), 'iosr:xcorrLindemann:invalidMaxlag', 'MAXLAG must be a scalar'); 46 | end 47 | 48 | % check for dim 49 | dims = size(L); 50 | if nargin<5 51 | dim = find(dims>1,1,'first'); 52 | else 53 | assert(isscalar(dim) && round(dim)==dim, 'iosr:xcorrLindemann:invalidDim', 'DIM must be an integer scalar') 54 | end 55 | 56 | % check L and R have a valid range 57 | if any(L(:)<0) || any(R(:)<0) || any(L(:)>1) || any(R(:)>1) 58 | error('iosr:xcorrLindemann:inputOutOfRange','L and/or R contain values outside of the range [0,1]. This is not allowed. Use LINDEMANN_INH to pre-process the L and R inputs.') 59 | end 60 | 61 | % check C function is compiled 62 | iosr.general.checkMexCompiled('-largeArrayDims',fullfile(fileparts(mfilename('fullpath')),'xcorrLindemann_c.c')) 63 | 64 | %% re-arrange input 65 | 66 | % re-arrange array to operate along columns 67 | order = mod(dim-1:dim+length(dims)-2,length(dims))+1; 68 | dims_shift = dims(order); 69 | L = rearrange(L,order,[dims_shift(1),numel(L)/dims_shift(1)]); 70 | R = rearrange(R,order,[dims_shift(1),numel(R)/dims_shift(1)]); 71 | 72 | %% do cross-correlation 73 | 74 | % time constants 75 | t_int = 5; % ms 76 | t_inh = 10; % ms 77 | 78 | xcorr_length = round(2*maxlag)+1; % length of cross-correlation 79 | c = zeros(xcorr_length,size(L,2)); % pre-allocate 80 | for n = 1:size(L,2) 81 | c(:,n) = iosr.auditory.xcorrLindemann_c(L(:,n),R(:,n),fs,maxlag,t_inh,t_int); 82 | end 83 | 84 | %% return values 85 | 86 | % rearrange to match input dimensions 87 | c = irearrange(c,order,[xcorr_length dims_shift(2:end)]); 88 | 89 | % return lags 90 | if nargin>1 91 | lags = (-maxlag:maxlag)./fs; 92 | end 93 | 94 | end 95 | 96 | function y = rearrange(x,dim_order,shape) 97 | %REARRANGE rearrange data to 2-D matrix with target dim as column 98 | 99 | y = permute(x,dim_order); 100 | y = reshape(y,shape); 101 | 102 | end 103 | 104 | function y = irearrange(x,dim_order,shape) 105 | %IREARRANGE inverse rearrangement 106 | 107 | y = reshape(x,shape); 108 | y = ipermute(y,dim_order); 109 | 110 | end 111 | -------------------------------------------------------------------------------- /+iosr/+auditory/gammatoneFast.m: -------------------------------------------------------------------------------- 1 | function [bm,env,delay] = gammatoneFast(x,cfs,fs,align) 2 | %GAMMATONEFAST Produce an array of responses from gammatone filters via FFT 3 | % 4 | % BM = IOSR.AUDITORY.GAMMATONEFAST(X,CFS,FS) passes the vector X through 5 | % a bank of fourth-order gammatone filters, with centre frequencies 6 | % specified by CFS. The function returns a matrix, with each row/column 7 | % corresponding to a filter output with a centre frequency determined by 8 | % the corresponding element in CFS. The orientation of the output is 9 | % determined by the orientation of the input: if X is a row vector then 10 | % the output will contain one row for each filter output, and vice versa. 11 | % 12 | % Centre frequencies may be any value below the Nyquist rate (determined 13 | % by the sampling frequency fs). Typically centre frequencies are equally 14 | % spaced on the ERB-rate scale and may be calculated thus: 15 | % 16 | % CFS = iosr.auditory.makeErbCFs(LOW_CF,HIGH_CF,NUMCHANS) 17 | % 18 | % where LOW_CF is the lowest frequency in the bank, HIGH_CF is the 19 | % highest, and NUMCHANS is the numbers of filters in the bank. 20 | % 21 | % BM = IOSR.AUDITORY.GAMMATONEFAST(...,ALIGN) allows phase alignment to 22 | % be applied. With ALIGN=false, no alignment is applied (default). With 23 | % ALIGN=true, fine structure and envelope alignment is applied so that 24 | % the impulse response peaks occurs at t=0. 25 | % 26 | % [BM,ENV] = IOSR.AUDITORY.GAMMATONEFAST(...) returns the instantaneous 27 | % envelopes ENV for each filter. 28 | % 29 | % [BM,ENV,DELAY] = IOSR.AUDITORY.GAMMATONEFAST(...) returns the delay 30 | % DELAY (in samples) removed by the phase alignment of each gammatone 31 | % filter, i.e. DELAY(n)=0 if ALIGN=true. DELAY is a vector the same size 32 | % as CFS. 33 | % 34 | % Based on code written by ZZ Jin, adapted by DLW in Jan'07 and JF 35 | % Woodruff in Nov'08 36 | % 37 | % See also IOSR.AUDITORY.MAKEERBCFS. 38 | 39 | % Copyright 2016 University of Surrey. 40 | 41 | if nargin < 3 42 | fs = 16000; % default sampling frequency 43 | end 44 | if nargin < 4 45 | align = false; % default phase alignment 46 | end 47 | 48 | % check inputs 49 | assert(isvector(x) & isnumeric(x), 'iosr:gammatoneFast:invalidX', 'x must be a vector') 50 | assert(isvector(cfs) & isnumeric(cfs), 'iosr:gammatoneFast:invalidCfs', 'cfs must be a vector') 51 | assert(isscalar(fs), 'iosr:gammatoneFast:invalidFs', 'fs must be a scalar') 52 | assert(islogical(align) & numel(align)==1, 'iosr:gammatoneFast:invalidAlign', 'align must be logical') 53 | 54 | % number of frequency channels 55 | numchans = length(cfs); 56 | 57 | filterOrder = 4; % filter order 58 | gL = 2^nextpow2(0.128*fs); % gammatone filter length at least 128 ms 59 | b = 1.019.*24.7.*(4.37.*cfs./1000+1); % rate of decay or bandwidth 60 | 61 | gt = zeros(gL,numchans); % Initialise IR 62 | tc = zeros(size(cfs)); % Initialise time lead 63 | phase = 0; 64 | 65 | tpt=(2*pi)/fs; 66 | gain=((1.019.*b.*tpt).^filterOrder)./6; % based on integral of impulse 67 | 68 | tmp_t = (0:gL-1)/fs; 69 | 70 | % calculate impulse response 71 | for i = 1:numchans 72 | if align 73 | tc(i) = (filterOrder-1)./(2*pi*b(i)); 74 | phase = -2*pi*cfs(i)*tc(i); 75 | end 76 | gt(:,i) = gain(i)*fs^3*tmp_t.^(filterOrder-1).*exp(-2*pi*b(i)*tmp_t).*cos(2*pi*cfs(i)*tmp_t+phase); 77 | end 78 | 79 | % if input is row vector, transpose to column vector 80 | rot = false; 81 | if size(x,1)==1 82 | x = x'; 83 | rot = true; 84 | end 85 | 86 | % gammatone filtering using FFTFILT 87 | bm = fftfilt(gt,repmat(x,1,numchans)); 88 | 89 | % Hilbert envelope 90 | env = abs(hilbert(bm)); 91 | 92 | % delay due to time lead 93 | delay = round(tc.*fs); 94 | 95 | % remove time lead 96 | for i = 1:numchans 97 | bm(:,i) = [bm(delay(i)+1:end,i); zeros(delay(i),1)]; 98 | env(:,i) = [env(delay(i)+1:end,i); zeros(delay(i),1)]; 99 | end 100 | 101 | % transpose output if necessary 102 | if rot 103 | bm = bm'; 104 | env = env'; 105 | end 106 | 107 | end 108 | -------------------------------------------------------------------------------- /+iosr/+svn/buildSvnProfile.m: -------------------------------------------------------------------------------- 1 | function [svn_profile,svn_str] = buildSvnProfile(folders,keywords,strs,rec) 2 | %BUILDSVNPROFILE Read data from files tagged with SVN keywords 3 | % 4 | % This function extracts SVN keyword data from specified files, and 5 | % returns the filename, path and keyword data to a cell array (and 6 | % optional char array). 7 | % 8 | % Keyword data are placed in files automatically by Subversion at each 9 | % commit using the 'keyword' function. The data can be useful in 10 | % maintaining an audit trail, or establishing the version used to 11 | % generate a particular set of results. 12 | % 13 | % SVN_PROFILE = IOSR.SVN.BUILDSVNPROFILE(FOLDERS,KEYWORDS) returns a cell 14 | % array SVN_PROFILE containing data associated with the SVN keywords 15 | % KEYWORDS. The function will search through files contained in folders. 16 | % 17 | % SVN_PROFILE is a two dimensional cell array; there is one row for each 18 | % file found in FOLDERS, and N columns: one each for the full path for 19 | % the file, and appended columns for each specified keyword in KEYWORDS. 20 | % Data are only returned for files that contain the specified keyword(s). 21 | % An empty cell array, 0-by-2, is returned if no keywords are found. 22 | % 23 | % SVN_PROFILE = IOSR.SVN.BUILDSVNPROFILE(FOLDERS,KEYWORDS,STRS) allows 24 | % additional filter strings STRS to be specified. See 25 | % IOSR.GENERAL.GETCONTENTS for details of permitted filter strings. 26 | % 27 | % ... = IOSR.SVN.BUILDSVNPROFILE(FOLDERS,KEYWORDS,STRS,REC), with REC = 28 | % true, allows folders to be searched recursively (default is false). 29 | % 30 | % [SVN_PROFILE,SVN_STR] = IOSR.SVN.BUILDSVNPROFILE(...) outputs a char 31 | % array SVN_STR of the profile, with columns concatenated to produce 32 | % continuous lines. Spaces are inserted to ensure that columns are 33 | % aligned (in a monospaced font). The array is appropriate for printing 34 | % or writing to a text file, for example. The char array can be written 35 | % to a text file with the following command: 36 | % 37 | % dlmwrite('svn_profile.txt',svn_str,'delimiter','') 38 | % 39 | % FOLDERS, KEYWORDS,and STRS may be a char array specifying a single 40 | % occurrence, or a cell array of strings specifying multiple occurrences. 41 | % 42 | % See also IOSR.SVN.READSVNKEYWORD, IOSR.GENERAL.GETCONTENTS. 43 | 44 | % Copyright 2016 University of Surrey. 45 | 46 | if ischar(folders) 47 | folders = cellstr(folders); 48 | end 49 | 50 | if ischar(keywords) 51 | keywords = cellstr(keywords); 52 | end 53 | 54 | if nargin > 2 55 | if ischar(strs) 56 | strs = cellstr(strs); 57 | end 58 | else 59 | strs = {'files'}; 60 | end 61 | 62 | % add subfolders if recursion is requested 63 | if nargin > 3 64 | if rec 65 | for f = folders 66 | folders = [folders; iosr.general.getContents(char(f),'filter','folders','rec',true,'path','full')]; %#ok 67 | end 68 | end 69 | end 70 | 71 | keydata = cell(0,1+length(keywords)); % empty cell array to be appended 72 | n = 1; 73 | for f = 1:length(folders) 74 | folder = folders{f}; 75 | for s = 1:length(strs) 76 | str = strs{s}; 77 | [files,dirflag] = iosr.general.getContents(folder,'filter',str,'path','full'); % find relevant files 78 | files = files(~dirflag); % ignore directories 79 | if ~isempty(files) 80 | for h = 1:length(files) 81 | file = files{h}; 82 | % extract keyword data from file 83 | for k = 1:length(keywords) 84 | keyword = keywords{k}; 85 | keydata{n,1} = file; 86 | keydata{n,1+k} = iosr.svn.readSvnKeyword(file,keyword); 87 | end 88 | n = n+1; 89 | end 90 | end 91 | end 92 | end 93 | 94 | % remove files where no keyword is found 95 | IX = true(size(keydata(:,2))); 96 | for k = 1:length(keywords) 97 | IX = IX & cellfun(@isempty,keydata(:,1+k)); 98 | end 99 | svn_profile = keydata(~IX,:); 100 | 101 | % Build char array version of data 102 | space = repmat(' ',size(svn_profile(:,1))); % spaces to insert between columns 103 | svn_str = char(svn_profile(:,1)); % first column (file path) 104 | % append keyword columns: 105 | for k = 1:length(keywords) 106 | svn_str = [svn_str space char(svn_profile(:,1+k))]; %#ok 107 | end 108 | 109 | end 110 | -------------------------------------------------------------------------------- /+iosr/+auditory/loudWeight.m: -------------------------------------------------------------------------------- 1 | function g = loudWeight(f,phon) 2 | %LOUDWEIGHT Calculate loudness weighting coefficients 3 | % 4 | % G = IOSR.AUDITORY.LOUDWEIGHT(F) returns loudness-weighting linear- 5 | % magnitude coefficients for frequencies F (Hz). The function is based on 6 | % the loudness at 65 phons defined in ISO 226:2003. The coefficients are 7 | % scaled such that the G = 1 when F = 1000. 8 | % 9 | % G = IOSR.AUDITORY.LOUDWEIGHT(F,PHON) returns loudness weighting 10 | % coefficients at the loudness level PHON. PHON should be a scalar and, 11 | % according to the standard, is only valid at all frequencies such that 12 | % 20<=PHON<80 (although the function will return extrapolated 13 | % coefficients outside of this range). 14 | % 15 | % The input f may be an array of any size. The outputs will be the same 16 | % size as f, with coefficients calculated for each element. 17 | % 18 | % G = IOSR.AUDITORY.LOUDWEIGHT(F,METHOD) returns loudness weighting 19 | % coefficients for a variety of methods. Specifying METHOD as 'A', 'C', 20 | % or 'Z' selects frequency weighting curves defined in IEC 61672-1:2013; 21 | % 'ISO-226' selects a loudness weighting curve derived from ISO 226:2003 22 | % at 65 phons; 'B' selects a frequency weighting curve defined in IEC 23 | % 60651:1979; 'D' selects a weighting curve defined in IEC 537:1976. 24 | % 25 | % See also IOSR.AUDITORY.ISO226, IOSR.AUDITORY.DUPWEIGHT. 26 | 27 | % Copyright 2016 University of Surrey. 28 | 29 | %% Check input 30 | 31 | assert(all(f(:)>=0), 'iosr:loudWeight:invalidF', 'f should be greater than or equal to zero!') 32 | 33 | if nargin<2 34 | phon = 65; 35 | end 36 | 37 | if ischar(phon) 38 | method = phon; 39 | if strcmpi(method,'iso-226') 40 | phon = 65; 41 | end 42 | elseif isnumeric(phon) 43 | method = 'iso-226'; 44 | assert(isscalar(phon), 'iosr:loudWeight:invalidPhon', 'phon must be a scalar.') 45 | else 46 | error('iosr:loudWeight:invalidArg','Second argument should be a scalar or char array.') 47 | end 48 | 49 | %% coefficients 50 | 51 | fr = 1000; 52 | fL = 10^1.5; 53 | fH = 10^3.9; 54 | fA = 10^2.45; 55 | D = sqrt(0.5); 56 | b = (1/(1-D)) * ((fr^2) + (((fL^2)*(fH^2))/(fr^2)) - (D*((fL^2)+(fH^2)))); 57 | c = (fL^2)*(fH^2); 58 | f1 = sqrt((-b - sqrt((b^2) - (4*c))) / (2)); 59 | f4 = sqrt((-b + sqrt((b^2) - (4*c))) / (2)); 60 | f2 = ((3 - sqrt(5)) / 2) * fA; 61 | f3 = ((3 + sqrt(5)) / 2) * fA; 62 | 63 | %% Calculate weighting coefficients 64 | 65 | switch lower(method) 66 | case 'a' 67 | g = a_weighting(f); 68 | case 'b' 69 | g = b_weighting(f); 70 | case 'c' 71 | g = c_weighting(f); 72 | case 'd' 73 | g = d_weighting(f); 74 | case 'z' 75 | g = z_weighting(f); 76 | case 'iso-226' 77 | % calculate weighting coefficients 78 | gdB = iosr.auditory.iso226(phon, 1000) - ... 79 | squeeze(iosr.auditory.iso226(phon, f)); 80 | g = 10.^(gdB./20); 81 | otherwise 82 | error('iosr:loudWeight:unknownMethod','Unknown method.') 83 | end 84 | 85 | function w = a_weighting(f) 86 | %A_WEIGHTING return A-weighting magnitude coefficients 87 | function w = calculate(f) 88 | w = ((f4^2).*(f.^4))./... 89 | ( ((f.^2)+(f1^2)) .* sqrt((f.^2)+(f2^2)) .* sqrt((f.^2)+(f3^2)) .* ((f.^2)+(f4^2)) ); 90 | end 91 | w = calculate(f)./calculate(1000); 92 | end 93 | 94 | function w = b_weighting(f) 95 | %B_WEIGHTING return B-weighting magnitude coefficients 96 | w = ((12200^2).*(f.^3))./... 97 | (((f.^2)+(20.6^2)).*sqrt((f.^2)+(158.5^2)).*((f.^2)+(12200^2))); 98 | end 99 | 100 | function w = c_weighting(f) 101 | %C_WEIGHTING return C-weighting magnitude coefficients 102 | function w = calculate(f) 103 | w = ((f4^2).*(f.^2)) ./... 104 | ( ((f.^2)+(f1^2)) .* ((f.^2)+(f4^2)) ); 105 | end 106 | w = calculate(f)./calculate(1000); 107 | end 108 | 109 | function w = d_weighting(f) 110 | %D_WEIGHTING return D-weighting magnitude coefficients 111 | hf = (((1037918.48-(f.^2)).^2)+(1080768.16.*(f.^2)))./... 112 | (((9837328-(f.^2)).^2)+(11723776.*(f.^2))); 113 | w = (f./(6.8966888496476*(10^(-5)))).*sqrt(hf./(((f.^2)+79919.29).*((f.^2)+1345600))); 114 | end 115 | 116 | function w = z_weighting(f) 117 | %Z_WEIGHTING return Z-weighting magnitude coefficients 118 | w = ones(size(f)); 119 | end 120 | 121 | end 122 | -------------------------------------------------------------------------------- /+iosr/+auditory/xcorrLindemann_c.c: -------------------------------------------------------------------------------- 1 | /* Lindemann's precedence model. 2 | * 3 | * Copyright 2016 University of Surrey. 4 | * 5 | */ 6 | 7 | #include "math.h" 8 | #include "mex.h" 9 | 10 | #define TEMP_ARRAY mxCreateDoubleMatrix((mwSize)length_c,(mwSize)1,mxREAL) 11 | #define IN_ARRAY mxCreateDoubleMatrix((mwSize)n_sample,(mwSize)1,mxREAL) 12 | 13 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 14 | { 15 | /* ====================== INPUTS & SCALARS ====================== */ 16 | 17 | double *L = mxGetPr(prhs[0]), /* pointer to left signal input */ 18 | *R = mxGetPr(prhs[1]), /* pointer to right signal input */ 19 | fs = *mxGetPr(prhs[2]); /* samle frequency */ 20 | 21 | mwSize maxlag = *mxGetPr(prhs[3]); /* maximum lag */ 22 | 23 | double tinh = (*mxGetPr(prhs[4]))/1000.0, /* fade-off time constant */ 24 | tint = (*mxGetPr(prhs[5]))/1000.0, /* integration time constant */ 25 | td = 0.5*(1.0/fs), /* half sample period */ 26 | alpha = exp(-td/tinh), /* fade-off factor */ 27 | alpha2 = exp(-td/tint), /* integration factor */ 28 | Mf = 6.0, /* fading constant for monaural sensitivity */ 29 | wf = 0.035; /* monaural sensitivity of a correlator to the signal at the end of a delay line */ 30 | 31 | /* sizes */ 32 | mwSize numsamples = mxGetM(prhs[0]), /* number of samples */ 33 | length_c = (2*maxlag)+1, /* length of cross-correlation (2*maxlag)+1 */ 34 | in_length = length_c+numsamples-1, /* length of input to cross-correlation */ 35 | n_sample = 2*in_length-1; /* length of inhibited input to cross-correlation */; 36 | 37 | /* indices */ 38 | mwIndex n, /* sample */ 39 | m, /* lag */ 40 | leftn, /* left samples to be cross-correlated */ 41 | rightn; /* right samples to be cross-correlated */ 42 | 43 | /* ====================== TEMP ARRAYS ====================== */ 44 | 45 | /* monaural sensitivity functions */ 46 | mxArray *wl_mx,*wr_mx; 47 | wl_mx = TEMP_ARRAY; 48 | wr_mx = TEMP_ARRAY; 49 | double *wl = mxGetPr(wl_mx), 50 | *wr = mxGetPr(wr_mx); 51 | for ( m = 0; m < length_c; m++ ) { 52 | wl[m] = wf*exp(-((double)m)/Mf); 53 | wr[m] = wf*exp(((double)m-(double)length_c+1.0)/Mf); 54 | } 55 | 56 | /* running cross-correlation */ 57 | mxArray *k_mx; 58 | k_mx = TEMP_ARRAY; 59 | double *k = mxGetPr(k_mx); 60 | 61 | /* inhibited input to the cross-correlation */ 62 | mxArray *lc_mx,*rc_mx; 63 | lc_mx = TEMP_ARRAY; 64 | rc_mx = TEMP_ARRAY; 65 | double *lc = mxGetPr(lc_mx), 66 | *rc = mxGetPr(rc_mx); 67 | 68 | /* input to cross-correlation */ 69 | mxArray *lin_mx,*rin_mx; 70 | lin_mx = IN_ARRAY; 71 | rin_mx = IN_ARRAY; 72 | double *lin = mxGetPr(lin_mx), 73 | *rin = mxGetPr(rin_mx); 74 | 75 | /* inhibitory components */ 76 | mxArray *il_mx,*ir_mx; 77 | il_mx = TEMP_ARRAY; 78 | ir_mx = TEMP_ARRAY; 79 | double *il = mxGetPr(il_mx), 80 | *ir = mxGetPr(ir_mx); 81 | 82 | /* dynamic inhibitory component for current and previous samples */ 83 | mxArray *phin_mx,*phic_mx; 84 | phin_mx = TEMP_ARRAY; 85 | phic_mx = TEMP_ARRAY; 86 | double *phin = mxGetPr(phin_mx), 87 | *phic = mxGetPr(phic_mx); 88 | 89 | /* inhibited cross-correlation for current and previous samples */ 90 | mxArray *sumn_mx,*sumc_mx; 91 | sumn_mx = TEMP_ARRAY; 92 | sumc_mx = TEMP_ARRAY; 93 | double *sumn = mxGetPr(sumn_mx), 94 | *sumc = mxGetPr(sumc_mx); 95 | 96 | /* ====================== OUTPUT ====================== */ 97 | 98 | plhs[0] = TEMP_ARRAY; 99 | double *c_out = mxGetPr(plhs[0]); 100 | 101 | /* ====================== CROSS-CORRELATE ====================== */ 102 | 103 | for ( n = 0 ; n < numsamples ; n++ ) { 104 | /* input to cross-correlation */ 105 | lin[(2*n)] = L[n]; 106 | rin[(2*n)] = R[n]; 107 | } 108 | for ( n = 0; n < n_sample; n++ ) { 109 | for ( m = 0; m < 2*maxlag; m++ ) { 110 | /* inhibit input to cross-correlation */ 111 | lc[m] = lc[m+1]*il[m+1]; 112 | rc[(2*maxlag)-m] = rc[(2*maxlag)-m-1]*ir[(2*maxlag)-m-1]; 113 | } 114 | lc[length_c-1] = lin[n]; 115 | rc[0] = rin[n]; 116 | /* cross-correlate */ 117 | for ( m = 0; m < length_c; m++ ) { 118 | k[m] = (wl[m]+(1.0-wl[m])*rc[m])*(wr[m]+(1.0-wr[m])*lc[m]); 119 | phin[m] = k[m]+alpha*phic[m]*(1-k[m]); 120 | phic[m]=phin[m]; 121 | il[m]=(1.0-rc[m])*(1-phic[m]); 122 | ir[m]=(1.0-lc[m])*(1-phic[m]); 123 | sumn[m]=sumc[m]*alpha2+(1.0-alpha2)*k[m]; 124 | sumc[m]=sumn[m]; 125 | c_out[m] += sumn[m]; 126 | } 127 | } 128 | /* Destroy mx arrays */ 129 | mxDestroyArray(wl_mx); 130 | mxDestroyArray(wr_mx); 131 | mxDestroyArray(k_mx); 132 | mxDestroyArray(lc_mx); 133 | mxDestroyArray(rc_mx); 134 | mxDestroyArray(il_mx); 135 | mxDestroyArray(ir_mx); 136 | mxDestroyArray(phin_mx); 137 | mxDestroyArray(phic_mx); 138 | mxDestroyArray(sumn_mx); 139 | mxDestroyArray(sumc_mx); 140 | mxDestroyArray(lin_mx); 141 | mxDestroyArray(rin_mx); 142 | return; 143 | } 144 | -------------------------------------------------------------------------------- /+iosr/+auditory/chXcorr2.m: -------------------------------------------------------------------------------- 1 | function [ccg,ic] = chXcorr2(hc_L,hc_R,fs,varargin) 2 | %CHXCORR2 Calculate cross-correlograms with a range of options. 3 | % 4 | % CCG = IOSR.AUDITORY.CHXCORR2(HC_L,HC_R,FS) cross-correlates the input 5 | % 2-D matrices HC_L and HC_R over 10ms frame with a maximum lag of 1ms. 6 | % It is assumed that the number of frequency channels is min(size(HC_L)) 7 | % and hence HC_L and HC_R can be in either orientation. The 8 | % cross-correlograms consist of cross-correlations for every frame and 9 | % frequency channel. CCG has dimensions [lag,frequency,frame]. The 10 | % function calculates the traditional cross-correlation in each frame. 11 | % The number of frames FRAME_COUNT is calculated thus: 12 | % 13 | % FRAME_COUNT = FIX((MAX(SIZE(HC_L)))/FRAME_LENGTH); 14 | % 15 | % CCG = IOSR.AUDITORY.CHXCORR2(HC_L,HC_R,FS,'PARAMETER',VALUE) allows a 16 | % number of options to be specified. The options are: 17 | % 18 | % ({} indicates the default value) 19 | % 20 | % 'frame_length' : {round(0.01*fs)} | scalar 21 | % The length of frames (in samples) used for calculating 22 | % cross-correlations. 23 | % 'hop' : {[]} | scalar 24 | % The hop size (in samples). By default the hop size is equal to the 25 | % frame length (HOP is specified as an empty array). The hop size 26 | % determines the number of frames as 27 | % FIX((MAX(SIZE(HC_L))-(FRAME_LENGTH-HOP))/HOP); 28 | % 'maxlag' : {round(0.001*fs)} | scalar 29 | % The maximum lag of the cross-correlation (in samples). 30 | % 'norm_flag' : {0} | scalar 31 | % Specifies whether the cross-correlograms are calculated using 32 | % normalised cross-correlations. A non-zero value indicates that 33 | % normalised cross-correlations are used. 34 | % 35 | % [CCG,IC] = IOSR.AUDITORY.CHXCORR2(...) returns the calculated IC for 36 | % each frame to the matrix IC. 37 | 38 | % Copyright 2016 University of Surrey. 39 | 40 | assert(nargin>=3, 'iosr:chXcorr2:nargin', 'Number of input arguments must be greater than or equal to three.') 41 | 42 | % Check source file is compiled 43 | iosr.general.checkMexCompiled('-largeArrayDims',fullfile(fileparts(mfilename('fullpath')),'chXcorr2_c.c')) 44 | 45 | options = struct(... 46 | 'frame_length',round(0.01*fs),... 47 | 'maxlag',round(0.001*fs),... 48 | 'norm_flag',0,... 49 | 'hop',[]); 50 | 51 | % read parameter/value inputs 52 | if nargin > 3 % if parameters are specified 53 | % read the acceptable names 54 | optionNames = fieldnames(options); 55 | % count arguments 56 | nArgs = length(varargin); 57 | if round(nArgs/2)~=nArgs/2 58 | error('iosr:chXcorr2:nameValuePair','CHXCORR2 needs propertyName/propertyValue pairs') 59 | end 60 | % overwrite defults 61 | for pair = reshape(varargin,2,[]) % pair is {propName;propValue} 62 | IX = strcmpi(pair{1},optionNames); % find match parameter names 63 | if any(IX) 64 | % do the overwrite 65 | options.(optionNames{IX}) = pair{2}; 66 | else 67 | error('iosr:chXcorr2:unknownOption','%s is not a recognized parameter name',pair{1}) 68 | end 69 | end 70 | end 71 | 72 | % assign options to variables 73 | frame_length = options.frame_length; 74 | maxlag = options.maxlag; 75 | norm_flag = options.norm_flag; 76 | if isempty(options.hop) 77 | hop = frame_length; 78 | else 79 | hop = options.hop; 80 | end 81 | 82 | % check inputs 83 | assert(all(size(hc_L)==size(hc_R)), 'iosr:chXcorr2:invalidInput', '''hc_L'' and ''hc_R'' must be the same size') 84 | assert(round(frame_length)==frame_length && isscalar(frame_length) && frame_length>0, ... 85 | 'iosr:chXcorr2:invalidFrame', '''frame_length'' must be an integer greater than zero') 86 | assert(round(maxlag)==maxlag && isscalar(maxlag) && maxlag>0, 'iosr:chXcorr2:invalidMaxlag', ... 87 | '''maxlag'' must be an integer greater than zero') 88 | assert(isscalar(norm_flag), 'iosr:chXcorr2:invalidNorm', '''norm_flag'' must be a scalar') 89 | 90 | % Calculate frame count 91 | frame_count = fix((max(size(hc_L))-(frame_length-hop))/hop); 92 | 93 | % Calculate number of frequency channels 94 | numchans = min(size(hc_L)); 95 | 96 | % Check orientation of HC and inhib data (i.e. that frequency runs across the rows) 97 | dims = size(hc_L); 98 | hc_L = check_input(hc_L,2,numchans); 99 | hc_R = check_input(hc_R,2,numchans); 100 | 101 | % set a flag if data has been transposed in this way 102 | if dims(1)~=size(hc_L,1) 103 | rot = true; 104 | else 105 | rot = false; 106 | end 107 | 108 | % Calculate cross-correlograms 109 | [ccg,ic] = iosr.auditory.chXcorr2_c(hc_L,hc_R,frame_count,frame_length,maxlag,hop,norm_flag); 110 | 111 | % Correct orientation of IC data, if data was transposed, and crop to remove appended zeros 112 | if rot 113 | ic = ic'; 114 | end 115 | 116 | end 117 | 118 | function output = check_input(input,dim,target) 119 | %CHECK_INPUT check input is correct orientation 120 | 121 | if size(input,dim)~=target 122 | output = input'; 123 | assert(size(output,dim)==target, 'iosr:chXcorr2:invalidInput', 'Input invalid') 124 | else 125 | output = input; 126 | end 127 | 128 | end 129 | -------------------------------------------------------------------------------- /+iosr/+statistics/kernelDensity.m: -------------------------------------------------------------------------------- 1 | function [d, xd, bw] = kernelDensity(x, bins, h, kernel) 2 | %KERNELDENSITY Calculate the kernel density of a data set 3 | % 4 | % [D, XD] = IOSR.STATISTICS.KERNELDENSITY(X) calculate the kernel density 5 | % D of a dataset X for query points XD. The kernel density is calculated 6 | % for 100 query points equally spaced between the minimum and maximum of 7 | % the data X. The density is estimated using a gaussian kernel with a 8 | % width that is optimal for normal data. X may be a vector, matrix, or 9 | % multi-dimensional array; the entire array is treated as the sample. D 10 | % and XD are 100-point column vectors. NaN are excluded from the 11 | % calculations. 12 | % 13 | % ... = IOSR.STATISTICS.KERNELDENSITY(X, BINS) calculates the density for 14 | % the query points specified by BINS. If BINS is a scalar, then BINS 15 | % points are queried between MIN(X(:)) and MAX(X(:)); if BINS is a 16 | % vector, then the values are used as the query points directly. D and XD 17 | % are column vectors. 18 | % 19 | % ... = IOSR.STATISTICS.KERNELDENSITY(X, BINS, H) uses the bandwidth H to 20 | % calculate the kernel density. H must be a scalar. BINS may be an empty 21 | % array in order to use the default described above. 22 | % 23 | % ... = IOSR.STATISTICS.KERNELDENSITY(X, BINS, [], KERNEL) uses the 24 | % kernel function specified by KERNEL to calculate the density. The 25 | % kernel may be: 26 | % - 'normal' (default), 27 | % - 'uniform', 28 | % - 'triangular', 29 | % - 'epanechnikov', 30 | % - 'quartic', 31 | % - 'triweight', 32 | % - 'tricube', 33 | % - 'cosine', 34 | % - 'logistic', 35 | % - 'sigmoid', or 36 | % - 'silverman'. 37 | % For the uniform case the bandwidth is set to 15% of the range of the 38 | % data [1]. Otherwise the bandwidth is chosen to be optimal for normal 39 | % data assuming a gaussian kernel. 40 | % 41 | % ... = IOSR.STATISTICS.KERNELDENSITY(X, BINS, H, KERNEL) allows the 42 | % bins BINS and bandwidth H to be specified directly. 43 | % 44 | % [D, XD, BW] = IOSR.STATISTICS.KERNELDENSITY(...) returns the badwidth 45 | % BW. 46 | % 47 | % Examples 48 | % 49 | % Example 1: Plot the kernel density of gaussian data 50 | % figure 51 | % % gaussian random numbers 52 | % y = randn(100000, 1); 53 | % % density 54 | % [d, xd] = iosr.statistics.kernelDensity(y); 55 | % % plot 56 | % plot(xd, d); 57 | % 58 | % Example 2: Density trace with 200 bins of width of 10% of data range 59 | % figure 60 | % % random numbers 61 | % y = randn(100000, 1); 62 | % % y range 63 | % range = max(y(:)) - min(y(:)); 64 | % % density trace 65 | % [d, xd] = iosr.statistics.kernelDensity(y, 200, 0.1*range, 'uniform'); 66 | % % plot 67 | % plot(xd, d); 68 | % 69 | % References 70 | % 71 | % [1] Hintze, Jerry L.; Nelson, Ray D. (1998). "Violin Plots: A Box 72 | % Plot-Density Trace Synergism". The American Statistician. 52 (2): 73 | % 181?4. 74 | 75 | %% input check 76 | 77 | x = x(:); 78 | x = x(~isnan(x)); 79 | assert(numel(x) > 1, 'X must be a vector, matrix, or array.') 80 | 81 | % x bins 82 | if nargin < 2 83 | bins = []; 84 | end 85 | if isempty(bins) 86 | bins = 100; 87 | end 88 | if isscalar(bins) 89 | bins = linspace(min(x), max(x), round(bins)); 90 | end 91 | bins = bins(:); 92 | 93 | % bin width 94 | if nargin < 3 95 | h = []; 96 | end 97 | 98 | % kernel 99 | if nargin < 4 100 | kernel = []; 101 | end 102 | if isempty(kernel) 103 | kernel = 'normal'; 104 | end 105 | % return kernel function 106 | switch lower(kernel) 107 | case 'uniform' 108 | K = @(u) 0.5*(abs(u) <= 1); 109 | if isempty(h) 110 | h = 0.15 * (max(x) - min(x)); 111 | end 112 | case 'normal' 113 | K = @(u) ((1/sqrt(2*pi)) * exp(-0.5*(u.^2))); 114 | case 'triangular' 115 | K = @(u) ((1-abs(u)) .* (abs(u) <= 1)); 116 | case 'epanechnikov' 117 | K = @(u) ((0.75*(1-(u.^2))) .* (abs(u) <= 1)); 118 | case 'quartic' 119 | K = @(u) (((15/16)*(1-(u.^2)).^2) .* (abs(u) <= 1)); 120 | case 'triweight' 121 | K = @(u) (((35/32)*(1-(u.^2)).^3) .* (abs(u) <= 1)); 122 | case 'tricube' 123 | K = @(u) (((70/81)*(1-(abs(u).^3)).^3) .* (abs(u) <= 1)); 124 | case 'cosine' 125 | K = @(u) (((pi/4)*cos((pi/2)*u)) .* (abs(u) <= 1)); 126 | case 'logistic' 127 | K = @(u) (1 / (exp(u) + 2 + exp(-u))); 128 | case 'sigmoid' 129 | K = @(u) ((2/pi) * (1 / (exp(u) + exp(-u)))); 130 | case 'silverman' 131 | K = @(u) (0.5 * exp((-abs(u))/(sqrt(2))) .* sin((abs(u))/(sqrt(2)) + (pi/4))); 132 | otherwise 133 | error('Unknown kernel specified'); 134 | end 135 | if isempty(h) 136 | h = ((4*(std(x).^5))/(3*numel(x))).^(1/5); 137 | end 138 | 139 | assert(isscalar(h), 'h must be a scalar') 140 | 141 | %% calculate kernel density 142 | 143 | xd = sort(bins); 144 | d = zeros(size(xd)); 145 | 146 | for i = 1:numel(xd) 147 | d(i) = sum(K((x-xd(i))/h))./(numel(x)*h); 148 | end 149 | d(isnan(d)) = 0; 150 | bw = h; 151 | 152 | end -------------------------------------------------------------------------------- /+iosr/+statistics/tab2box.m: -------------------------------------------------------------------------------- 1 | function [y,x,g] = tab2box(Xin,Yin,Gin) 2 | %TAB2BOX Prepare tabular data for boxPlot function 3 | % 4 | % Y = IOSR.STATISTICS.TAB2BOX(XIN,YIN) prepares data, in tabular form, 5 | % for use in the BOX_PLOT function. Specifically, XIN and YIN are vectors 6 | % (for example column vectors from a results table). Y is an N-by-P 7 | % numeric array, where P is the number of unique elements in XIN, and N 8 | % is the maximum number of occurences of any individual element of XIN. 9 | % In cases where elements in XIN do not occur an equal number of times, 10 | % columns in YIN are padded with NaNs. 11 | % 12 | % Y = IOSR.STATISTICS.TAB2BOX(XIN,YIN,GIN) returns an 13 | % N-by-P-by-G-by-I-by-J... numeric array, where G is the number of unique 14 | % elements in the first column of GIN, I is the number of unique elements 15 | % in the second column of GIN, etc. GIN is a numeric or cell matrix with 16 | % as many rows as XIN or YIN. The input facilitates hierarchical grouping 17 | % of the data in the box plot, with the grouping order determined by the 18 | % column order of GIN. 19 | % 20 | % [Y,X] = IOSR.STATISTICS.TAB2BOX(...) returns the unique values in XIN 21 | % to X. 22 | % 23 | % [Y,X,G] = IOSR.STATISTICS.TAB2BOX(...) returns the unique values in GIN 24 | % to G. G is a cell vector whereby the Nth element contains a vector of 25 | % length SIZE(Y,N+2). 26 | % 27 | % Example 28 | % 29 | % % Prepare data for box_plot grouped by two variables 30 | % 31 | % % load data 32 | % % (requires Statistics or Machine Learning Toolbox) 33 | % load carbig 34 | % 35 | % % arrange data 36 | % [y,x,g] = iosr.statistics.tab2box(Cylinders,MPG,when); 37 | % 38 | % % sort 39 | % IX = [1 3 2]; % order 40 | % g = g{1}(IX); 41 | % y = y(:,:,IX); 42 | % 43 | % % plot 44 | % figure 45 | % h = iosr.statistics.boxPlot(x,y,... 46 | % 'boxColor','auto','medianColor','k',... 47 | % 'scalewidth',true,'xseparator',true,... 48 | % 'groupLabels',g,'showLegend',true); 49 | % box on 50 | % title('MPG by number of cylinders and period') 51 | % xlabel('Number of cylinders') 52 | % ylabel('MPG') 53 | % 54 | % See also IOSR.STATISTICS.BOXPLOT. 55 | 56 | % Copyright 2016 University of Surrey. 57 | 58 | %% validate input 59 | 60 | % validate Xin 61 | if ischar(Xin) 62 | Xin = cellstr(Xin); 63 | end 64 | assert(isvector(Xin), 'iosr:tab2box:invalidInput', 'Xin must be a vector') 65 | 66 | % validate Yin 67 | assert(isvector(Yin), 'iosr:tab2box:invalidInput', 'Yin must be a vector') 68 | assert(isnumeric(Yin), 'iosr:tab2box:invalidInput', 'Yin must be numeric') 69 | 70 | % validate Gin 71 | if nargin<3 72 | Gin = []; 73 | else 74 | if ischar(Gin) 75 | Gin = cellstr(Gin); 76 | end 77 | assert(isvector(Gin) || size(Gin,1)==numel(Yin), 'iosr:tab2box:invalidInput', 'Gin must be a vector or a matrix with as many rows as Y has elements.') 78 | if isvector(Gin) 79 | Gin = Gin(:); 80 | end 81 | end 82 | 83 | %% group 84 | 85 | % unique values 86 | x = getUniques(Xin); 87 | if iscell(x) 88 | x = x{1}; 89 | end 90 | g = getUniques(Gin); 91 | if ~iscell(g) 92 | temp = cell(1,size(g,2)); 93 | for c = 1:size(g,2); 94 | temp{c} = g(:,c); 95 | end 96 | g = temp; 97 | end 98 | 99 | % preallocate cell array 100 | gdims = cellfun(@length,g); 101 | dims = [1,length(x),gdims]; 102 | yc = cell(dims); 103 | subgidx = cell(1,length(gdims)); 104 | 105 | % put data into cells 106 | for a = 1:length(x) 107 | IXx = findIX(Xin,x,a); 108 | for b = 1:prod(gdims) 109 | [subgidx{:}] = ind2sub(gdims, b); % get group index 110 | subidx = [{1} {a} subgidx{:}]; % make main index 111 | IXg = true(size(Xin)); % select everything at first 112 | for c = 1:length(subgidx) % narrow it down 113 | IXg = IXg & findIX(Gin(:,c),g{c},subgidx{c}); 114 | end 115 | % return y as column 116 | yc{subidx{:}} = reshape(Yin(IXx & IXg),sum(IXx & IXg),1); 117 | end 118 | end 119 | 120 | %% convert to numeric array 121 | 122 | try % see if can concat directly 123 | y = cell2mat(yc); 124 | catch % else pad with NaNs 125 | maxSizeY = max(cellfun(@length,yc(:))); 126 | for a = 1:numel(yc) 127 | if length(yc{a}) < maxSizeY 128 | yc{a} = [yc{a}; NaN(maxSizeY-length(yc{a}),1)]; 129 | end 130 | end 131 | y = cell2mat(yc); 132 | end 133 | 134 | end 135 | 136 | function IX = findIX(L,l,n) 137 | %FINDIX find entry in array from lookup array and index 138 | 139 | if iscellstr(L) 140 | IX = strcmp(L,l{n}); 141 | elseif iscell(L) 142 | IX = cell2mat(L)==l(n); 143 | else 144 | IX = L==l(n); 145 | end 146 | end 147 | 148 | function out = getUniques(d) 149 | %GETUNIQUES return unique entries from vector 150 | 151 | if isvector(d) % ensure column vector 152 | d = d(:); 153 | end 154 | dims = zeros(1,size(d,2)); 155 | u = cell(1,size(d,2)); 156 | % put unique values into columns 157 | for c = 1:size(d,2) 158 | if iscellstr(d(:,c)) || isnumeric(d(:,c)) 159 | u{c} = unique(d(:,c)); 160 | else 161 | u{c} = unique(cell2mat(d(:,c))); 162 | end 163 | dims(c) = length(u{c}); 164 | end 165 | try % to make a numeric array 166 | out = cell2mat(u); 167 | catch % keep as cell array 168 | out = u; 169 | end 170 | 171 | end 172 | -------------------------------------------------------------------------------- /+iosr/+statistics/qqPlot.m: -------------------------------------------------------------------------------- 1 | function h = qqPlot(varargin) 2 | %QQPLOT Quantile-quantile plot with patch option 3 | % 4 | % IOSR.STATISTICS.QQPLOT(Y) displays a quantile-quantile plot of the 5 | % sample quantiles of Y versus theoretical quantiles from a normal 6 | % distribution. If the distribution of Y is normal, the plot will be 7 | % close to linear. 8 | % 9 | % IOSR.STATISTICS.QQPLOT(X,Y) displays a quantile-quantile plot of two 10 | % samples. If the samples come from the same distribution, the plot will 11 | % be linear. 12 | % 13 | % The inputs X and Y should be numeric and have an equal number of 14 | % elements; every element is treated as a member of the sample. 15 | % 16 | % The plot displays the sample data with the plot symbol 'x'. 17 | % Superimposed on the plot is a dashed straight line connecting the first 18 | % and third quartiles. 19 | % 20 | % IOSR.STATISTICS.QQPLOT(...,MODE) allows the appearance of the plot to 21 | % be configured. With MODE='line' (default), the plot appears as 22 | % described above. With MODE='patch', the data are plotted as a patch 23 | % object, with the area bound by the x-distribution and the linear fit 24 | % shaded grey. With mode='both' the two appearances are combined. 25 | % 26 | % IOSR.STATISTICS.QQPLOT(...,MODE,METHOD) and 27 | % IOSR.STATISTICS.QQPLOT(...,[],METHOD) allows the method for calculating 28 | % the quartiles, used for the fit line, to be specified. The default is 29 | % 'R-8'. Type 'help iosr.statistics.quantile' for more information. The 30 | % latter form of the function call uses the default mode. 31 | % 32 | % H = IOSR.STATISTICS.QQPLOT(...) returns a two- or three-element vector 33 | % of handles to the plotted object. The nature of the handles depends 34 | % upon the mode. In all cases, the first handle is to the sample data, 35 | % the second handle is to the fit line. With MODE='patch' or MODE='both', 36 | % there is third handle to the patch object. 37 | % 38 | % Example 39 | % 40 | % % Display Q-Q plots for the rand and randn functions 41 | % figure 42 | % subplot(2,1,1) 43 | % iosr.statistics.qqPlot(rand(20),'patch') 44 | % subplot(2,1,2) 45 | % h = iosr.statistics.qqPlot(randn(20),'patch'); 46 | % set(h(3),'FaceColor','r') % change fill color 47 | % 48 | % See also IOSR.STATISTICS.QUANTILE, IOSR.STATISTICS.BOXPLOT. 49 | 50 | % Copyright 2016 University of Surrey. 51 | 52 | %% determine X and Y 53 | 54 | IXn = cellfun(@(x) isnumeric(x) & ~isempty(x),varargin); 55 | 56 | switch sum(IXn) 57 | case 0 58 | error('iosr:qqPlot:noData','No input data specified') 59 | case 1 60 | % compare to normal distrbution 61 | Y = get_input_sample(varargin,IXn); 62 | p = (.5:length(Y))/length(Y); 63 | X = sqrt(2)*erfinv(2*p - 1); 64 | x_label = 'Standard normal quantiles'; 65 | y_label = 'Sample quantiles'; 66 | case 2 67 | % compare to input data distribution 68 | Y = get_input_sample(varargin,find(IXn,1,'last')); 69 | X = get_input_sample(varargin,find(IXn,1,'first')); 70 | assert(isequal(size(X),size(Y)), 'iosr:quantile:invalidInput', 'Input data must be the same size') 71 | x_label = 'X quantiles'; 72 | y_label = 'Y quantiles'; 73 | otherwise 74 | error('iosr:qqPlot:unkonwnInput','Unknown input specified') 75 | end 76 | 77 | %% determine mode and method 78 | 79 | % find inputs 80 | IXc = cellfun(@(x) ischar(x) | isempty(x),varargin); 81 | switch sum(IXc) 82 | case 0 83 | mode = []; 84 | method = []; 85 | case 1 86 | mode = varargin{IXc}; 87 | method = []; 88 | case 2 89 | mode = varargin{find(IXc,1,'first')}; 90 | method = varargin{find(IXc,1,'last')}; 91 | otherwise 92 | error('iosr:qqPlot:unknownString','Unknown string specified') 93 | end 94 | 95 | % defaults 96 | if isempty(mode) 97 | mode = 'line'; 98 | end 99 | if isempty(method) 100 | method = 'R-8'; 101 | end 102 | 103 | %% calculate fit to first and third quartile 104 | 105 | % quartiles 106 | q1x = iosr.statistics.quantile(X,.25,[],method); 107 | q3x = iosr.statistics.quantile(X,.75,[],method); 108 | q1y = iosr.statistics.quantile(Y,.25,[],method); 109 | q3y = iosr.statistics.quantile(Y,.75,[],method); 110 | 111 | % slope 112 | slope = (q3y-q1y)./(q3x-q1x); 113 | centerx = (q1x+q3x)/2; 114 | centery = (q1y+q3y)/2; 115 | 116 | % fit 117 | maxx = max(X); 118 | minx = min(X); 119 | maxy = centery + slope.*(maxx - centerx); 120 | miny = centery - slope.*(centerx - minx); 121 | 122 | % lines 123 | X_fit = linspace(minx,maxx,length(X)); 124 | Y_fit = linspace(miny,maxy,length(X)); 125 | 126 | %% plot data 127 | 128 | hold on 129 | 130 | if strcmpi(mode,'patch') || strcmpi(mode,'both') 131 | Hp = patch([X fliplr(X_fit)],[Y fliplr(Y_fit)],[0.5 0.5 0.5]); 132 | set(Hp,'edgecolor','none') 133 | else 134 | Hp = NaN; 135 | end 136 | 137 | switch lower(mode) 138 | case 'line' 139 | linestyle = 'xk'; 140 | case 'patch' 141 | linestyle = '-k'; 142 | case 'both' 143 | linestyle = 'xk'; 144 | otherwise 145 | error('iosr:qqPlot:unknownMode','Unknown mode specified') 146 | end 147 | 148 | H = plot(X,Y,linestyle,X_fit,Y_fit,'--k'); 149 | 150 | hold off 151 | 152 | % axis labels 153 | xlabel(x_label) 154 | ylabel(y_label) 155 | 156 | box on; axis tight 157 | set(gca,'layer','top') 158 | 159 | % return handle 160 | if nargout>0 161 | h = H; 162 | if isobject(Hp) || ishandle(Hp) 163 | h = [h; Hp]; 164 | end 165 | end 166 | 167 | end 168 | 169 | function Z = get_input_sample(z,IX) 170 | %GET_INPUT_SAMPLE get sample, order, and convert to vector 171 | Z = z{IX}; 172 | Z = sort(Z(:))'; 173 | end 174 | -------------------------------------------------------------------------------- /+iosr/+figures/subfigrid.m: -------------------------------------------------------------------------------- 1 | function pos = subfigrid(nrows,ncols,offset,scale) 2 | %SUBFIGRID Create axis positions for subfigures 3 | % 4 | % The spacing of axes using the subplot command can be quite large, and 5 | % manipulating axis positions after plotting can be tricky. For 6 | % publication quality graphics, it is better to specify the subplot 7 | % position directly, rather than using subplot indices. For example: 8 | % 9 | % figure 10 | % subplot('position',[0.1 0.1 0.2 0.2]) 11 | % plot(rand(20,1)) 12 | % 13 | % This function creates appropriate position vectors, for use in the 14 | % above scenario, based on the number of subplots required. Optional 15 | % scaling and offset parameters allow the size of each subplot to be 16 | % fine-tuned, and space for axis labels to be allotted. All calculations 17 | % are performed in normalized units. 18 | % 19 | % POS = IOSR.FIGURES.SUBFIGRID(NROWS,NCOLS) creates an array of positions 20 | % for positioning axes as subfigures. The array has dimensions [M,P,N]: M 21 | % is the subplot row, N is the subplot column, and P is the position 22 | % vector. By default, each axis will be scaled such that [width height] 23 | % will be [1/NCOLS 1/NROWS]. 24 | % 25 | % POS = IOSR.FIGURES.SUBFIGRID(NROWS,NCOLS,OFFSET) allows a margin OFFSET 26 | % to be specified. This should be a four-element vector specifying the 27 | % margins thus: [left right top bottom]. By default offset=[0 0 0 0]. 28 | % Axes will be scaled to fill the remaining space. 29 | % 30 | % POS = IOSR.FIGURES.SUBFIGRID(NROWS,NCOLS,OFFSET,SCALE) allows the axes 31 | % to be scaled. This should be a two-element vector specifying a scale 32 | % factor that will be applied to each axis; SCALE(1) scales the width, 33 | % SCALE(2) scales the height. The axes will be scaled such that the 34 | % offset margin will be retained. By default SCALE=[1 1]. 35 | % 36 | % If scaling is required, but an offset is not, OFFSET may be set to the 37 | % empty matrix []. 38 | % 39 | % Examples 40 | % 41 | % Example 1 42 | % 43 | % % Normal use of subfigrid 44 | % scrsz = get(0,'ScreenSize'); 45 | % nrows = 2; 46 | % ncols = 3; 47 | % pos = iosr.figures.subfigrid(nrows,ncols,... 48 | % [0.05 0.01 0.01 0.05],[0.85 0.88]); 49 | % 50 | % figure('units','pixels','position',... 51 | % [scrsz(3)/4,scrsz(4)/4,scrsz(3)/2,scrsz(4)/2]) 52 | % for m = 1:nrows 53 | % for n = 1:ncols 54 | % subplot('position',pos(m,:,n)) 55 | % plot(randn(20,1)) 56 | % end 57 | % end 58 | % 59 | % Example 2 60 | % 61 | % % Use ind2sub when row/col indices are not available 62 | % scrsz = get(0,'ScreenSize'); 63 | % nrows = 2; 64 | % ncols = 3; 65 | % pos = iosr.figures.subfigrid(nrows,ncols,... 66 | % [0.05 0.01 0.01 0.05],[0.85 0.88]); 67 | % 68 | % figure('units','pixels','position',... 69 | % [scrsz(3)/4,scrsz(4)/4,scrsz(3)/2,scrsz(4)/2]) 70 | % for p = 1:nrows*ncols 71 | % [m,n] = ind2sub([nrows ncols],p); 72 | % subplot('position',pos(m,:,n)) 73 | % plot(randn(20,1)) 74 | % end 75 | % 76 | % See also SUBPLOT. 77 | 78 | % Copyright 2016 University of Surrey. 79 | 80 | %% parse inputs 81 | 82 | if nargin<3 83 | offset = []; 84 | end 85 | if nargin<4 86 | scale = []; 87 | end 88 | 89 | assert(isscalar(nrows) & round(nrows)==nrows, 'iosr:subfigrid:invalidInput', 'nrows must be a whole number scalar') 90 | assert(isscalar(ncols) & round(ncols)==ncols, 'iosr:subfigrid:invalidInput', 'ncols must be a whole number scalar') 91 | assert(nrows>=1 & ncols>=1, 'iosr:subfigrid:invalidInput', 'nrows and ncols must be greater than, or equal to, 1.') 92 | 93 | if isempty(offset) 94 | offset = zeros(1,4); 95 | elseif ~(isnumeric(offset) && numel(offset)==4) 96 | error('iosr:subgigrid:offsetInvalid','offset should be a four-element numeric vector') 97 | elseif any(offset<0) 98 | error('iosr:subgigrid:offsetInvalid','offset should not contain any negative values') 99 | else 100 | offset = offset(:)'; 101 | end 102 | 103 | if isempty(scale) 104 | scale = [1 1]; 105 | elseif ~(isnumeric(scale) && numel(scale)==2) 106 | error('iosr:subgigrid:scaleInvalid','scale should be a two-element numeric vector') 107 | elseif any(scale>1) || any(scale<0) 108 | error('iosr:subgigrid:scaleInvalid','scale values should be in the range [0,1]') 109 | else 110 | scale = scale(:)'; 111 | end 112 | 113 | %% calculate positions 114 | 115 | % ignore scale values for dimensions where there is one unit. 116 | if ncols==1 && scale(1)<1 117 | scale(1) = 1; 118 | warning('iosr:subgigrid:scaleUse','Since there is only one column, scale(1) has no effect (and is consequently set to 1).') 119 | end 120 | if nrows==1 && scale(2)<1 121 | scale(2) = 1; 122 | warning('iosr:subgigrid:scaleUse','Since there is only one row, scale(2) has no effect (and is consequently set to 1).') 123 | end 124 | 125 | % calculate array of left positions 126 | sub_pos_l = linspace(offset(1),1-offset(2),ncols+1); 127 | width = min(abs(diff(sub_pos_l)))*scale(1); 128 | sub_pos_l = sub_pos_l(1:end-1); 129 | % shift axes to maintain right margin: 130 | sub_pos_l = sub_pos_l+linspace(0,((1-scale(1))*width),ncols); 131 | 132 | % calculate array of bottom positions 133 | sub_pos_b = linspace(1-offset(3),offset(4),nrows+1); 134 | height = min(abs(diff(sub_pos_b)))*scale(2); 135 | sub_pos_b = sub_pos_b(2:end); 136 | % shift axes to maintain top margin: 137 | sub_pos_b = sub_pos_b+linspace(((1-scale(2))*height),0,nrows); 138 | 139 | % create array of positions 140 | pos = zeros(nrows,4,ncols); 141 | for m = 1:nrows 142 | for n = 1:ncols 143 | pos(m,1,n) = sub_pos_l(n); % left 144 | pos(m,2,n) = sub_pos_b(m); % bottom 145 | end 146 | end 147 | pos(:,3,:) = width; 148 | pos(:,4,:) = height; 149 | 150 | end 151 | -------------------------------------------------------------------------------- /+iosr/+bss/resynthesise.m: -------------------------------------------------------------------------------- 1 | function output = resynthesise(x,fs,cfs,m,varargin) 2 | %RESYNTHESISE Resynthesise a target from a time-frequency mask 3 | % 4 | % OUTPUT = IOSR.BSS.RESYNTHESISE(X,FS,CFS,M) takes an input vector X, 5 | % sampled at FS Hz, and applies the time-frequency mask M. The mask M is 6 | % a matrix, with one column for each frequency channel, and one row for 7 | % each sample. A row may also correspond to a frame - see 'frame_length' 8 | % option below. The centre frequency of each column is contained in CFS. 9 | % 10 | % OUTPUT = IOSR.BSS.RESYNTHESISE(X,FS,CFS,M,'PARAMETER',VALUE) allows a 11 | % number of options to be specified. The options are: 12 | % 13 | % % ({} indicates the default value) 14 | % 15 | % 'frame_length' : {1} | scalar 16 | % The frame length for each time-frequency unit in M, in samples. 17 | % 'delay' : {zeros(size(m,2),1)} | vector 18 | % The delay in the filterbank when the time-frequency mask M was 19 | % calculated. The delay is removed only when 'frame_length' > 1 and 20 | % the full mask must be reconstructed. The value should be a vector 21 | % whose length is equal to the number of columns in M; delay(n) is 22 | % the delay in frequency band cfs(n). The delay does not affect the 23 | % ouput of the reconstruction filters, since the filters are 24 | % zero-phase. 25 | % 'kernel' : {1} | vector 26 | % Allows smoothing to be applied to the full mask. See 27 | % IOSR.BSS.GETFULLMASK for more details. 28 | % 'filter' : {'gammatone'} | 'sinc' 29 | % Allows the reconstruction filter to be specified. By default a 30 | % gammatone filterbank is used, and cfs corresponds to the filter 31 | % centre frequencies. Alternatively a series sinc band-pass filters 32 | % can be used. In this case it is assumed that the frequencies in cfs 33 | % correspond to the upper cut-off frequencies of the sinc filters. 34 | % The function IOSR.BSS.CFS2FCS can be used to calculate the cut-off 35 | % frequencies. 36 | % 'order' : {fs} | scalar 37 | % The sinc filter order. 38 | % 39 | % Algorithm 40 | % 41 | % The algorithm has the following steps: 42 | % 1) If necessary, obtain the full (smoothed) binary mask. See 43 | % IOSR.BSS.GETFULLMASK. 44 | % 2) Pass the vector x through the reconstruction filterbank, with 45 | % centre/cut-off frequencies cfs. 46 | % 3) Multiply the full mask with the output of the filterbank. 47 | % 4) Sum the responses of the filterbank to produce a vector the same 48 | % size as x. 49 | % 50 | % See also IOSR.BSS.CFS2FCS, IOSR.BSS.GETFULLMASK, IOSR.BSS.SINCFILTER. 51 | 52 | % Copyright 2016 University of Surrey. 53 | 54 | %% parse input 55 | 56 | if nargin<4 57 | error('iosr:resynthesise:tooFewInputArgs','Not enough input arguments') 58 | end 59 | 60 | numchans = size(m,2); 61 | 62 | options = struct(... 63 | 'frame_length',1,... 64 | 'delay',zeros(numchans,1),... 65 | 'kernel',1,... 66 | 'filter','gammatone',... 67 | 'order',fs); 68 | 69 | % read parameter/value inputs 70 | if nargin > 4 % if parameters are specified 71 | % read the acceptable names 72 | optionNames = fieldnames(options); 73 | % count arguments 74 | nArgs = length(varargin); 75 | if round(nArgs/2)~=nArgs/2 76 | error('iosr:resynthesise:nameValuePairs','RESYNTHESISE needs propertyName/propertyValue pairs') 77 | end 78 | % overwrite defults 79 | for pair = reshape(varargin,2,[]) % pair is {propName;propValue} 80 | IX = strcmpi(pair{1},optionNames); % find match parameter names 81 | if any(IX) 82 | % do the overwrite 83 | options.(optionNames{IX}) = pair{2}; 84 | else 85 | error('iosr:resynthesise:unknownParameter','%s is not a recognized parameter name',pair{1}) 86 | end 87 | end 88 | end 89 | 90 | delay = options.delay; 91 | kernel = options.kernel; 92 | outfilter = options.filter; 93 | frame_d = options.frame_length; 94 | order = options.order; 95 | 96 | % validate 97 | assert(isvector(x) && isnumeric(x), 'iosr:resynthesise:invalidX', 'x must be a vector.'); 98 | assert(all(round(delay)==delay), 'iosr:resynthesise:invalidDelay', 'Values in ''delay'' must be integers.'); 99 | assert(length(delay)==numchans && isnumeric(delay), 'iosr:resynthesise:invalidDelay', 'The ''delay'' parameter must be a numeric vector the same length as the number of columns in m.'); 100 | assert(ischar(outfilter), 'iosr:resynthesise:invalidFilter', 'The ''filter'' option must be a char array (string).'); 101 | assert(round(frame_d)==frame_d && isscalar(frame_d), 'iosr:resynthesise:invalidFrame', '''frame_length'' must be a integer scalar.'); 102 | assert(isnumeric(kernel), 'iosr:resynthesise:invalidKernel', '''kernel'' must be numeric.'); 103 | assert(isvector(cfs) && length(cfs)==numchans && isnumeric(cfs), 'iosr:resynthesise:invalidCfs', 'cfs must be a vector with the same number of elements as there are columns of m.') 104 | 105 | %% get sample-by-sample masks 106 | 107 | if frame_d>1 108 | m_full = iosr.bss.getFullMask(m,frame_d,delay,kernel); 109 | else 110 | m_full = m; 111 | end 112 | 113 | % crop or zero-pad x to match mask 114 | if size(m_full,1)length(x) 117 | xpad = [x; zeros(size(m_full,1)-length(x),1)]; 118 | else 119 | xpad = x; 120 | end 121 | 122 | %% Pass x through reconstruction filterbank 123 | 124 | switch lower(outfilter) 125 | case 'gammatone' 126 | z = iosr.auditory.gammatoneFast(xpad,cfs,fs,true); % phase aligned GTFB 127 | case 'sinc' 128 | fcs = [0 cfs]; 129 | z = zeros(length(xpad),numchans); 130 | for i = 1:numchans 131 | z(:,i) = iosr.dsp.sincFilter(xpad,fcs(i:i+1)./(fs/2),order)'; 132 | end 133 | otherwise 134 | error('Unknown ''filter'' option specified') 135 | end 136 | 137 | %% create output 138 | 139 | % apply mask 140 | z = z.*m_full; 141 | 142 | % Sum to create waveforms 143 | output = sum(z,2); 144 | 145 | % crop or zero-pad output to match input 146 | if length(output)>length(x) 147 | output = output(1:length(x)); 148 | elseif length(output) % insert blank lines where no functions will be 67 | % determine package prefix 68 | pkgprefix = strrep(dirs{d},[filesep '+'],'.'); 69 | pkgprefix = strrep(pkgprefix,[filesep '@'],'.'); 70 | dots = strfind(pkgprefix,'.'); 71 | if ~isempty(dots) 72 | pkgprefix = [pkgprefix(dots(1)+1:end) '.']; 73 | else 74 | pkgprefix = ''; 75 | end 76 | for f = 1:length(temp) % read H1 lines 77 | H1_lines = [H1_lines; {get_H1_line([dirs{d} filesep temp{f}])}]; %#ok % add H1 lines 78 | % remove extension from and add package prefix to m-files 79 | [~,fname,ext] = fileparts(temp{f}); 80 | if strcmpi(ext,'.m') 81 | temp{f} = [pkgprefix fname]; 82 | end 83 | end 84 | files = [files; {''}; {upper(dirs{d}(fIX+1:end))}; temp;]; %#ok % add filenames 85 | end 86 | end 87 | 88 | % longest file name (so appropriate space can be added between files and H1 lines 89 | longest_word = max(cellfun(@length,files(cellfun(@(x) ~isempty(x),H1_lines)))); 90 | 91 | % write to output 92 | nrows = length(files); 93 | fid = fopen(filename, 'w'); % open file for writing 94 | fprintf(fid, '%s\n%% \n', ['% ' upper(name)]); 95 | fprintf(fid, '%s\n', ['% Contents file for ' upper(folder(fIX+1:end)) ' and its subfolders.']); 96 | for row=1:nrows 97 | if isempty(H1_lines{row}) 98 | fprintf(fid, '%s\n', ['% ' files{row,:}]); 99 | else 100 | rowfilename = files{row,:}; 101 | [~,name,ext] = fileparts(rowfilename); 102 | if strcmpi(ext,'.m') % remove extension from m files 103 | rowfilename = name; 104 | end 105 | fprintf(fid, '%s\n',['% ' rowfilename repmat(' ',1,longest_word-length(rowfilename)) ' - ' H1_lines{row,:}]); 106 | end 107 | end 108 | fprintf(fid, '%% \n%% %s on %s at %s.\n', 'This file was generated by updateContents.m',datestr(datetime('now'),'dd mmm yyyy'),datestr(datetime('now'),'HH:MM:SS')); 109 | fclose(fid); 110 | 111 | end 112 | 113 | function H1_line = get_H1_line(filename) 114 | %GET_H1_LINE get the H1 line for a file 115 | 116 | [~,name,ext] = fileparts(filename); 117 | H1_line = ''; % default output 118 | if strcmp(ext,'.m') 119 | fid = fopen(filename); % open file 120 | tline = fgetl(fid); % read first line 121 | while ischar(tline) 122 | k = strfind(tline,'%'); % find comment 123 | if ~isempty(k) % if it is found 124 | k = k(1); 125 | ispercents = false(size(tline(k:end))); 126 | ispercents(strfind(tline(k:end),'%'))=true; 127 | start = k+find(~(isspace(tline(k:end)) | ispercents),1,'first')-1; 128 | if ~isempty(start) 129 | tline = tline(start:end); % remove leading space/percent 130 | IX = strfind(lower(tline),lower(name)); 131 | if ~isempty(IX) 132 | if IX(1)==1 133 | tline = tline(length(name)+1:end); % remove function name 134 | end 135 | tline = strtrim(tline); % remove any leading/trailing space 136 | end 137 | H1_line = tline; 138 | H1_line = strtrim(H1_line); 139 | if ~isempty(H1_line) 140 | if strcmp(H1_line(end),'.') % remove trailing period 141 | H1_line = H1_line(1:end-1); 142 | end 143 | H1_line(1) = upper(H1_line(1)); % capitalize first letter 144 | end 145 | end 146 | tline = -1; % set tline to numeric 147 | else 148 | tline = fgetl(fid); % read next line 149 | end 150 | end 151 | fclose(fid); 152 | end 153 | 154 | end 155 | -------------------------------------------------------------------------------- /+iosr/+auditory/chXcorr2_c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Calculate cross-correlograms with a range of options. 3 | * Called from chXcorr2.m. 4 | * 5 | * Copyright 2016 University of Surrey. 6 | * 7 | */ 8 | 9 | #include "math.h" 10 | #include "mex.h" 11 | #include "matrix.h" 12 | 13 | #define max(a,b) \ 14 | ({ __typeof__ (a) _a = (a); \ 15 | __typeof__ (b) _b = (b); \ 16 | _a > _b ? _a : _b; }) 17 | 18 | /* structure for cross-correlation info */ 19 | struct xcorr 20 | { 21 | int maxlag; 22 | double ic; 23 | double *c; 24 | double *nc; 25 | double *aL; 26 | double *aR; 27 | }; 28 | 29 | /* cross-correlation functions */ 30 | void xcorr(double L[], double R[], int frame_length, int numsamples, struct xcorr * data); 31 | void initXcorr(unsigned int maxlag, unsigned int length, struct xcorr * data); 32 | void freeXcorr(struct xcorr * data); 33 | 34 | /* main function */ 35 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 36 | { 37 | /* ====================== DECLARATIONS ====================== */ 38 | 39 | double 40 | *hc_L = mxGetPr(prhs[0]), /* input left signal */ 41 | *hc_R = mxGetPr(prhs[1]), /* input right signal */ 42 | *cout; /* output */ 43 | 44 | int 45 | frame_count = *mxGetPr(prhs[2]), /* Input number of frames in input */ 46 | frame_length = *mxGetPr(prhs[3]), /* Frame length */ 47 | maxlag = *mxGetPr(prhs[4]), /* Maximum lag for cross-correlation */ 48 | hop = *mxGetPr(prhs[5]), /* hop size */ 49 | norm_flag = *mxGetPr(prhs[6]), /* normalisation flag */ 50 | lengthCCG = (2*maxlag)+1; /* Cross-correlation length */ 51 | 52 | mwSize 53 | numsamples = mxGetM(prhs[0]), /* Number of samples in HC data */ 54 | numchans = mxGetN(prhs[0]); /* Number of frequency channels in input HC data */ 55 | 56 | /* Indices */ 57 | mwIndex 58 | m, /* lag index */ 59 | i, /* Frequency channel index */ 60 | j, /* Frame index */ 61 | sample, /* sample index */ 62 | index_ccg, /* Index to CCG */ 63 | index_ic; /* index into interaural coherence */ 64 | 65 | /* cross-correlation data */ 66 | struct xcorr ccdata; 67 | initXcorr(maxlag, lengthCCG, &ccdata); 68 | 69 | /* choose output according to normalisation */ 70 | if (norm_flag == 0) { 71 | cout = ccdata.c; 72 | } 73 | else { 74 | cout = ccdata.nc; 75 | } 76 | 77 | /* ====================== OUTPUTS ====================== */ 78 | 79 | mwSize ccg_dims[3] = {(mwSize)lengthCCG, numchans, (mwSize)frame_count}; /* CCG dimensions */ 80 | plhs[0] = mxCreateNumericArray(3,ccg_dims,mxDOUBLE_CLASS,mxREAL); 81 | plhs[1] = mxCreateDoubleMatrix(frame_count,numchans,mxREAL); 82 | double *ccg_out = mxGetPr(plhs[0]), 83 | *ic_out = mxGetPr(plhs[1]); 84 | 85 | /* ====================== CROSS-CORRELATE ====================== */ 86 | for ( j = 0; j < frame_count; j++ ) { 87 | for ( i = 0; i < numchans; i++ ) { 88 | /* indices */ 89 | sample = (i*(mwIndex)numsamples)+(j*(mwIndex)hop); 90 | index_ccg = (i*lengthCCG)+(j*lengthCCG*numchans); 91 | index_ic = (i*(mwIndex)frame_count)+j; 92 | /* cross-correlate */ 93 | xcorr(hc_L+sample, hc_R+sample, frame_length, numsamples, &ccdata); 94 | /* outputs */ 95 | ic_out[index_ic] = ccdata.ic; 96 | for ( m = 0; m < lengthCCG; m++ ) { 97 | ccg_out[index_ccg+m] = cout[m]; 98 | } 99 | } /* end frequency loop */ 100 | } /* end frame loop */ 101 | /* Destroy arrays */ 102 | freeXcorr(&ccdata); 103 | return; 104 | } 105 | 106 | void xcorr(double L[], double R[], int frame_length, int numsamples, struct xcorr * data) 107 | { 108 | 109 | int m, /* lag index */ 110 | n, /* sample index */ 111 | maxlag = data->maxlag; /* maximum cross-correlation lag */ 112 | double 113 | Rshift, /* shifted right signal */ 114 | mL = 0.0, /* mean of left */ 115 | mR = 0.0, /* mean of right */ 116 | denom, /* denominator */ 117 | *c = data->c, /* cross-correlation */ 118 | *nc = data->nc, /* normalised cross-correlation */ 119 | *aL = data->aL, /* left variance */ 120 | *aR = data->aR, /* right variance */ 121 | *ic = &(data->ic); /* interaural coherence */ 122 | 123 | /* reset arrays to 0 */ 124 | for (m = 0; m < 2*maxlag+1; m++) { 125 | c[m] = 0.0; 126 | nc[m] = 0.0; 127 | aL[m] = 0.0; 128 | aR[m] = 0.0; 129 | } 130 | 131 | /* calculate mean of time series */ 132 | for (n = 0; n < frame_length; n++) { 133 | mL += L[n]/frame_length; 134 | mR += R[n]/frame_length; 135 | } 136 | 137 | /* calculate cross-correlations of time series */ 138 | for (m = -maxlag; m <= maxlag; m++) { 139 | for (n = 0; n < frame_length; n++) { 140 | /* do not wrap, but set out-of-bounds to zero */ 141 | if (((n-m) < 0) || ((n-m) > frame_length) || ((n-m) > numsamples)) { 142 | Rshift = 0.0; 143 | } 144 | else { 145 | Rshift = R[n-m]; 146 | } 147 | /* cross-correlate */ 148 | c[m+maxlag] += (L[n]-mL) * (Rshift-mR); /* cross-correlation */ 149 | aL[m+maxlag] += pow(L[n]-mL,2.0); /* variance of left */ 150 | aR[m+maxlag] += pow(Rshift-mR,2.0); /* variance of right */ 151 | } 152 | } 153 | 154 | /* normalise the cross-correlation */ 155 | data->ic = 0.0; /* reset to 0 */ 156 | for (m = 0; m < 2*maxlag+1; m++) { 157 | denom = sqrt(aL[m])*sqrt(aR[m]); /* denominator */ 158 | if (denom > 0.0) { 159 | nc[m] = c[m]/denom; 160 | } 161 | else { /* prevent divide by zero */ 162 | nc[m] = 0.0; 163 | } 164 | /* calculate coherence */ 165 | *ic = max(*ic,nc[m]); 166 | } 167 | 168 | return; 169 | 170 | } 171 | 172 | void initXcorr(unsigned int maxlag, unsigned int length, struct xcorr * data) 173 | { 174 | /* initialise and allocate data */ 175 | data->maxlag = maxlag; 176 | data->ic = 0.0; 177 | data->c = calloc(length,sizeof(double)); 178 | data->nc = calloc(length,sizeof(double)); 179 | data->aL = calloc(length,sizeof(double)); 180 | data->aR = calloc(length,sizeof(double)); 181 | if (length != 0 && (!data->c || !data->nc || !data->aL || !data->aR)) { 182 | freeXcorr(data); 183 | } 184 | } 185 | 186 | void freeXcorr(struct xcorr * data) 187 | { 188 | /* free memory */ 189 | if (data->c) 190 | free(data->c); 191 | if (data->nc) 192 | free(data->nc); 193 | if (data->aL) 194 | free(data->aL); 195 | if (data->aR) 196 | free(data->aR); 197 | } 198 | -------------------------------------------------------------------------------- /+iosr/Contents.m: -------------------------------------------------------------------------------- 1 | % +IOSR 2 | % 3 | % Contents file for +IOSR and its subfolders. 4 | % 5 | % +IOSR 6 | % iosr.install - Set search paths, and download and install dependencies 7 | % 8 | % +IOSR/+ACOUSTICS 9 | % iosr.acoustics.irStats - Calculate RT, DRR, Cte, and EDT for impulse response file 10 | % iosr.acoustics.rtEst - Estimate reverberation time based on room size and absorption 11 | % 12 | % +IOSR/+AUDITORY 13 | % iosr.auditory.azimuth2itd - Convert azimuth in degrees to ITD 14 | % iosr.auditory.binSearch - Conduct a binary search 15 | % iosr.auditory.calcIld - Calculate normalised interaural level difference 16 | % iosr.auditory.chXcorr - Calculate cross-correlograms with a wide range of options 17 | % iosr.auditory.chXcorr2 - Calculate cross-correlograms with a range of options 18 | % chXcorr2_c.c 19 | % chXcorr_c.c 20 | % iosr.auditory.createWindow - Create a Hann or exp. window with specified onsets/offsets 21 | % iosr.auditory.dupWeight - Calculate duplex weighting coefficients for ITD and ILD 22 | % iosr.auditory.erbRate2hz - Convert ERB rate to Hz 23 | % iosr.auditory.freqMulti - Calculate frequency coefficient for ITD-azimuth warping 24 | % iosr.auditory.gammatoneFast - Produce an array of responses from gammatone filters via FFT 25 | % iosr.auditory.hz2erbRate - Convert Hz to ERB rate 26 | % iosr.auditory.iso226 - ISO 226:2003 Normal equal-loudness-level contours 27 | % iosr.auditory.itd2azimuth - Convert ITD to azimuth 28 | % iosr.auditory.lindemannInh - Signal pre-processing for Lindemann's cross-correlation 29 | % iosr.auditory.loudWeight - Calculate loudness weighting coefficients 30 | % iosr.auditory.makeErbCFs - Make a series of center frequencies equally spaced in ERB-rate 31 | % iosr.auditory.meddisHairCell - Calculate Ray Meddis' hair cell model for a number of channels 32 | % iosr.auditory.perceptualCentroid - Perceptual spectral centroid 33 | % iosr.auditory.perceptualCentroid2 - Alternative perceptual spectral centroid 34 | % iosr.auditory.xcorrLindemann - Cross-correlation based on Lindemann's precedence model 35 | % xcorrLindemann_c.c 36 | % 37 | % +IOSR/+BSS 38 | % iosr.bss.applyIdealMasks - Calculate and apply ideal masks via STFT 39 | % iosr.bss.applyMask - Apply a time-frequency mask to an STFT 40 | % iosr.bss.calcImr - Calculates the Ideal Mask Ratio (IMR) 41 | % iosr.bss.calcSnr - Calculate the separation SNR 42 | % iosr.bss.cfs2fcs - Calculate gammatone crossover frequencies 43 | % iosr.bss.example - Determine STFT parameters 44 | % iosr.bss.generateMixtures - Generate arrays of mixtures from targets and interferers 45 | % iosr.bss.getFullMask - Convert frame rate mask to a sample-by-sample mask 46 | % iosr.bss.idealMasks - Calculate ideal time-frequency masks from STFTs 47 | % iosr.bss.mixture - Class of sound source separation mixture 48 | % iosr.bss.resynthesise - Resynthesise a target from a time-frequency mask 49 | % iosr.bss.source - Class of sound source separation source 50 | % 51 | % +IOSR/+DSP 52 | % iosr.dsp.audio - Abstract superclass providing audio-related properties and methods 53 | % iosr.dsp.autocorr - Perform autocorrelation via FFT 54 | % iosr.dsp.convFft - Convolve two vectors using FFT multiplication 55 | % iosr.dsp.istft - Calculate the Inverse Short-Time Fourier Transform 56 | % iosr.dsp.lapwin - Laplace window 57 | % iosr.dsp.localpeaks - Find local peaks and troughs in a vector 58 | % iosr.dsp.ltas - Calculate the long-term average spectrum of a signal 59 | % iosr.dsp.matchEQ - Match the LTAS of a signal to an arbitrary spectral magnitude 60 | % iosr.dsp.rcoswin - Raised cosine window 61 | % iosr.dsp.rms - Calculate the rms of a vector or matrix 62 | % iosr.dsp.sincFilter - Apply a near-ideal low-pass or band-pass brickwall filter 63 | % iosr.dsp.smoothSpectrum - Apply 1/N-octave smoothing to a frequency spectrum 64 | % iosr.dsp.stft - Calculate the short-time Fourier transform of a signal 65 | % iosr.dsp.vsmooth - Smooth a vector using mathematical functions 66 | % 67 | % +IOSR/+FIGURES 68 | % iosr.figures.chMap - Create a monochrome-compatible colour map 69 | % iosr.figures.cmrMap - Create a monochrome-compatible colour map 70 | % iosr.figures.multiwaveplot - Stacked line plots from a matrix or vectors 71 | % iosr.figures.subfigrid - Create axis positions for subfigures 72 | % 73 | % +IOSR/+GENERAL 74 | % iosr.general.cell2csv - Output a cell array to a CSV file 75 | % iosr.general.checkMexCompiled - Check if mex file is compiled for system 76 | % iosr.general.getContents - Get the contents of a specified directory 77 | % iosr.general.updateContents - Create a Contents.m file including subdirectories 78 | % iosr.general.urn - Generate random number sequence without duplicates 79 | % 80 | % +IOSR/+STATISTICS 81 | % iosr.statistics.boxPlot - Draw a box plot 82 | % iosr.statistics.functionalBoxPlot - Draw a functional boxplot 83 | % iosr.statistics.functionalPlot - Abstract superclass for functional plots 84 | % iosr.statistics.functionalSpreadPlot - Draw a functional plot showing data spread 85 | % iosr.statistics.getRmse - Calculate the root-mean-square error between input data 86 | % iosr.statistics.laprnd - Pseudorandom numbers drawn from the Laplace distribution 87 | % iosr.statistics.qqPlot - Quantile-quantile plot with patch option 88 | % iosr.statistics.quantile - Quantiles of a sample via various methods 89 | % iosr.statistics.statsPlot - An abstract superclass for classes that plot statistics 90 | % iosr.statistics.tab2box - Prepare tabular data for boxPlot function 91 | % iosr.statistics.trirnd - Pseudorandom numbers drawn from the triangular distribution 92 | % 93 | % +IOSR/+SVN 94 | % iosr.svn.buildSvnProfile - Read data from files tagged with SVN keywords 95 | % iosr.svn.headRev - Retrieve the head revision for specified files 96 | % iosr.svn.readSvnKeyword - Read data from a file tagged with an SVN keyword 97 | % 98 | % This file was generated by updateContents.m on 31 May 2017 at 17:06:32. 99 | -------------------------------------------------------------------------------- /+iosr/+general/getContents.m: -------------------------------------------------------------------------------- 1 | function [cont,dirflag] = getContents(directory,varargin) 2 | %GETCONTENTS Get the contents of a specified directory 3 | % 4 | % This function returns the contents of a specified directory. 5 | % 6 | % CONT = IOSR.GENERAL.GETCONTENTS(DIRECTORY) returns the files and 7 | % folders in a directory and returns them to the cell array cont. It 8 | % ignores hidden files and folders (those starting '.'). DIRECTORY must 9 | % be a character array (string). 10 | % 11 | % CONT = IOSR.GENERAL.GETCONTENTS(DIRECTORY,'PARAMETER',VALUE) allows 12 | % search options to be specified. The options include: 13 | % 'rec' {false} | true 14 | % Search recursively within the subfolders of the 15 | % specified directory. 16 | % 'path' {'relative'} | 'full' 17 | % Specifies whether returned paths are full or relative 18 | % to the specified directory. 19 | % 'sort' {false} | true 20 | % Specify whether the output is sorted alphabetically. 21 | % 'filter' {'all'} | 'files' | 'folders' | '*.ext' | str 22 | % This option allows a filter to be specified. 'files' 23 | % returns names of all files in the directory. 'folders' 24 | % returns names of all folders in the directory. '*.ext', 25 | % where 'ext' is a user-specified file extension, returns 26 | % all files with the extension '.ext'. str may be any 27 | % string; only elements that contain str will be returned 28 | % (files or folders). str is case-sensitive. 29 | % 30 | % [CONT,DIRFLAG] = IOSR.GENERAL.GETCONTENTS(...) returns a logical array 31 | % DIRFLAG, the same size as CONT, indicating whether each element is a 32 | % directory. 33 | % 34 | % Examples 35 | % 36 | % Ex. 1 37 | % 38 | % % Return all m-files in the current directory 39 | % 40 | % cont = iosr.general.getContents(cd,'filter','*.m') 41 | % 42 | % Ex. 2 43 | % 44 | % % Return all files in the current directory and its 45 | % % sub-directories 46 | % 47 | % cont = iosr.general.getContents(cd,'rec',true) 48 | % 49 | % Ex. 3 50 | % 51 | % % Return all files in current directory with names 52 | % % containing 'foo' 53 | % 54 | % % may return files and folders: 55 | % [cont,dirflag] = iosr.general.getContents(cd,'filter','foo') 56 | % 57 | % % use dirflag to limit: 58 | % cont = cont(~dirflag); 59 | 60 | % Copyright 2016 University of Surrey. 61 | 62 | % parse input arguments and arrange call(s) to 'main', which 63 | % does the actual searching of directories 64 | 65 | assert(ischar(directory), 'iosr:getContents:invalidDir', 'directory must be a character array') 66 | 67 | % Switch trap parses the varargin inputs 68 | % default values 69 | recflag = false; 70 | pathflag = 'relative'; 71 | sortflag = false; 72 | str = 'all'; 73 | % find values 74 | for i = 1:2:length(varargin) 75 | switch lower(varargin{i}) 76 | case 'path' 77 | pathflag=varargin{i+1}; 78 | case 'rec' 79 | recflag=varargin{i+1}; 80 | case 'sort' 81 | sortflag=varargin{i+1}; 82 | case 'filter' 83 | str=varargin{i+1}; 84 | otherwise 85 | error('iosr:getContents:unknownOption','Unknown option: %s\n',varargin{i}); 86 | end 87 | end 88 | 89 | % check input options 90 | assert(ischar(pathflag), 'iosr:getContents:invalidPath', '''path'' option must be a string') 91 | assert(strcmp(pathflag,'relative') | strcmp(pathflag,'full'),... 92 | 'iosr:getContents:invalidPath', ... 93 | '''path'' option must ''relative'' or ''full''') 94 | assert(islogical(recflag) & numel(recflag)==1, 'iosr:getContents:invalidRec', '''rec'' option must be logical') 95 | assert(islogical(sortflag) & numel(sortflag)==1, 'iosr:getContents:invalidSoftFlag', '''sort'' option must be a logical') 96 | assert(ischar(str), 'iosr:getContents:invalidStr', 'str must be a character array') 97 | 98 | % first pass: contents of top-level folder 99 | [cont,dirflag] = main(directory,str); 100 | 101 | % do the recursive bit, if recursion is requested 102 | if recflag 103 | dirs = main(directory,'folders'); 104 | count = length(dirs); 105 | n = 1; 106 | while n <= count % recursion requested 107 | [cont_temp,dirflag_temp] = main(dirs{n},str); % search them 108 | cont = [cont; cont_temp]; %#ok append search results 109 | dirflag = [dirflag; dirflag_temp]; %#ok append search results 110 | sdirs = main(dirs{n},'folders'); 111 | dirs = [dirs; sdirs]; %#ok 112 | count = length(dirs); 113 | n = n+1; 114 | end 115 | end 116 | 117 | % remove full path 118 | if strcmp(pathflag,'relative') 119 | if ~strcmp(directory(end),filesep) 120 | directory = [directory filesep]; 121 | end 122 | for n = 1:length(cont) 123 | cont{n} = strrep(cont{n}, directory, ''); 124 | end 125 | end 126 | 127 | % sort output (case insensitive) 128 | if sortflag 129 | [~,IX] = sort(lower(cont)); 130 | cont = cont(IX); 131 | dirflag = dirflag(IX); 132 | end 133 | 134 | end 135 | 136 | function [cont,dirflag] = main(directory,str) 137 | %MAIN get the contents 138 | 139 | list = struct2cell(dir(directory)); 140 | dirbool = cell2mat(list(cellfun(@islogical,list(:,1)),:)); % return directory flags 141 | list = list(1,:); % keep only file names 142 | X = ~strncmp(list, '.', 1); % remove hidden files (those starting '.') 143 | list = list(X); 144 | list = list(:); % make column vector 145 | dirbool = dirbool(X); 146 | dirbool = dirbool(:); % make column vector 147 | 148 | for n = 1:length(list) 149 | list{n} = fullfile(directory,list{n}); 150 | end 151 | 152 | if nargin > 1 153 | % find filename extensions 154 | exts = cell(size(list)); 155 | for n = 1:length(list) 156 | [~,~,exts{n}] = fileparts(list{n}); 157 | end 158 | % filter 159 | if strncmp(str,'*.',2) % if extensions are requested 160 | ext = str(2:end); 161 | str = 'ext'; 162 | end 163 | switch lower(str) 164 | case 'files' 165 | Y = ~dirbool; 166 | case 'folders' 167 | Y = dirbool; 168 | case 'ext' 169 | Y = strcmp(exts,ext); 170 | case 'all' 171 | Y = true(size(dirbool)); 172 | otherwise % use literal search string 173 | Y = ~cellfun(@isempty,strfind(list,str)); 174 | end 175 | else 176 | Y = true(size(list)); 177 | end 178 | 179 | % return search results 180 | cont = list(Y); 181 | dirflag = dirbool(Y); 182 | 183 | end 184 | -------------------------------------------------------------------------------- /+iosr/+auditory/createWindow.m: -------------------------------------------------------------------------------- 1 | function OutWin = createWindow(varargin) 2 | % Create a Hann or exp. window with specified onsets/offsets 3 | % 4 | % OutWin = iosr.auditory.createWindow(WinLen, Type, Duration) 5 | % 6 | % Other (non-symmetric) uses include: 7 | % 8 | % OutWin = iosr.auditory.createWindow(WinLen, ... 9 | % Hann, ... 10 | % OnDuration, ... 11 | % Hann, ... 12 | % OffDuration,) 13 | % OutWin = iosr.auditory.createWindow(WinLen, ... 14 | % Hann, ... 15 | % OnDuration, ... 16 | % Exp, ... 17 | % OffDuration, ... 18 | % OffSlope) 19 | % OutWin = iosr.auditory.createWindow(WinLen, ... 20 | % Exp, ... 21 | % OnDuration, ... 22 | % OnSlope, ... 23 | % Hann, ... 24 | % OffDuration) 25 | % OutWin = iosr.auditory.createWindow(WinLen, ... 26 | % Exp, ... 27 | % OnDuration, ... 28 | % OnSlope, ... 29 | % Exp, ... 30 | % OffDuration, ... 31 | % OffSlope) 32 | % 33 | % OutWin = iosr.auditory.createWindow(type, samples) 34 | % OutWin = iosr.auditory.createWindow(Ontype, Onsamples, ... 35 | % Offtype, Offsamples) 36 | % 37 | % In the first mode of operation, the onset and offset are assumed to be 38 | % identical (symmetric window). In other modes, the OnRamp and OffRamp 39 | % durations (and slopes if necessary) are specified independently 40 | % 41 | % OutWin: the created window 42 | % 43 | % WinLen: the total duration (in samples) of the desired window 44 | % 45 | % Type (inc. Ontype and Offtype): 46 | % 47 | % - 'Hann': a raised cosine ramp, following value should always be the 48 | % Duration of the ramp 49 | % 50 | % - 'Exp': an exponential ramp, following value should be always be the 51 | % Duration of the ramp, and then the Slope. e.g Slope > 3 very shallow, 52 | % slope < 0.1 very steep 53 | % 54 | % Duration: number of samples over which the OnRamp and OffRamp are applied 55 | % 56 | % Slope: the steepness of the exponential ramp 57 | % 58 | % Example case: 100 samples onset Hann, followed by 50 unramped samples 59 | % (i.e. value = 1), followed by 500 samples of shallow exponential offset 60 | % ramp 61 | % 62 | % OutWin = iosr.auditory.createWindow(650, 'Hann', 100, 'Exp', 500, 3) 63 | 64 | % Copyright 2016 University of Surrey. 65 | 66 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 67 | % Begin Create Window 68 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 69 | 70 | 71 | %% test input types 72 | assert(isnumeric(varargin{1}),['1st input should be window length' ... 73 | 'specified in samples']); 74 | assert(size(varargin, 2)>2,'not enough input arguments'); 75 | assert(size(varargin, 2)<8,'too many input arguments'); 76 | 77 | %% extract inputs 78 | nInputs = (size(varargin, 2)); 79 | WinLen = varargin{1}; 80 | Slope = [0 0]; 81 | switch nInputs 82 | 83 | case 3 % Hann or Rec symmetric window 84 | assert(~strcmp(varargin{2},('Exp')),'Must specify slope of exponential'); 85 | assert(strcmp(varargin{2},('Hann')) || strcmp(varargin{2},('Rec')),'Input type not specified'); 86 | RampOn = varargin{2}; 87 | RampOff = varargin{2}; 88 | RampOnDur = varargin{3}; 89 | RampOffDur = varargin{3}; 90 | case 4 % Exp symmetric window 91 | assert(strcmp(varargin{2},('Exp')),'wrong number of input arguments or ramp type'); 92 | assert(varargin{4}~=0,'exponential slope may not be 0'); 93 | RampOn = 'Exp'; 94 | RampOff = 'Exp'; 95 | RampOnDur = varargin{3}; 96 | RampOffDur = varargin{3}; 97 | Slope(1) = varargin{4}; 98 | case 5 % Hann or Rec ramp on and ramp off 99 | assert(strcmp(varargin{2},('Hann')) || strcmp(varargin{2},('Rec')),'Input type not specified'); 100 | assert(strcmp(varargin{4},('Hann')) || strcmp(varargin{4},('Rec')),'Input type not specified'); 101 | RampOn = varargin{2}; 102 | RampOff = varargin{4}; 103 | RampOnDur = varargin{3}; 104 | RampOffDur = varargin{5}; 105 | case 6 % (Hann or Rec) + Exp, or Exp + (Hann or Rec) 106 | if (strcmp(varargin{2},'Hann')||strcmp(varargin{2},'Rec')) 107 | RampOn = varargin{2}; 108 | RampOnDur = varargin{3}; 109 | RampOff = 'Exp'; 110 | RampOffDur = varargin{5}; 111 | Slope(2) = varargin{6}; 112 | elseif strcmp(varargin{2},'Exp') 113 | RampOn = 'Exp'; 114 | RampOnDur = varargin{3}; 115 | assert(isnumeric(varargin{4}),'exponential slope should be specified as arg 4'); 116 | assert(varargin{4}~=0,'exponential slope may not be 0'); 117 | Slope(1) = varargin{4}; 118 | RampOff = varargin{5}; 119 | RampOffDur = varargin{6}; 120 | else 121 | error('incorrect input arguments'); 122 | end 123 | case 7 % Ramp on = Exp, Ramp off = Exp 124 | assert(strcmp(varargin{2},('Exp')) && strcmp(varargin{2},('Exp')),'both ramps should be Exp for 7 input arguments'); 125 | RampOn = 'Exp'; 126 | RampOff = 'Exp'; 127 | RampOnDur = varargin{3}; 128 | Slope(1) = varargin{4}; 129 | RampOffDur = varargin{6}; 130 | Slope(2) = varargin{7}; 131 | otherwise 132 | error('incorrect number of inputs'); 133 | end 134 | assert(WinLen>=((RampOnDur)+(RampOffDur)),'ramp lengths greater than signal duration!'); 135 | 136 | %% Test Hann gives correct answer for dummy example 137 | assert( 0.01 > ( max(abs( AppWin(100,'Hann',10,'Hann',10) - ... 138 | [ 0; 0.0245; 0.0955; 0.2061; 0.3455; 0.5000; ... 139 | 0.6545; 0.7939; 0.9045; 0.9755; ... 140 | ones(80,1); ... 141 | 0.9755; 0.9045; 0.7939; 0.6545; 0.5000; ... 142 | 0.3455; 0.2061; 0.0955; 0.0245; 0 ]))), ... 143 | 'Hann not functioning corectly'); 144 | 145 | %% Do Work 146 | OutWin = AppWin(WinLen,RampOn,RampOnDur,RampOff,RampOffDur,Slope); 147 | 148 | end 149 | 150 | % ---------------------------------------------------------- 151 | % DO AppWin 152 | % ---------------------------------------------------------- 153 | function OutWin = AppWin(WinLen,RampOn,RampOnDur,RampOff,RampOffDur,Slope) 154 | 155 | % generate rectangular window 156 | OutWin = ones(WinLen,1); 157 | 158 | %% Create the ramp on 159 | switch RampOn 160 | case 'Hann' 161 | OutWin(1:RampOnDur) = (1-cos(pi/RampOnDur*[0:RampOnDur-1])) / 2; 162 | 163 | case 'Exp' 164 | OutWin(1:RampOnDur) = exp(([1:RampOnDur]./(Slope(1)*RampOnDur))-(1/Slope(1))); 165 | 166 | case 'Rec' 167 | 168 | otherwise 169 | error('ramp on unspecified'); 170 | end 171 | 172 | %% Create the ramp off 173 | switch RampOff 174 | case 'Hann' 175 | OutWin(end-RampOffDur+1:end) = (1+cos(pi/RampOffDur*[1:RampOffDur]))/2; 176 | 177 | case 'Exp' 178 | OutWin(end-RampOffDur+1:end) = exp((fliplr([1:RampOffDur])./(Slope(2)*RampOffDur))-(1/Slope(2))); 179 | 180 | case 'Rec' 181 | 182 | otherwise 183 | error('ramp off unspecified'); 184 | end 185 | 186 | end 187 | -------------------------------------------------------------------------------- /+iosr/+auditory/iso226.m: -------------------------------------------------------------------------------- 1 | function [spl, f, params] = iso226(phon,fq,sq) 2 | %ISO226 ISO 226:2003 Normal equal-loudness-level contours 3 | % 4 | % [SPL,F] = IOSR.AUDITORY.ISO226(PHON) returns the sound pressure level 5 | % (SPL) (dB) of pure tone frequencies F (Hz) at the loudness level(s) 6 | % PHON. The values are calculated according to ISO 226:2003 using the 7 | % reference frequencies specified in the standard. According to the 8 | % standard, PHON is only valid at all frequencies if 20<=PHON<80 9 | % (although the function will return SPL values outside of this range). 10 | % If PHON is 0, the threshold of hearing is returned. 11 | % 12 | % PHON may be an array of any size; SPL and F will be of size 13 | % [1,29,M,N,P,...] where M,N,P,... are the dimensions of PHON. 14 | % 15 | % [SPL,F] = IOSR.AUDITORY.ISO226(PHON,FQ) returns the SPL of the pure 16 | % tone frequencies in FQ at the specified loudness level(s). For 17 | % non-standard frequencies, the SPL is calculated by interpolating the 18 | % parameters used in its calculation. According to the standard, FQ is 19 | % only valid between 20 Hz and 12.5 kHz; the function will extrapolate 20 | % SPL values above 12.5 kHz by mirroring 20 Hz values at 20 kHz. 21 | % 22 | % FQ may be an array of any size; SPL and F will be of size 23 | % [Q,R,S,...,M,N,P,...] where Q,R,S,... are the dimensions of FQ. 24 | % 25 | % ... = IOSR.AUDITORY.ISO226(PHON,FQ,SQ) specifies whether singleton 26 | % dimensions will be removed from the output. With sq=false, singleton 27 | % dimensions will be retained (default), else they will be removed. 28 | % 29 | % ... = IOSR.AUDITORY.ISO226(PHON,[],SQ) uses the standard reference 30 | % frequencies for SPL calculations. 31 | % 32 | % [SPL,F,PARAMS] = IOSR.AUDITORY.ISO226(...) returns the reference 33 | % parameters used to calculate the normal equal-loudness-level contours. 34 | % PARAMAS is a structure with the following fields: 35 | % 'f' : the reference frequencies, 36 | % 'alpha_f' : the exponent of loudness perception, 37 | % 'L_U' : magnitude of the linear transfer function normalized 38 | % at 1000 Hz, and 39 | % 'T_f' : the threshold of hearing. 40 | % 41 | % Example 42 | % 43 | % % Plot equal-loudness contours between 20 and 80 phon 44 | % 45 | % % Calculate SPLs 46 | % phons = 20:10:80; 47 | % [spl,f] = iosr.auditory.iso226(phons,[],true); 48 | % 49 | % % plot 50 | % figure; semilogx(f,spl) 51 | % set(gca,'xlim',[min(f(:)) max(f(:))]) 52 | % legend(num2str(phons'),'location','southwest'); 53 | % title('Equal loudness contours for different loudness levels (in phons)') 54 | % xlabel('Frequency [Hz]') 55 | % ylabel('SPL [dB]') 56 | % 57 | % See also IOSR.AUDITORY.LOUDWEIGHT. 58 | 59 | % Copyright 2016 University of Surrey. 60 | 61 | %% Check input 62 | 63 | if any(phon > 80) 64 | warning('iosr:iso226:phonRange','SPL values may not be accurate for loudness levels above 80 phon.') 65 | elseif any(phon(phon~=0) < 20) 66 | warning('iosr:iso226:phonRange','SPL values may not be accurate for loudness levels below 20 phon.') 67 | end 68 | 69 | if nargin>1 70 | if ~isempty(fq) 71 | if any(fq(:) < 20 | fq(:) > 4000) && any(phon > 90) 72 | warning('iosr:iso226:frequencyRange','ISO 226:2003 is valid for 20?4000 Hz only up to 90 phon. SPL values may be inaccurate.') 73 | elseif any(fq(:) < 5000 | fq(:) > 12500) && any(phon > 80) 74 | warning('iosr:iso226:frequencyRange','ISO 226:2003 is valid for 5000?12500 Hz only up to 80 phon. SPL values may be inaccurate.') 75 | elseif any(fq(:)>12500) 76 | warning('iosr:iso226:frequencyRange','ISO 226:2003 defines loudness levels up to 12.5 kHz. SPL values for frequencies above 12.5 kHz may be inaccurate.') 77 | end 78 | assert(all(fq(:)>=0), 'iosr:iso226:invalidFrequencies', 'Frequencies must be greater than or equal to 0 Hz.') 79 | end 80 | else 81 | fq = []; 82 | end 83 | 84 | if nargin<3 85 | sq = false; 86 | else 87 | assert(islogical(sq), 'iosr:iso226:invalidSq', 'sq must be logical.') 88 | end 89 | 90 | %% References 91 | 92 | % reference frequencies 93 | params.f = [20 25 31.5 40 50 63 80 100 125 160 200 250 315 400 ... 94 | 500 630 800 1000 1250 1600 2000 2500 3150 4000 5000 ... 95 | 6300 8000 10000 12500]; 96 | 97 | % exponent of loudness perception 98 | params.alpha_f = [0.532 0.506 0.480 0.455 0.432 0.409 0.387 ... 99 | 0.367 0.349 0.330 0.315 0.301 0.288 0.276 0.267 0.259... 100 | 0.253 0.250 0.246 0.244 0.243 0.243 0.243 0.242 0.242... 101 | 0.245 0.254 0.271 0.301]; 102 | 103 | % magnitude of linear transfer function normalized at 1 kHz 104 | params.L_U = [-31.6 -27.2 -23.0 -19.1 -15.9 -13.0 -10.3 -8.1 ... 105 | -6.2 -4.5 -3.1 -2.0 -1.1 -0.4 0.0 0.3 0.5 0.0 -2.7 ... 106 | -4.1 -1.0 1.7 2.5 1.2 -2.1 -7.1 -11.2 -10.7 -3.1]; 107 | 108 | % threshold of hearing 109 | params.T_f = [78.5 68.7 59.5 51.1 44.0 37.5 31.5 26.5 22.1 17.9... 110 | 14.4 11.4 8.6 6.2 4.4 3.0 2.2 2.4 3.5 1.7 -1.3 -4.2... 111 | -6.0 -5.4 -1.5 6.0 12.6 13.9 12.3]; 112 | 113 | %% Calculate 114 | 115 | % determine frequency range 116 | if isempty(fq) 117 | f = params.f; 118 | else 119 | f = fq; 120 | end 121 | 122 | % output size 123 | out_dims = [size(f) size(phon)]; 124 | 125 | % independent outputs 126 | f_squeeze = zeros(numel(f),numel(phon)); 127 | spl_squeeze = zeros(numel(f),numel(phon)); 128 | 129 | % iterate through phons 130 | for p = 1:numel(phon) 131 | % frequencies for phon level 132 | f_squeeze(:,p) = f(:); 133 | % interpolate reference parameters 134 | if nargin>1 135 | if any(f_squeeze(:,p) > 12500) 136 | % extrapolate - mirror 20Hz behaviour at 20kHz 137 | f_r_extrap = [params.f 20000]; 138 | alpha_f_r_extrap = [params.alpha_f params.alpha_f(1)]; 139 | L_U_r_extrap = [params.L_U params.L_U(1)]; 140 | T_f_r_extrap = [params.T_f params.T_f(1)]; 141 | else 142 | f_r_extrap = params.f; 143 | alpha_f_r_extrap = params.alpha_f; 144 | L_U_r_extrap = params.L_U; 145 | T_f_r_extrap = params.T_f; 146 | end 147 | % interpolate parameters 148 | alpha_f = interp1(f_r_extrap, alpha_f_r_extrap, f_squeeze(:,p)', 'spline', 'extrap'); 149 | L_U = interp1(f_r_extrap, L_U_r_extrap, f_squeeze(:,p)', 'spline', 'extrap'); 150 | T_f = interp1(f_r_extrap, T_f_r_extrap, f_squeeze(:,p)', 'spline', 'extrap'); 151 | else 152 | alpha_f = params.alpha_f; 153 | L_U = params.L_U; 154 | T_f = params.T_f; 155 | end 156 | % calculate SPL 157 | A_f = 0.00447 * ((10^(0.025*phon(p)))-1.15) + ... 158 | ((0.4*(10.^(((T_f+L_U)./10)-9))).^alpha_f); 159 | if phon(p) > 0 160 | spl_squeeze(:,p) = ((10./alpha_f).*log10(A_f)) - L_U + 94; 161 | else 162 | spl_squeeze(:,p) = T_f; 163 | end 164 | end 165 | 166 | % reshape outputs 167 | f = reshape(f_squeeze,out_dims); 168 | spl = reshape(spl_squeeze,out_dims); 169 | 170 | % remove singleton dimensions if requested 171 | if sq 172 | f = squeeze(f); 173 | spl = squeeze(spl); 174 | end 175 | 176 | end 177 | -------------------------------------------------------------------------------- /+iosr/+dsp/ltas.m: -------------------------------------------------------------------------------- 1 | function [s,f] = ltas(x,fs,varargin) 2 | %LTAS calculate the long-term average spectrum of a signal 3 | % 4 | % S = IOSR.DSP.LTAS(X,FS) calculates the long-term average spectrum 5 | % (LTAS) of signal X, sampled at FS Hz. The spectrum is calculated from 6 | % the average power spectral density (PSD) obtained from a series of 7 | % overlapping FFTs; the FFT length is 4096, and the hop size is 2048. The 8 | % segments of X are Hann-windowed. The average PSD is then 9 | % Gaussian-smoothed to 1/3-octave resolution. 10 | % 11 | % X can be a vector, matrix, or multidimensional array; LTAS will operate 12 | % along the first non-signleton dimension, and return the LTAS for each 13 | % corresponding row/column/etc. 14 | % 15 | % S = IOSR.DSP.LTAS(X,FS,'PARAMETER','VALUE') allows numerous parameters 16 | % to be specified. These parameters are:- 17 | % 'dim' : {find(size(X)>1,1,'first')} | scalar 18 | % Specifies the dimension of operation (defaults to the first 19 | % non-singleton dimension). 20 | % 'graph' : {false} | true 21 | % Choose whether to plot a graph of the LTAS. 22 | % 'hop' : {NFFT/2} | scalar 23 | % Specifies the step size through X used to calculate each 24 | % segment. NFFT is determined by the 'win' parameter. 25 | % 'noct' : {3} | scalar 26 | % Apply 1/noct-octave smoothing to the frequency spectrum. 27 | % Setting 'noct' to 0 results in no smoothing. 28 | % 'scaling' : {'none'} | 'max0' 29 | % Specifies any scaling to apply to S. By default, no scaling is 30 | % applied. If scaling is set to 'max0', S will be scaled to have 31 | % a maximum value of 0dB. 32 | % 'units' : {dB} | 'none' 33 | % Specifies the output units. By default the PSD is calculated in 34 | % dB. Otherwise the PSD is returned directly. 35 | % 'win' : {4096} | scalar | vector 36 | % Specifies the window or FFT length NFFT used to calculate the 37 | % spectrum. If 'win' is a scalar, it specifies the FFT length, 38 | % and a Hann window is applied to each segment. If 'win' is a 39 | % vector, NFFT is the length of the vector, and the vector is 40 | % multiplied with each segment. 41 | % 42 | % [S,F] = IOSR.DSP.LTAS(...) returns the frequencies F for each 43 | % corresponding bin of S. 44 | % 45 | % Example 46 | % 47 | % % Plot the 1/6th-octave-smoothed LTAS of the Handel example 48 | % load handel.mat 49 | % figure 50 | % iosr.dsp.ltas(y,Fs,'noct',6,'graph',true); 51 | % 52 | % See also IOSR.DSP.STFT, IOSR.DSP.SMOOTHSPECTRUM. 53 | 54 | % Copyright 2016 University of Surrey. 55 | 56 | %% parse input 57 | 58 | if nargin < 2 59 | error('iosr:ltas:nargin''Not enough input arguments') 60 | end 61 | 62 | options = struct(... 63 | 'win',4096,... 64 | 'hop',[],... 65 | 'noct',3,... 66 | 'dim',[],... 67 | 'units','db',... 68 | 'scaling','none',... 69 | 'graph',false); 70 | 71 | % read parameter/value inputs 72 | if nargin>2 % if parameters are specified 73 | % read the acceptable names 74 | optionNames = fieldnames(options); 75 | % count arguments 76 | nArgs = length(varargin); 77 | if round(nArgs/2)~=nArgs/2 78 | error('iosr:ltas:nameValuePair','LTAS needs propertyName/propertyValue pairs') 79 | end 80 | % overwrite defults 81 | for pair = reshape(varargin,2,[]) % pair is {propName;propValue} 82 | IX = strcmpi(pair{1},optionNames); % find match parameter names 83 | if any(IX) 84 | % do the overwrite 85 | options.(optionNames{IX}) = pair{2}; 86 | else 87 | error('iosr:ltas:unknownOption','%s is not a recognized parameter name',pair{1}) 88 | end 89 | end 90 | end 91 | 92 | %% check and assign input 93 | 94 | % required inputs 95 | assert(isnumeric(x), 'iosr:ltas:invalidX', 'X must be numeric.'); 96 | assert(isscalar(fs), 'iosr:ltas:invalidFs', 'FS must be a scalar.'); 97 | assert(fs>0, 'iosr:ltas:invalidFs', 'FS must be greater than 0.'); 98 | assert(isint(fs), 'iosr:ltas:invalidFs', 'FS must be an integer.'); 99 | 100 | % determine fft parameters 101 | if numel(options.win)>1 && isvector(options.win) 102 | NFFT = length(options.win); 103 | win = options.win; 104 | elseif isscalar(options.win) 105 | NFFT = options.win; 106 | win = NFFT; 107 | else 108 | error('iosr:ltas:invalidWin','''WIN'' must be a vector or a scalar'); 109 | end 110 | assert(isint(NFFT) && NFFT>0, 'iosr:ltas:invalidNfft', '''WIN'' must be a positive integer'); 111 | 112 | % determine hop 113 | hop = options.hop; 114 | if isempty(hop) 115 | hop = fix(NFFT/2); 116 | end 117 | 118 | % smoothing 119 | Noct = options.noct; 120 | 121 | % dimension to operate along 122 | dim = options.dim; 123 | dims = size(x); 124 | if isempty(dim) 125 | dim = find(dims>1,1,'first'); 126 | else 127 | assert(isnumeric(dim),'iosr:ltas:invalidDim', 'DIM must be an integer'); 128 | assert(isint(dim), 'iosr:ltas:invalidDim', 'DIM must be an integer or empty'); 129 | assert(dim>0, 'iosr:ltas:invalidDim', 'DIM must be greater than 0') 130 | end 131 | 132 | %% permute and rehape x to operate down columns 133 | 134 | % number of useful coefficients 135 | if mod(NFFT,2)==0 136 | Nout = (NFFT/2)+1; 137 | else 138 | Nout = (NFFT+1)/2; 139 | end 140 | 141 | % reorder and permute 142 | order = mod(dim-1:dim+length(dims)-2,length(dims))+1; 143 | dims_shift = dims(order); 144 | x = rearrange(x,order,[dims_shift(1) prod(dims_shift(2:end))]); 145 | dims_out_shift = dims_shift; 146 | dims_out_shift(1) = Nout; 147 | 148 | %% calculate spectra 149 | 150 | % choose units function 151 | switch lower(options.units) 152 | case 'db' 153 | units = @(x) 10*log10(x); 154 | max0scale = @(s) s-max(s(:)); 155 | labelY = 'Power spectral density [dBFS]'; 156 | yscale = 'linear'; 157 | case 'none' 158 | units = @(x) x; 159 | max0scale = @(s) s./max(s(:)); 160 | labelY = 'Power spectral density'; 161 | yscale = 'log'; 162 | otherwise 163 | error('iosr:ltas:unknownUnits','Unknown units option ''%s''',options.units); 164 | end 165 | 166 | % do calculations 167 | s = zeros(dims_out_shift); 168 | for c = 1:size(x,2) 169 | [S,f] = iosr.dsp.stft(x(:,c),win,hop,fs); % short-time ft 170 | s(:,c) = mean(abs(S/NFFT).^2,2); % mean PSD 171 | s(:,c) = units(s(:,c)); % put into dB 172 | s(:,c) = iosr.dsp.smoothSpectrum(s(:,c),f,Noct); % smooth 173 | end 174 | 175 | % invert permutation 176 | s = irearrange(s,order,dims_out_shift); 177 | 178 | % scale output 179 | switch lower(options.scaling) 180 | case 'max0' 181 | s = max0scale(s); 182 | case 'none' 183 | % do nothing 184 | otherwise 185 | error('iosr:matchEQ:unknownScaling','Unknown scaling option ''%s''',options.scaling); 186 | end 187 | 188 | %% plot 189 | 190 | assert(islogical(options.graph) && numel(options.graph)==1, 'iosr:ltas:invalidGraph', '''graph'' option must be logical.') 191 | if options.graph 192 | semilogx(f,rearrange(s,order,[dims_out_shift(1) prod(dims_out_shift(2:end))])); 193 | xlabel('Frequency [Hz]'); 194 | ylabel(labelY); 195 | set(gca,'yscale',yscale); 196 | grid on 197 | if prod(dims_out_shift(2:end)) > 1 198 | legend(num2str((1:prod(dims_out_shift(2:end)))')); 199 | end 200 | end 201 | 202 | end 203 | 204 | function y = rearrange(x,order,shape) 205 | %REARRANGE reshape and permute to make target dim column 206 | y = permute(x,order); 207 | y = reshape(y,shape); 208 | end 209 | 210 | function y = irearrange(x,order,shape) 211 | %IREARRANGE reshape and permute to original size 212 | y = reshape(x,shape); 213 | y = ipermute(y,order); 214 | end 215 | 216 | function y = isint(x) 217 | %ISINT check if input is whole number 218 | y = x==round(x); 219 | end 220 | -------------------------------------------------------------------------------- /+iosr/+dsp/audio.m: -------------------------------------------------------------------------------- 1 | classdef (Abstract) audio < matlab.mixin.Copyable 2 | %AUDIO Abstract superclass providing audio-related properties and methods. 3 | % 4 | % The IOSR.DSP.AUDIO superclass is an abstract class that defines 5 | % audio-related properties and methods. As an abstract class, it cannot 6 | % be instantiated. 7 | % 8 | % IOSR.DSP.AUDIO is a subclass of MATLAB.MIXIN.COPYABLE, which is a 9 | % subclass of the HANDLE class. Hence subclasses of AUDIO are handle 10 | % classes. 11 | % 12 | % IOSR.DSP.AUDIO properties: 13 | % filename - Name of the corresponding audio file 14 | % fs - Sampling frequency (Hz) 15 | % signal - The sampled data (read-only) 16 | % 17 | % IOSR.DSP.AUDIO methods: 18 | % sound - Replay the audio signal 19 | % Static methods: 20 | % up_down_mix - Remix a source to a designated channel count 21 | % normalize - Normalize an audio signal 22 | % spat - Spatialise a sound using a SOFA file 23 | % 24 | % See also IOSR.BSS.MIXTURE, IOSR.BSS.SOURCE, HANDLE, 25 | % MATLAB.MIXIN.COPYABLE. 26 | 27 | % Copyright 2016 University of Surrey. 28 | 29 | properties (AbortSet) 30 | filename % Name of the audio file 31 | fs = 16000 % Sampling frequency (Hz) 32 | end 33 | 34 | properties (Abstract, Dependent, SetAccess = protected) 35 | signal % The sampled data (read-only) 36 | end 37 | 38 | properties (Access = protected) 39 | rendered = false % Flag indicates whether files match object 40 | end 41 | 42 | methods % public methods 43 | 44 | function obj = audio(filename,fs) 45 | %AUDIO Create the audio object 46 | 47 | if nargin > 0 48 | obj.filename = filename; 49 | end 50 | if nargin > 1 51 | obj.fs = fs; 52 | end 53 | 54 | end 55 | 56 | function sound(obj) 57 | %SOUND Replay the audio signal 58 | % 59 | % IOSR.DSP.AUDIO.SOUND() replays the audio signal. 60 | 61 | obj.replay(obj.signal) 62 | 63 | end 64 | 65 | % validate properties 66 | 67 | % validate filename 68 | function set.filename(obj,val) 69 | assert(ischar(val) || isempty(val), 'iosr:audio:invalidFile', 'FILENAME must be a char array or an empty array') 70 | obj.filename = val; 71 | obj.property_changed('filename',val); 72 | end 73 | 74 | % validate fs 75 | function set.fs(obj,val) 76 | assert(isscalar(val), 'iosr:audio:invalidFs', 'FS must be a scalar') 77 | assert(val > 0, 'iosr:audio:invalidFs', 'FS must be greater than 0') 78 | obj.fs = val; 79 | obj.property_changed('fs',val); 80 | end 81 | 82 | end 83 | 84 | methods (Access = protected) 85 | 86 | function property_changed(obj,name,val) %#ok 87 | %PROPERTY_CHANGED handle property changes 88 | 89 | obj.rendered = false; 90 | end 91 | 92 | function replay(obj,x) 93 | %REPLAY Replay any audio signal 94 | 95 | sound(obj.normalize(x),obj.fs) % replay 96 | 97 | end 98 | 99 | function set_properties(obj,vgin,propNames) 100 | %SET_PROPERTIES Parse varargin input to set object properties 101 | % 102 | % IOSR.DSP.AUDIO.SET_PROPERTIES(VGIN,PROPNAMES) searches through 103 | % the cell array VGIN (a cell array of parameter/value pairs) for 104 | % properties specified in the cell array PROPNAMES. Any matching 105 | % properties are written to OBJ. 106 | 107 | % count arguments 108 | nArgs = length(vgin); 109 | if round(nArgs/2)~=nArgs/2 110 | error('iosr:audio:nameValuePair',[class(obj) ' needs propertyName/propertyValue pairs']) 111 | end 112 | % overwrite defults 113 | for pair = reshape(vgin,2,[]) % pair is {propName;propValue} 114 | IX = strcmpi(pair{1},propNames); % find match parameter names 115 | if any(IX) 116 | % do the overwrite 117 | obj.(propNames{IX}) = pair{2}; 118 | else 119 | error('iosr:audio:unknownOption','%s is not a recognized parameter name',pair{1}) 120 | end 121 | end 122 | 123 | end 124 | 125 | function cpObj = copyElement(obj) 126 | %COPYELEMENT Overload copy method with additional functionality 127 | 128 | % Make a shallow copy of all properties 129 | cpObj = copyElement@matlab.mixin.Copyable(obj); 130 | 131 | end 132 | 133 | end 134 | 135 | methods (Static) 136 | 137 | function [y,gain] = normalize(x) 138 | %NORMALIZE Normalize an audio signal 139 | % 140 | % Y = IOSR.DSP.AUDIO.NORMALIZE(X) normalises X such that 141 | % ABS(Y(:))<1. 142 | % 143 | % [Y,GAIN] = IOSR.DSP.AUDIO.NORMALIZE(X) returns the scalar gain 144 | % GAIN applied to X in order to normalize it. 145 | 146 | gain = (1-((2*16)/(2^16)))/max(abs(x(:))); 147 | y = x.*gain; 148 | 149 | end 150 | 151 | function y = spat(SOFAfile,x,azimuth,elevation,fs) 152 | %SPAT Spatialise a sound using a SOFA file 153 | % 154 | % IOSR.DSP.AUDIO.SPAT(SOFAFILE,X,AZIMUTH,ELEVATION) spatialises 155 | % the signal X using spatialisation data contained in the SOFA 156 | % file SOFAFILE at the corresponding AZIMUTH and ELEVATION. The 157 | % sampling frequency is that of the SOFA data. X should be a 158 | % column vector; if it is not, it will be down-mixed to a column 159 | % vector. 160 | % 161 | % IOSR.DSP.AUDIO.SPAT(...,FS) uses the sampling frequency FS for 162 | % the spatialisation. If the SOFA data are not at the sampling 163 | % rate FS, the data are resampled. 164 | 165 | assert(size(x,1)>1, 'iosr:audio:invalidX', 'X should be a column vector. Matrices will be downmixed to a column vector'); 166 | 167 | if size(x,2)>1 % mix to mono 168 | x = audio.up_down_mix(x,1); 169 | end 170 | SOFAobj = SOFAload(SOFAfile); % load SOFA object 171 | if ~exist('fs','var')==1 % set default FS 172 | fs = SOFAobj.Data.SamplingRate; 173 | end 174 | 175 | % convert navigational coordinates to spherical coordinates 176 | if azimuth<0 177 | [azimuth,elevation] = nav2sph(azimuth,elevation); 178 | end 179 | 180 | % find index of corresponding IR is SOFA obj 181 | dist = (SOFAobj.SourcePosition(:,1)-azimuth).^2 + ... 182 | (SOFAobj.SourcePosition(:,2)-elevation).^2; 183 | [~,idx] = min(dist); 184 | 185 | % return IR 186 | IR = squeeze(SOFAobj.Data.IR(idx,:,:))'; 187 | 188 | % resample? 189 | if SOFAobj.Data.SamplingRate~=fs 190 | disp(['Resampling audio to ' num2str(fs) ' Hz.']) 191 | IR = resample(IR,fs,SOFAobj.Data.SamplingRate); 192 | end 193 | 194 | % convolve 195 | y = zeros(length(x)+size(IR,1)-1,size(IR,2)); 196 | for c = 1:size(IR,2) 197 | y(:,c) = iosr.dsp.convFft(x,IR(:,c)); 198 | end 199 | 200 | % crop 201 | y = y(1:length(x),:); 202 | 203 | end 204 | 205 | function y = up_down_mix(x,N) 206 | %UP_DOWN_MIX Remix a source to a designated channel count 207 | % 208 | % Y = IOSR.DSP.AUDIO.UP_DOWN_MIX(X,N) mixes the signal X to have 209 | % the specified channel count N. Data for each channel in X 210 | % should be stored down its columns. For input channel count M, 211 | % the up-/down-mix coefficients are calculated as 212 | % ONES(M,N)./(M*N). 213 | 214 | M = size(x,2); 215 | y = x*(ones(M,N)./(M*N)); 216 | 217 | end 218 | 219 | end 220 | 221 | methods (Static, Access = protected) 222 | 223 | function ensure_path(filename) 224 | %ENSURE_PATH Ensure path exists for specified filename 225 | 226 | % get path 227 | filepath = fileparts(filename); 228 | if ~isempty(filepath) 229 | % ensure path exits 230 | if exist(filepath,'dir')~=7 231 | mkdir(filepath) 232 | end 233 | end 234 | 235 | end 236 | 237 | end 238 | 239 | end 240 | -------------------------------------------------------------------------------- /+iosr/+bss/source.m: -------------------------------------------------------------------------------- 1 | classdef source < iosr.dsp.audio 2 | %SOURCE Class of sound source separation source. 3 | % 4 | % iosr.bss.source objects contain information about a sound source 5 | % (including its spatial location). iosr.bss.source objects are passed to 6 | % iosr.bss.mixture objects in order to create a mixture. 7 | % 8 | % IOSR.BSS.SOURCE is a subclass of IOSR.DSP.AUDIO. 9 | % 10 | % IOSR.BSS.SOURCE properties: 11 | % azimuth - Azimuth of the source 12 | % elevation - Elevation of the source 13 | % numchans - Number of audio channels in the source 14 | % precomposed - Logical flag indicating whether the source should be 15 | % spatialised 16 | % 17 | % IOSR.BSS.SOURCE methods: 18 | % source - Create a source object 19 | % copy - Create an independent copy of the source (but not its 20 | % audio file) 21 | % write - Write the source audio file (and resample/downmix) 22 | % 23 | % See also IOSR.DSP.AUDIO, IOSR.BSS.MIXTURE. 24 | 25 | % Copyright 2016 University of Surrey. 26 | 27 | properties (AbortSet) 28 | azimuth % Azimuth of the source 29 | elevation % Elevation of the source 30 | numchans % Number of audio channels in the source 31 | precomposed % Logical flag indicating whether the source should be spatialised 32 | end 33 | 34 | properties (Dependent, SetAccess = protected) 35 | signal % The sampled data (read-only) 36 | end 37 | 38 | properties (GetAccess = private, SetAccess = public) 39 | parent 40 | end 41 | 42 | methods 43 | 44 | % constructor 45 | function obj = source(filename,varargin) 46 | %SOURCE Create a source object 47 | % 48 | % OBJ = IOSR.BSS.SOURCE(FILENAME) creates a source whose signal 49 | % is contained in the audio file FILENAME. The source will have 50 | % an azimuth and elevation of 0. The number of channels and 51 | % sampling frequency will be determined by the audio file. 52 | % 53 | % OBJ = IOSR.BSS.SOURCE(...,'PARAMETER',VALUE) allows additional 54 | % options to be specified. The options are ({} indicate 55 | % defaults): 56 | % 57 | % 'azimuth' : {0} | scalar 58 | % The azimuth of the source for rendering. 59 | % 'elevation' : {0} | scalar 60 | % The elevation of the source for rendering. 61 | % 'fs' : {fs of FILENAME} | scalar 62 | % The sampling frequency of the source. If the sampling 63 | % frequency of the audio file FILENAME does not match, 64 | % the audio file will be resampled each time the signal 65 | % is requested. 66 | % 'precomposed' : {false} | true 67 | % Logical flag indicating whether the source should be 68 | % spatialised. By default it is assumed that the source 69 | % is a point source (irrespective of its channel count), 70 | % and it will be spatialised. When set to true, the 71 | % source will be summed directly with the spatial signal. 72 | % 'numchans' : {numchans of FILENAME} | scalar 73 | % The number of channels in the source. If this does not 74 | % match the channel count of the audio file FILENAME, the 75 | % audio file will be up-/down-mixed each time the signal 76 | % is requested. 77 | % 78 | % The FS and NUMCHANS parameters do not affect the underlying 79 | % audio file, but are implemented on-the-fly each time the signal 80 | % is requested. To render the changes to a new audio file, use 81 | % the write() method. 82 | % 83 | % OBJ = IOSR.BSS.SOURCE creates an empty source object. 84 | % 85 | % Note that this is a handle class. Sources are hence passed by 86 | % reference. Use the COPY() method to create an independent copy 87 | % of the source. 88 | % 89 | % See also IOSR.DSP.AUDIO.UP_DOWN_MIX. 90 | 91 | if nargin > 0 92 | 93 | propNames = {'azimuth','elevation','fs','precomposed','numchans'}; 94 | 95 | obj.filename = filename; 96 | info = audioinfo(obj.filename); 97 | 98 | % defaults 99 | obj.azimuth = 0; 100 | obj.elevation = 0; 101 | obj.precomposed = false; 102 | obj.fs = info.SampleRate; 103 | obj.numchans = info.NumChannels; 104 | obj.rendered = false; 105 | 106 | % read parameter/value inputs 107 | if nargin > 1 % if parameters are specified 108 | obj.set_properties(varargin,propNames) 109 | end 110 | 111 | end 112 | 113 | end 114 | 115 | function write(obj,filename) 116 | %WRITE Write the source audio file (and resample/downmix) 117 | % 118 | % IOSR.BSS.SOURCE.WRITE() overwrites the source's audio file. If 119 | % the source audio file's sampling rate and/or channel count do 120 | % not match the object's properties, then the audio file will be 121 | % resampled and/or up- or down-mixed accordingly. 122 | % 123 | % IOSR.BSS.SOURCE.WRITE(FILENAME) writes the audio file to the 124 | % specified file FILENAME and updates SOURCE.FILENAME. 125 | % 126 | % See also IOSR.DSP.AUDIO.UP_DOWN_MIX. 127 | 128 | % overwrite filename if one is specified 129 | if exist('filename','var')==1 130 | obj.filename = filename; 131 | end 132 | 133 | assert(~isempty(obj.filename), 'iosr:source:invalidFile', 'SOURCE.FILENAME is empty.') 134 | 135 | % ensure path exists 136 | obj.ensure_path(obj.filename) 137 | 138 | % write new file and update filename 139 | audiowrite(obj.filename,obj.normalize(obj.signal),obj.fs); 140 | obj.rendered = true; 141 | 142 | end 143 | 144 | % validate properties 145 | 146 | % validate azimuth 147 | function set.azimuth(obj,val) 148 | assert(isscalar(val), 'iosr:source:invalidAzimuth', 'AZIMUTH must be a scalar') 149 | obj.azimuth = val; 150 | obj.property_changed('azimuth',val); 151 | end 152 | 153 | % validate elevation 154 | function set.elevation(obj,val) 155 | assert(isscalar(val), 'iosr:source:invalidElevation', 'ELEVATION must be a scalar') 156 | obj.elevation = val; 157 | obj.property_changed('elevation',val); 158 | end 159 | 160 | % validate numchans 161 | function set.numchans(obj,val) 162 | assert(isscalar(val), 'iosr:source:invalidNumchans', 'NUMCHANS must be a scalar') 163 | obj.numchans = val; 164 | obj.property_changed('numchans',val); 165 | end 166 | 167 | % validate precomposed 168 | function set.precomposed(obj,val) 169 | assert(islogical(val) && numel(val)==1, 'iosr:source:invalidPrecomposed', 'PRECOMPOSED property must be true or false') 170 | obj.precomposed = val; 171 | obj.property_changed('precomposed',val); 172 | end 173 | 174 | % dependent properties 175 | 176 | % get dependent property signal 177 | function signal = get.signal(obj) 178 | 179 | % read audio file 180 | [signal,fs2] = audioread(obj.filename); 181 | 182 | % do more stuff if differences not rendered 183 | if ~obj.rendered 184 | N = size(signal,2); 185 | 186 | if fs2~=obj.fs % resample 187 | signal = resample(signal,obj.fs,fs2); 188 | end 189 | if N~=obj.numchans % downmix 190 | signal = iosr.dsp.audio.up_down_mix(signal,obj.numchans); 191 | end 192 | end 193 | 194 | end 195 | 196 | end 197 | 198 | methods(Access = protected) 199 | 200 | function property_changed(obj,name,val) 201 | %PROPERTY_CHANGED handle property changes 202 | 203 | obj.rendered = false; 204 | 205 | switch lower(name) 206 | case 'fs' 207 | if isa(obj.parent,'mixture') && obj.parent.fs~=val 208 | obj.parent.fs = val; 209 | end 210 | end 211 | 212 | end 213 | 214 | function cpObj = copyElement(obj) 215 | %COPYELEMENT Overload copy method with additional functionality 216 | 217 | % Make a shallow copy of all properties 218 | cpObj = copyElement@iosr.dsp.audio(obj); 219 | 220 | end 221 | 222 | end 223 | 224 | end 225 | --------------------------------------------------------------------------------