Daniel@0: function varargout = miraudio(orig,varargin) Daniel@0: % a = miraudio('filename') loads the sound file 'filename' (in WAV or AU Daniel@0: % format) into a miraudio object. Daniel@0: % a = miraudio('Folder') loads all the sound files in the CURRENT folder Daniel@0: % into a miraudio object. Daniel@0: % a = miraudio(v,sr), where v is a column vector, translates the vector v Daniel@0: % into a miraudio object. The sampling frequency is set to sr Hertz. Daniel@0: % Default value for sr: 44100 Hz. Daniel@0: % a = miraudio(b, ...), where b is already a miraudio object, performs Daniel@0: % operations on b specified by the optional arguments (see below). Daniel@0: % Daniel@0: % Transformation options: Daniel@0: % miraudio(...,'Mono',0) does not perform the default summing of Daniel@0: % channels into one single mono track, but instead stores each Daniel@0: % channel of the initial soundfile separately. Daniel@0: % miraudio(...,'Center') centers the signals. Daniel@0: % miraudio(...,'Sampling',r) resamples at sampling rate r (in Hz). Daniel@0: % (Requires the Signal Processing Toolbox.) Daniel@0: % miraudio(...,'Normal') normalizes with respect to RMS energy. Daniel@0: % Extraction options: Daniel@0: % miraudio(...,'Extract',t1,t2,u,f) extracts the signal between dates Daniel@0: % t1 and t2, expressed in the unit u. Daniel@0: % Possible values for u: Daniel@0: % 's' (seconds, by default), Daniel@0: % 'sp' (sample index, starting from 1). Daniel@0: % The additional optional argument f indicates the referential Daniel@0: % origin of the temporal positions. Possible values for f: Daniel@0: % 'Start' (by default) Daniel@0: % 'Middle' (of the sequence) Daniel@0: % 'End' of the sequence Daniel@0: % When using 'Middle' or 'End', negative values for t1 or t2 Daniel@0: % indicate values before the middle or the end of the audio Daniel@0: % sequence. Daniel@0: % miraudio(...,'Trim') trims the pseudo-silence beginning and end off Daniel@0: % the audio file. Silent frames are frames with RMS below t times Daniel@0: % the medium RMS of the whole audio file. Daniel@0: % Default value: t = 0.06 Daniel@0: % instead of 'Trim': Daniel@0: % 'TrimStart' only trims the beginning of the audio file, Daniel@0: % 'TrimEnd' only trims the end. Daniel@0: % miraudio(...,'TrimThreshold',t) specifies the trimming threshold t. Daniel@0: % miraudio(...,'Channel',c) or miraudio(...,'Channels',c) selects the Daniel@0: % channels indicated by the (array of) integer(s) c. Daniel@0: % Labeling option: Daniel@0: % miraudio(...,'Label',l) labels the audio signal(s) following the Daniel@0: % label(s) l. Daniel@0: % If l is a (series of) number(s), the audio signal(s) are Daniel@0: % labelled using the substring of their respective file name of Daniel@0: % index l. If l=0, the audio signal(s) are labelled using the Daniel@0: % whole file name. Daniel@0: Daniel@0: Daniel@0: if isnumeric(orig) Daniel@0: if size(orig,2) > 1 || size(orig,3) > 1 Daniel@0: mirerror('MIRAUDIO','Only column vectors can be imported into mirtoolbox.'); Daniel@0: end Daniel@0: if nargin == 1 Daniel@0: f = 44100; Daniel@0: else Daniel@0: f = varargin{1}; Daniel@0: end Daniel@0: b = 32; Daniel@0: if size(orig,1) == 1 Daniel@0: orig = orig'; Daniel@0: end Daniel@0: tp = (0:size(orig,1)-1)'/f; Daniel@0: t = mirtemporal([],'Time',{{tp}},'Data',{{orig}},... Daniel@0: 'FramePos',{{tp([1 end])}},'Sampling',{f},... Daniel@0: 'Name',{inputname(1)},'Label',{{}},'Clusters',{{}},... Daniel@0: 'Channels',[],'Centered',0,'NBits',{b},... Daniel@0: 'Title','Audio signal',... Daniel@0: 'PeakPos',{{{}}},'PeakVal',{{{}}},'PeakMode',{{{}}}); Daniel@0: aa.fresh = 1; Daniel@0: varargout = {class(aa,'miraudio',t)}; Daniel@0: return Daniel@0: end Daniel@0: Daniel@0: Daniel@0: center.key = 'Center'; Daniel@0: center.type = 'Boolean'; Daniel@0: center.default = 0; Daniel@0: center.when = 'After'; Daniel@0: option.center = center; Daniel@0: Daniel@0: normal.key = 'Normal'; Daniel@0: normal.type = 'Boolean'; Daniel@0: normal.default = 0; Daniel@0: normal.when = 'After'; Daniel@0: option.normal = normal; Daniel@0: Daniel@0: extract.key = {'Extract','Excerpt'}; Daniel@0: extract.type = 'Integer'; Daniel@0: extract.number = 2; Daniel@0: extract.default = []; Daniel@0: extract.unit = {'s','sp'}; Daniel@0: extract.defaultunit = 's'; Daniel@0: extract.from = {'Start','Middle','End'}; Daniel@0: extract.defaultfrom = 'Start'; Daniel@0: option.extract = extract; Daniel@0: Daniel@0: trim.type = 'String'; Daniel@0: trim.choice = {'NoTrim','Trim','TrimBegin','TrimStart','TrimEnd'}; Daniel@0: trim.default = 'NoTrim'; Daniel@0: trim.when = 'After'; Daniel@0: option.trim = trim; Daniel@0: Daniel@0: trimthreshold.key = 'TrimThreshold'; Daniel@0: trimthreshold.type = 'Integer'; Daniel@0: trimthreshold.default = .06; Daniel@0: trimthreshold.when = 'After'; Daniel@0: option.trimthreshold = trimthreshold; Daniel@0: Daniel@0: label.key = 'Label'; Daniel@0: label.default = ''; Daniel@0: label.when = 'After'; Daniel@0: option.label = label; Daniel@0: Daniel@0: sampling.key = 'Sampling'; Daniel@0: sampling.type = 'Integer'; Daniel@0: sampling.default = 0; Daniel@0: sampling.when = 'Both'; Daniel@0: option.sampling = sampling; Daniel@0: Daniel@0: % segment.key = 'Segment'; Daniel@0: % segment.type = 'Integer'; Daniel@0: % segment.default = []; Daniel@0: % segment.when = 'After'; Daniel@0: % option.segment = segment; Daniel@0: Daniel@0: reverse.key = 'Reverse'; Daniel@0: reverse.type = 'Boolean'; Daniel@0: reverse.default = 0; Daniel@0: reverse.when = 'After'; Daniel@0: option.reverse = reverse; Daniel@0: Daniel@0: mono.key = 'Mono'; Daniel@0: mono.type = 'Boolean'; Daniel@0: mono.default = NaN; Daniel@0: mono.when = 'After'; Daniel@0: option.mono = mono; Daniel@0: Daniel@0: separate.key = 'SeparateChannels'; Daniel@0: separate.type = 'Boolean'; Daniel@0: separate.default = 0; Daniel@0: option.separate = separate; Daniel@0: Daniel@0: Ch.key = {'Channel','Channels'}; Daniel@0: Ch.type = 'Integer'; Daniel@0: Ch.default = []; Daniel@0: Ch.when = 'After'; Daniel@0: option.Ch = Ch; Daniel@0: Daniel@0: specif.option = option; Daniel@0: Daniel@0: specif.beforechunk = {@beforechunk,'normal'}; Daniel@0: specif.eachchunk = @eachchunk; Daniel@0: specif.combinechunk = @combinechunk; Daniel@0: Daniel@0: if nargin > 1 && ischar(varargin{1}) && strcmp(varargin{1},'Now') Daniel@0: if nargin > 2 Daniel@0: extract = varargin{2}; Daniel@0: else Daniel@0: extract = []; Daniel@0: end Daniel@0: para = []; Daniel@0: varargout = {main(orig,[],para,[],extract)}; Daniel@0: else Daniel@0: varargout = mirfunction(@miraudio,orig,varargin,nargout,specif,@init,@main); Daniel@0: end Daniel@0: if isempty(varargout) Daniel@0: varargout = {{}}; Daniel@0: end Daniel@0: Daniel@0: Daniel@0: function [x type] = init(x,option) Daniel@0: if isa(x,'mirdesign') Daniel@0: if option.sampling Daniel@0: x = setresampling(x,option.sampling); Daniel@0: end Daniel@0: end Daniel@0: type = 'miraudio'; Daniel@0: Daniel@0: Daniel@0: function a = main(orig,option,after,index,extract) Daniel@0: if iscell(orig) Daniel@0: orig = orig{1}; Daniel@0: end Daniel@0: if ischar(orig) Daniel@0: if nargin < 5 Daniel@0: extract = []; Daniel@0: end Daniel@0: [d{1},tp{1},fp{1},f{1},b{1},n{1},ch{1}] = mirread(extract,orig,1,0); Daniel@0: t = mirtemporal([],'Time',tp,'Data',d,'FramePos',fp,'Sampling',f,... Daniel@0: 'Name',n,'Label',cell(1,length(d)),... Daniel@0: 'Clusters',cell(1,length(d)),... Daniel@0: 'Channels',ch,'Centered',0,'NBits',b); Daniel@0: t = set(t,'Title','Audio waveform'); Daniel@0: a.fresh = 1; Daniel@0: a = class(a,'miraudio',t); Daniel@0: else Daniel@0: if not(isempty(option)) && not(isempty(option.extract)) Daniel@0: if not(isstruct(after)) Daniel@0: after = struct; Daniel@0: end Daniel@0: after.extract = option.extract; Daniel@0: end Daniel@0: if isa(orig,'miraudio') Daniel@0: a = orig; Daniel@0: else Daniel@0: a.fresh = 1; Daniel@0: a = class(a,'miraudio',orig); Daniel@0: end Daniel@0: end Daniel@0: if not(isempty(after)) Daniel@0: a = post(a,after); Daniel@0: end Daniel@0: Daniel@0: Daniel@0: function a = post(a,para) Daniel@0: if a.fresh && isfield(para,'mono') Daniel@0: a.fresh = 0; Daniel@0: if isnan(para.mono) Daniel@0: para.mono = 1; Daniel@0: end Daniel@0: end Daniel@0: if isfield(para,'mono') && para.mono == 1 Daniel@0: a = mirsum(a,'Mean'); Daniel@0: end Daniel@0: d = get(a,'Data'); Daniel@0: t = get(a,'Time'); Daniel@0: ac = get(a,'AcrossChunks'); Daniel@0: f = get(a,'Sampling'); Daniel@0: cl = get(a,'Clusters'); Daniel@0: for h = 1:length(d) Daniel@0: for k = 1:length(d{h}) Daniel@0: tk = t{h}{k}; Daniel@0: dk = d{h}{k}; Daniel@0: if isfield(para,'extract') && not(isempty(para.extract)) Daniel@0: t1 = para.extract(1); Daniel@0: t2 = para.extract(2); Daniel@0: if para.extract(4) Daniel@0: if para.extract(4) == 1 Daniel@0: shift = round(size(tk,1)/2); Daniel@0: elseif para.extract(4) == 2 Daniel@0: shift = size(tk,1); Daniel@0: end Daniel@0: if para.extract(3) Daniel@0: shift = tk(shift,1,1); Daniel@0: end Daniel@0: t1 = t1+shift; Daniel@0: t2 = t2+shift; Daniel@0: end Daniel@0: if para.extract(3) % in seconds Daniel@0: ft = find(tk>=t1 & tk<=t2); Daniel@0: else % in samples Daniel@0: if not(t1) Daniel@0: warning('WARNING IN MIRAUDIO: Extract sample positions should be real positive integers.') Daniel@0: display('Positions incremented by one.'); Daniel@0: t1 = t1+1; Daniel@0: t2 = t2+1; Daniel@0: end Daniel@0: ft = t1:t2; Daniel@0: end Daniel@0: tk = tk(ft,:,:); Daniel@0: dk = dk(ft,:,:); Daniel@0: end Daniel@0: if isfield(para,'Ch') && not(isempty(para.Ch)) Daniel@0: dk = dk(:,:,para.Ch); Daniel@0: end Daniel@0: if isfield(para,'center') && para.center Daniel@0: dk = center(dk); Daniel@0: a = set(a,'Centered',1); Daniel@0: end Daniel@0: if isfield(para,'normal') && para.normal Daniel@0: nl = size(dk,1); Daniel@0: nc = size(dk,3); Daniel@0: if isempty(ac) Daniel@0: ee = 0; Daniel@0: for j = 1:nc Daniel@0: ee = ee+sum(dk(:,:,j).^2); Daniel@0: end Daniel@0: ee = sqrt(ee/nl/nc); Daniel@0: else Daniel@0: ee = sqrt(sum(ac.sqrsum.^2)/ac.samples); Daniel@0: end Daniel@0: dk = dk./repmat(ee,[nl,1,nc]); Daniel@0: end Daniel@0: if isfield(para,'trim') && not(isequal(para.trim,0)) ... Daniel@0: && not(strcmpi(para.trim,'NoTrim')) Daniel@0: if not(para.trimthreshold) Daniel@0: para.trimthreshold = 0.06; Daniel@0: end Daniel@0: trimframe = 100; Daniel@0: trimhop = 10; Daniel@0: nframes = floor((length(tk)-trimframe)/trimhop)+1; Daniel@0: rms = zeros(1,nframes); Daniel@0: for j = 1:nframes Daniel@0: st = floor((j-1)*trimhop)+1; Daniel@0: for z = 1:size(dk,3) Daniel@0: rms(1,j,z) = norm(dk(st:st+trimframe-1,1,z))/sqrt(trimframe); Daniel@0: end Daniel@0: end Daniel@0: rms = (rms-repmat(min(rms),[1,size(rms,2),1]))... Daniel@0: ./repmat(max(rms)-min(rms),[1,size(rms,2),1]); Daniel@0: nosil = find(rms>para.trimthreshold); Daniel@0: if strcmpi(para.trim,'Trim') || strcmpi(para.trim,'TrimStart') ... Daniel@0: || strcmpi(para.trim,'TrimBegin') Daniel@0: nosil1 = min(nosil); Daniel@0: if nosil1 > 1 Daniel@0: nosil1 = nosil1-1; Daniel@0: end Daniel@0: n1 = floor((nosil1-1)*trimhop)+1; Daniel@0: else Daniel@0: n1 = 1; Daniel@0: end Daniel@0: if strcmpi(para.trim,'Trim') || strcmpi(para.trim,'TrimEnd') Daniel@0: nosil2 = max(nosil); Daniel@0: if nosil2 < length(rms) Daniel@0: nosil2 = nosil2+1; Daniel@0: end Daniel@0: n2 = floor((nosil2-1)*trimhop)+1; Daniel@0: else Daniel@0: n2 = length(tk); Daniel@0: end Daniel@0: wh = ones(n2-n1+1,1); Daniel@0: dt = round(.02*f{h}); Daniel@0: ha = hann(dt*2); Daniel@0: wh(1:dt) = ha(1:dt); Daniel@0: wh(end-dt+1:end) = ha(dt+1:end); Daniel@0: tk = tk(n1:n2); Daniel@0: dk = dk(n1:n2,1,:);%.*repmat(wh,[1 1 size(dk,3)]); Daniel@0: end Daniel@0: if isfield(para,'sampling') && para.sampling Daniel@0: if and(f{k}, not(f{k} == para.sampling)) Daniel@0: for j = 1:size(dk,3) Daniel@0: rk(:,:,j) = resample(dk(:,:,j),para.sampling,f{k}); Daniel@0: end Daniel@0: dk = rk; Daniel@0: tk = repmat((0:size(dk,1)-1)',[1 1 size(tk,3)])... Daniel@0: /para.sampling + tk(1,:,:); Daniel@0: end Daniel@0: f{k} = para.sampling; Daniel@0: end Daniel@0: d{h}{k} = dk; Daniel@0: t{h}{k} = tk; Daniel@0: %if isfield(para,'reverse') && para.reverse Daniel@0: % d{h}{k} = flipdim(d{h}{k},1); Daniel@0: %end Daniel@0: end Daniel@0: end Daniel@0: a = set(a,'Data',d,'Time',t,'Sampling',f,'Clusters',cl); Daniel@0: if isfield(para,'label') Daniel@0: if isnumeric(para.label) Daniel@0: n = get(a,'Name'); Daniel@0: l = cell(1,length(d)); Daniel@0: for k = 1:length(d) Daniel@0: if para.label Daniel@0: l{k} = n{k}(para.label); Daniel@0: else Daniel@0: l{k} = n{k}; Daniel@0: end Daniel@0: end Daniel@0: a = set(a,'Label',l); Daniel@0: elseif iscell(para.label) Daniel@0: idx = mod(get(a,'Index'),length(para.label)); Daniel@0: if not(idx) Daniel@0: idx = length(para.label); Daniel@0: end Daniel@0: a = set(a,'Label',para.label{idx}); Daniel@0: elseif ischar(para.label) Daniel@0: l = cell(1,length(d)); Daniel@0: for k = 1:length(d) Daniel@0: l{k} = para.label; Daniel@0: end Daniel@0: a = set(a,'Label',l); Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: Daniel@0: function [new orig] = beforechunk(orig,option,missing) Daniel@0: option.normal = 0; Daniel@0: a = miraudio(orig,option); Daniel@0: d = get(a,'Data'); Daniel@0: old = get(orig,'AcrossChunks'); Daniel@0: if isempty(old) Daniel@0: old.sqrsum = 0; Daniel@0: old.samples = 0; Daniel@0: end Daniel@0: new = mircompute(@crossum,d); Daniel@0: new = new{1}{1}; Daniel@0: new.sqrsum = old.sqrsum + new.sqrsum; Daniel@0: new.samples = old.samples + new.samples; Daniel@0: Daniel@0: Daniel@0: function s = crossum(d) Daniel@0: s.sqrsum = sum(d.^2); Daniel@0: s.samples = length(d); Daniel@0: Daniel@0: Daniel@0: function [y orig] = eachchunk(orig,option,missing) Daniel@0: y = miraudio(orig,option); Daniel@0: Daniel@0: Daniel@0: function y = combinechunk(old,new) Daniel@0: do = get(old,'Data'); Daniel@0: to = get(old,'Time'); Daniel@0: dn = get(new,'Data'); Daniel@0: tn = get(new,'Time'); Daniel@0: y = set(old,'Data',{{[do{1}{1};dn{1}{1}]}},... Daniel@0: 'Time',{{[to{1}{1};tn{1}{1}]}});