Dawn@4: function i = sf_info(i) Dawn@4: % i=sf_info(i) - extract useful info from file Dawn@4: Dawn@4: % Alain de Cheveigné, CNRS/Ircam, 2002. Dawn@4: % Copyright (c) 2002 Centre National de la Recherche Scientifique. Dawn@4: % Dawn@4: % Permission to use, copy, modify, and distribute this software without Dawn@4: % fee is hereby granted FOR RESEARCH PURPOSES only, provided that this Dawn@4: % copyright notice appears in all copies and in all supporting Dawn@4: % documentation, and that the software is not redistributed for any Dawn@4: % fee (except for a nominal shipping charge). Dawn@4: % Dawn@4: % For any other uses of this software, in original or modified form, Dawn@4: % including but not limited to consulting, production or distribution Dawn@4: % in whole or in part, specific prior permission must be obtained from CNRS. Dawn@4: % Algorithms implemented by this software may be claimed by patents owned Dawn@4: % by CNRS, France Telecom, Ircam or others. Dawn@4: % Dawn@4: % The CNRS makes no representations about the suitability of this Dawn@4: % software for any purpose. It is provided "as is" without express Dawn@4: % or implied warranty. Beware of the bugs. Dawn@4: Dawn@4: if ~nargin ; error('sf_info: no input arguement'); end Dawn@4: if ~isa(i, 'struct') Dawn@4: j.fname=i; Dawn@4: i=j; Dawn@4: i=sf_info(i); Dawn@4: return Dawn@4: end Dawn@4: if ~isfield(i, 'fname'); error('sf_info: no fname field'); end Dawn@4: Dawn@4: % guess format only if unknown Dawn@4: if ~isfield(i, 'format') | isempty(i.format); Dawn@4: i = sf_format(i); Dawn@4: if ~strcmp(i.format, 'matrix') disp(i.format); end Dawn@4: end Dawn@4: Dawn@4: % handle workspace matrices as if they were files Dawn@4: if strcmp(i.format, 'matrix') Dawn@4: [nrows, ncols] = size(i.fname); Dawn@4: i.nchans=ncols; Dawn@4: i.nsamples=nrows; Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: i.sr=[]; Dawn@4: return; Dawn@4: end Dawn@4: Dawn@4: % close file if open Dawn@4: if exist('i.fd') & fopen(i.fd) Dawn@4: fclose(i.fd); Dawn@4: end Dawn@4: Dawn@4: % use standard matlab functions for AU and WAV and MACSND Dawn@4: if strcmp(i.format, 'AU') Dawn@4: if isempty(findstr('.', i.fname)) Dawn@4: disp(['WARNING: matlab function AUREAD requires '... Dawn@4: '.au or .snd suffix on file name: ' i.fname]); Dawn@4: end Dawn@4: sz = auread(i.fname, 'size'); Dawn@4: i.nsamples=sz(1); Dawn@4: i.nchans=sz(2); Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: [dummy, i.sr, i.samplebits] = auread(i.fname, 1); Dawn@4: return; Dawn@4: end Dawn@4: if strcmp(i.format, 'WAV') Dawn@4: if isempty(findstr('.wav', i.fname)) & isempty(findstr('.WAV', i.fname)) Dawn@4: disp(['WARNING: matlab function WAVREAD requires '... Dawn@4: '.wav suffix on file name: ' i.fname]); Dawn@4: end Dawn@4: sz = wavread(i.fname, 'size'); Dawn@4: i.nsamples=sz(1); Dawn@4: i.nchans=sz(2); Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: [dummy, i.sr, i.samplebits] = wavread(i.fname, 1); Dawn@4: return; Dawn@4: end Dawn@4: if strcmp(i.format, 'MACSND') Dawn@4: % must load the data to get info - this is stupid Dawn@4: if ~isempty(findstr(':', i.fname)) Dawn@4: disp(['matlab function READSND cannot handle an '... Dawn@4: 'indirect path: ' i.fname]); Dawn@4: end Dawn@4: if 3==exist('readsnd') Dawn@4: [data, i.sr] = eval('readsnd(i.fname)'); Dawn@4: else Dawn@4: error('cannot read MACSND on this platform'); Dawn@4: end Dawn@4: i.nsamples = size(data,2); Dawn@4: i.nchans = size(data,1); Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: return Dawn@4: end Dawn@4: Dawn@4: % reopen file Dawn@4: if strcmp(i.format, 'ascii') ... Dawn@4: | strcmp(i.format, 'csv') | strcmp(i.format, '|WAVE') Dawn@4: [i.fd, msg] = fopen(i.fname, 'rt'); Dawn@4: else Dawn@4: [i.fd, msg] = fopen(i.fname, 'r', 'ieee-be.l64'); Dawn@4: end Dawn@4: if i.fd == -1 Dawn@4: if isempty(msg) Dawn@4: error(['could not open: ', i.fname]); Dawn@4: else Dawn@4: error(msg) Dawn@4: end Dawn@4: end Dawn@4: fd = i.fd; Dawn@4: if ~isfield(i, 'nbytes') Dawn@4: if (-1 == fseek(i.fd, 0, 1)) ; error ('fseek failed'); end; Dawn@4: i.nbytes = ftell(i.fd); Dawn@4: end Dawn@4: fseek(fd, 0, -1); % rewind Dawn@4: Dawn@4: Dawn@4: switch i.format Dawn@4: %%%%%%%%%%%%%%%%%%% AIFF %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case {'AIFF','AIFC'} Dawn@4: fseek(fd, 12, 0); % skip container chunk Dawn@4: % skip over spurious chunks Dawn@4: idx=ftell(fd); Dawn@4: while 1 Dawn@4: magic=char(fread(fd,4,'uchar'))'; Dawn@4: if strcmp(magic,'COMM'); break; end; Dawn@4: idx = idx+1; Dawn@4: status = fseek(fd,idx,-1); Dawn@4: if status == -1 Dawn@4: error('expected COMM magic word, found eof'); Dawn@4: end; Dawn@4: end; Dawn@4: Dawn@4: %ckSize=fread(fd,1,'int32'); Dawn@4: %status = fseek(fd, ckSize, 0); % skip to end of chunk Dawn@4: %if status == -1 Dawn@4: % error('unexpected eof'); Dawn@4: %end Dawn@4: Dawn@4: %while (1) Dawn@4: % magic = char(fread(fd, 4, 'char'))'; Dawn@4: % if ~strcmp(magic, 'SSND') Dawn@4: % fseek(fd, -4, 0); % skip back Dawn@4: % break; Dawn@4: % end Dawn@4: % ckSize = fread(fd, 1, 'long'); Dawn@4: % fseek(fd, ckSize, 0); % skip to end of sound chunk Dawn@4: %end Dawn@4: %magic = char(fread (fd, 4, 'char'))'; Dawn@4: %if ~strcmp(magic, 'COMM'); error(['expected COMM, found ', magic]) ; end Dawn@4: commsz = fread(fd, 1, 'int32'); Dawn@4: i.nchans = fread(fd, 1, 'int16'); Dawn@4: i.nsamples = fread(fd, 1, 'uint32'); Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: i.samplebits = fread(fd, 1, 'int16'); Dawn@4: switch i.samplebits Dawn@4: case 16 Dawn@4: i.sample_bytes = 2; Dawn@4: i.sample_type = 'int16'; Dawn@4: case 32 Dawn@4: i.sample_bytes = 4; Dawn@4: i.sample_type = 'int32'; % or float? Dawn@4: otherwise Dawn@4: error(['unexpected samplebits: ' num2str(i.samplebits) ]); Dawn@4: end Dawn@4: % read sampling rate using Hideki Kawahara's code: Dawn@4: srex1=fread(fd,1,'uint16'); Dawn@4: srex2=fread(fd,1,'uint64'); Dawn@4: if strcmp(char(i.format),'AIFC') Dawn@4: compress=fread(fd,4,'uchar'); Dawn@4: if ~strcmp(char(compress),'NONE') Dawn@4: error('Compression is not supported.'); Dawn@4: end; Dawn@4: fseek(fd, commsz-22, 0); Dawn@4: end; Dawn@4: i.sr = 2^(srex1-16383)*srex2/hex2dec('8000000000000000'); Dawn@4: %fseek(fd, 12, -1); % skip back to end of container chunk Dawn@4: % skip over eventual common chunk Dawn@4: %while(1) Dawn@4: % magic = char(fread(fd, 4, 'char'))'; Dawn@4: % if ~strcmp(magic, 'COMM') Dawn@4: % fseek(fd, -4, 0); % skip back Dawn@4: % break; Dawn@4: % end Dawn@4: % ckSize = fread(fd, 1, 'long'); Dawn@4: % fseek(fd, ckSize, 0); % skip over chunk Dawn@4: %end Dawn@4: magic=char(fread(fd,4,'uchar'))'; Dawn@4: while ~strcmp(char(magic),'SSND') Dawn@4: [ckSize, count]=fread(fd,1,'int32'); Dawn@4: if ~count; Dawn@4: error('expected chunk size field, found eof'); Dawn@4: return Dawn@4: end Dawn@4: status = fseek(fd, ckSize, 0); % skip to end of chunk Dawn@4: if status == -1 Dawn@4: error('expected SSND magic word, found eof'); Dawn@4: return Dawn@4: end; Dawn@4: magic=char(fread(fd,4,'uchar'))'; Dawn@4: end; Dawn@4: Dawn@4: %magic = char(fread(fd, 4, 'char'))'; Dawn@4: %if ~strcmp(magic, 'SSND') Dawn@4: % error (['expected SSND, found' magic]); Dawn@4: %end Dawn@4: fseek(fd, 12, 0); % skip over ckSize, offset and blocksize fields Dawn@4: i.bytes_to_data = ftell(fd); Dawn@4: if i.totalsamples*i.sample_bytes ~= i.nbytes-i.bytes_to_data Dawn@4: disp(['WARNING: header fields sample_bytes: ' ... Dawn@4: num2str(i.sample_bytes)]); Dawn@4: disp (['and sample and channel count: ' num2str(i.nsamples) ... Dawn@4: ', ' num2str(i.nchans)]); Dawn@4: disp(['are inconsistent with offset to data: ' ... Dawn@4: num2str(i.bytes_to_data) ' and file size: ' ... Dawn@4: num2str(i.nbytes)]); Dawn@4: disp (['(' num2str(i.totalsamples*i.sample_bytes) ... Dawn@4: ' ~= ' num2str(i.nbytes-i.bytes_to_data) ')']); Dawn@4: end Dawn@4: Dawn@4: return Dawn@4: %%%%%%%%%%%%%%%%%%% NIST %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case 'NIST' Dawn@4: % fseek(fd, 0, -1); Dawn@4: % line = fscanf(fd, '%s' , 1); Dawn@4: % if ~strcmp(line, 'NIST_1A'); error(['expected NIST_1A, found ', magic]); end Dawn@4: fseek(fd, 8, 0); % skip over magic string Dawn@4: i.bytes_to_data = fscanf(fd, '%d', 1); Dawn@4: while (1) Dawn@4: key = fscanf(fd, '%s', 1); Dawn@4: if strcmp(key, 'end_head'); break; end; Dawn@4: % read third field according to spec in second field (type) Dawn@4: % this may need refining... Dawn@4: type = fscanf(fd, '%s', 1); Dawn@4: if strcmp(type(1:2), '-s') Dawn@4: bytes_to_read = sscanf(type(3:end), '%d', 1); Dawn@4: fseek(fd, 1, 0); % skip blank Dawn@4: value = char(fread(fd, bytes_to_read, 'char'))'; Dawn@4: else Dawn@4: value = fscanf(fd, '%f', 1); Dawn@4: end Dawn@4: i = setfield(i, key, value); Dawn@4: end Dawn@4: % give standard names to useful fields Dawn@4: if isfield(i, 'channel_count'); i.nchans = i.channel_count; end Dawn@4: if isfield(i, 'sample_count'); i.nsamples = i.sample_count; end Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: if isfield(i, 'sample_rate'); Dawn@4: i.sr = i.sample_rate; Dawn@4: i.xunits = 's'; Dawn@4: end Dawn@4: if ~isfield(i, 'sample_coding'); i.sample_coding = 'pcm'; end Dawn@4: i.bytes_to_data = 1024; % needs checking Dawn@4: i.sample_bytes=2; Dawn@4: i.sample_type='int16'; Dawn@4: return Dawn@4: %%%%%%%%%%%%%%%%%%% |WAVE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case '|WAV' Dawn@4: line = fscanf(fd, '%s' , 1); % skip first line Dawn@4: i.nsamples = fscanf(fd, '%d', 1); Dawn@4: i.nchans = fscanf(fd, '%d', 1); Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: i.bytes_to_data = ftell(fd); Dawn@4: % channel info handled during data read Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: return Dawn@4: %%%%%%%%%%%%%%%%%%% WFF %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case 'wff' Dawn@4: fseek(fd, 4, 0); % skip magic number Dawn@4: i.version = fread(fd, 1, 'long'); Dawn@4: i.type_code = fread(fd, 1, 'long'); Dawn@4: i.nchans = fread(fd, 1, 'long'); Dawn@4: i.info.channel_flags = fread(fd, 1, 'long'); Dawn@4: i.bytes_to_data = fread(fd, 1, 'long'); Dawn@4: fseek(fd, 40, 0); Dawn@4: i.gen_prog_name = fread(fd, 32, 'char'); Dawn@4: i.comment = fread(fd, 32, 'char'); Dawn@4: i.sample_bytes = 2; Dawn@4: i.sample_type = 'int16'; Dawn@4: return; Dawn@4: % channel info handled later during channel read Dawn@4: %%%%%%%%%%%%%%%%%%% ESPS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case 'ESPS' Dawn@4: % Based on Peter Kabal's afsp package. Dawn@4: % This handles at least one kind of ESPS waveform file. Others? Dawn@4: i.machine_code = fread(fd, 1, 'uint'); Dawn@4: i.version_check_code = fread(fd, 1, 'uint'); Dawn@4: i.bytes_to_data = fread(fd, 1, 'uint'); Dawn@4: i.record_size = fread(fd, 1, 'uint'); Dawn@4: fseek(fd, 20, -1); Dawn@4: i.EDR_ESPS_flag = fread(fd, 1, 'uint'); Dawn@4: i.align_pad_size = fread(fd, 1, 'uint'); Dawn@4: fseek(fd, 32, -1); Dawn@4: i.file_type = fread(fd, 1, 'uint16'); Dawn@4: fseek(fd, 40, -1); Dawn@4: i.file_creation_date_time = char(fread(fd, 26, 'char'))'; Dawn@4: i.header_version = char(fread(fd, 8, 'char'))'; Dawn@4: i.program_name = char(fread(fd, 16, 'char'))'; Dawn@4: i.program_version = char(fread(fd, 8, 'char'))'; Dawn@4: i.compile_date = char(fread(fd, 26, 'char'))'; Dawn@4: i.tag = fread(fd, 1, 'uint'); Dawn@4: fseek(fd, 132, -1); Dawn@4: i.ndoubles = fread(fd, 1, 'uint'); Dawn@4: i.nfloats = fread(fd, 1, 'uint'); Dawn@4: i.nlongs = fread(fd, 1, 'uint'); Dawn@4: i.nshorts = fread(fd, 1, 'uint'); Dawn@4: i.nchars = fread(fd, 1, 'uint'); Dawn@4: i.fixed_header_size = fread(fd, 1, 'uint'); Dawn@4: i.var_header_size = fread(fd, 1, 'uint'); Dawn@4: %w.dunno_what = fread(fd, 1, 'uint'); Dawn@4: fseek(fd, 160, -1); Dawn@4: i.user = char(fread(fd, 8, 'char'))'; Dawn@4: % scan the rest of the header to find sampling rate Dawn@4: a = ftell(fd); Dawn@4: bytes_left_in_header = i.bytes_to_data - ftell(fd); Dawn@4: hunk = char(fread(fd, bytes_left_in_header, 'uchar'))'; Dawn@4: b = findstr(hunk, 'record_freq'); Dawn@4: %fseek(fd, a+b-1, -1); Dawn@4: %w.xxxx = char(fread(fd, 12, 'char'))'; Dawn@4: %w.count = fread(fd, 1, 'uint'); Dawn@4: %w.data_code = fread(fd, 1, 'ushort'); Dawn@4: fseek(fd, a+b-1+12+2+4, -1); Dawn@4: i.sr = fread(fd, 1, 'double'); Dawn@4: i.nchans = i.ndoubles+i.nfloats+i.nlongs ... Dawn@4: +i.nshorts+i.nchars; Dawn@4: recordsize = i.ndoubles*8 + i.nfloats*4 + i.nlongs*4 ... Dawn@4: +i.nshorts*2+i.nchars; Dawn@4: i.nsamples = (i.nbytes - i.bytes_to_data) / recordsize; Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: i.sample_bytes = 2; % bug Dawn@4: i.sample_type = 'int16'; % bug Dawn@4: return; Dawn@4: %%%%%%%%%%%%% PLAIN ASCII %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case 'ascii' Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: line = fgetl(fd); Dawn@4: i.nchans = size(sscanf(line, '%f'), 1); Dawn@4: nlines = 1; Dawn@4: while (1) Dawn@4: line = fgets(i.fd); Dawn@4: if isa(line, 'double') & -1 == line; break; end Dawn@4: nlines = nlines+1; Dawn@4: end Dawn@4: i.nsamples = nlines; Dawn@4: i.totalsamples = i.nsamples*i.nchans; Dawn@4: i.sr=1; Dawn@4: return Dawn@4: %%%%%%%%%%%%% COMMA-SEPARATED VALUES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case 'csv'; Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: % todo Dawn@4: return Dawn@4: %%%%%%%%%%%%% Binary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Dawn@4: case {'uchar'}; % should take care of signed/unsigned Dawn@4: i.sample_bytes = 1; Dawn@4: i.sample_type = 'uchar'; Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: if ~isfield(i, 'nchans') Dawn@4: i.nchans = 1; Dawn@4: end Dawn@4: i.nsamples = (i.nbytes-i.bytes_to_data)/i.sample_bytes; Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: i.sr=1; Dawn@4: return Dawn@4: case {'short', 'int16'}; Dawn@4: i.sample_bytes = 2; Dawn@4: i.sample_type = 'int16'; Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: if ~isfield(i, 'nchans') Dawn@4: i.nchans = 1; Dawn@4: end Dawn@4: i.nsamples = (i.nbytes-i.bytes_to_data)/i.sample_bytes; Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: i.sr=1; Dawn@4: return Dawn@4: case {'long', 'int32'}; Dawn@4: i.sample_bytes = 4; Dawn@4: i.sample_type = 'int32'; Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: if ~isfield(i, 'nchans') Dawn@4: i.nchans = 1; Dawn@4: end Dawn@4: i.nsamples = (i.nbytes-i.bytes_to_data)/i.sample_bytes; Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: i.sr=1; Dawn@4: return Dawn@4: case {'float', 'float32'}; Dawn@4: i.sample_bytes = 4; Dawn@4: i.sample_type = 'float32'; Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: if ~isfield(i, 'nchans') Dawn@4: i.nchans = 1; Dawn@4: end Dawn@4: i.nsamples = (i.nbytes-i.bytes_to_data)/i.sample_bytes; Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: i.sr=1; Dawn@4: return Dawn@4: case {'double', 'float64'}; Dawn@4: i.sample_bytes = 8; Dawn@4: i.sample_type = 'float64'; Dawn@4: if isfield(i, 'bytes_to_data') Dawn@4: fseek(fd, i.bytes_to_data, 0); Dawn@4: else Dawn@4: i.bytes_to_data = 0; Dawn@4: end Dawn@4: if ~isfield(i, 'nchans') Dawn@4: i.nchans = 1; Dawn@4: end Dawn@4: i.nsamples = (i.nbytes-i.bytes_to_data)/i.sample_bytes; Dawn@4: i.totalsamples = i.nsamples * i.nchans; Dawn@4: i.sr=1; Dawn@4: return Dawn@4: otherwise Dawn@4: error(['unknown format: >' i.format '<']); Dawn@4: end