Mercurial > hg > camir-aes2014
view toolboxes/MIRtoolbox1.3.2/MIRToolbox/aiffread.m @ 0:e9a9cd732c1e tip
first hg version after svn
author | wolffd |
---|---|
date | Tue, 10 Feb 2015 15:05:51 +0000 |
parents | |
children |
line wrap: on
line source
function [data,Fs,nBits,formChunk] = aiffread(filePath,indexRange) %AIFFREAD Read AIFF (Audio Interchange File Format) sound file. % Y = AIFFREAD(FILE) reads an AIFF file specified by the string FILE, % returning the sampled data in Y. The ".aif" extension is appended if no % extension is given. % % [Y,FS,NBITS,CHUNKDATA] = AIFFREAD(FILE) returns the sample rate (FS) in % Hertz, the number of bits per sample (NBITS) used to encode the data in % the file, and a complete structure of the chunk data (CHUNKDATA) % contained in the AIFF file (minus the actual audio data returned in Y). % See below for a description of CHUNKDATA. % % [...] = AIFFREAD(FILE,N) returns only the first N samples from each % channel in the file. % % [...] = AIFFREAD(FILE,[N1 N2]) returns only samples N1 through N2 from % each channel in the file. % % [SIZ,...] = AIFFREAD(FILE,'size') returns the size of the audio data % contained in the file in place of the actual audio data, where % SIZ = [nSampleFrames nChannels]. % %-NOTES-------------------------------------------------------------------- % % A note on compressed files: % % Both AIFF and AIFC/AIFF-C (compressed) file types can be read by % AIFFREAD, but the data returned for AIFC/AIFF-C files will be the % raw, compressed data (i.e. AIFFREAD loads the data from the file % without modification). Currently, since there are many compression % formats, it is the responsibility of the user to uncompress the % sound data using parameters defined in the COMM chunk (contained in % the returned CHUNKDATA structure). When loading AIFC/AIFF-C files, % any optional numerical subranges are ignored and the entire set of % compressed data is returned as a column vector of signed bytes % (INT8 type). % % A note on the CHUNKDATA structure: % % The CHUNKDATA structure has the following fields: % % -'chunkID': The 4-character ID for the chunk. This will always be % the string 'FORM'. % -'chunkSize': The size (in bytes) of the remaining data in the % file. % -'formType': A 4-character string for the file type that should % be either 'AIFF' or 'AIFC'. % -'chunkArray': An array of structures, one entry for every chunk % that is in the file (not counting this parent FORM % chunk). The fields of this structure are: % -'chunkID': The 4-character ID for the chunk. % -'chunkSize': The size (in bytes) of the % remaining data in the chunk. % -'chunkData': A structure of data for the % chunk. The form of this data for % a given chunkID can be found at % the links given below for the % file format standards. % % The data portion of certain chunks may not have a clearly defined % format, or that format may be dependent on the implementation or % application that will be using the data. In such cases, the data % returned for that chunk in the CHUNKDATA structure will be in a raw % format (vectors of signed or unsigned 8-bit integers) and it will be % up to the user/application to parse and format this data correctly. % The following is a list of such AIFF/AIFF-C chunks: % % -Audio Recording Chunk (chunkID = 'AESD'): The chunkData % structure has one field 'aesChannelStatusData' that stores a % column vector of 24 8-bit unsigned integers. % -Application Specific Chunk (chunkID = 'APPL'): The chunkData % structure has two fields. 'applicationSignature' stores a % 4-character string identifying the application. 'data' stores a % column vector of 8-bit signed integers. % -MIDI Data Chunk (chunkID = 'MIDI'): The chunkData structure has % one field 'midiData' that stores a column vector of 8-bit % unsigned integers. % -Sound Accelerator (SAXEL) Chunk (chunkID = 'SAXL'): There is no % finalized format for Saxel chunks, so the chunkData structure % follows the draft format given in Appendix D of the AIFF-C % standard. % % Description for the AIFF standard can be found here: % % http://muratnkonar.com/aiff/index.html % % Descriptions for the AIFC/AIFF-C standard can be found here: % % http://www.cnpbagwell.com/aiff-c.txt % http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/... % AIFF-C.9.26.91.pdf % Author: Ken Eaton % Last modified: 3/17/09 %-------------------------------------------------------------------------- % Initializations: aiffChunkPrecedence = {'COMM' 'SSND' 'MARK' 'INST' 'COMT' 'NAME' ... 'AUTH' '[c] ' 'ANNO' 'AESD' 'MIDI' 'APPL'}; aiffChunkLimits = [1 1 1 1 1 1 1 1 inf 1 inf inf]; aifcChunkPrecedence = {'FVER' 'COMM' 'INST' 'SAXL' 'COMT' 'MARK' ... 'SSND' 'NAME' 'AUTH' '[c] ' 'ANNO' 'AESD' ... 'MIDI' 'APPL'}; aifcChunkLimits = [1 1 1 inf 1 1 1 1 1 1 inf 1 inf inf]; fid = -1; % Check the number of input arguments: switch nargin, case 0, error(error_message('notEnoughInputs')); case 1, indexRange = [1 inf]; end % Check the file name input argument: if ~ischar(filePath), error(error_message('badArgumentType','File name','char')); end [filePath,fileName,fileExtension] = fileparts(filePath); if isempty(fileExtension), fileExtension = '.aif'; end if ~any(strcmpi(fileExtension,{'.aif' '.afc' '.aiff' '.aifc'})), error(error_message('unknownExtension',fileExtension)); end % Check the optional input argument: if isnumeric(indexRange), % Numeric range specification indexRange = double(indexRange); nRange = numel(indexRange); if (nRange > 2) || any(indexRange < 1) || any(isnan(indexRange)), error(error_message('badIndexValue')); end indexRange = [ones(nRange ~= 2) round(indexRange) inf(nRange == 0)]; elseif ischar(indexRange), % Specification for returning just the size if ~strncmpi(indexRange,'size',numel(indexRange)), error(error_message('invalidString')); end indexRange = []; else % Invalid input error(error_message('badArgumentType','Optional argument',... 'numeric or char')); end % Check that the file exists and can be opened: fid = fopen(fullfile(filePath,[fileName fileExtension]),'r','b'); if fid == -1, error(error_message('invalidFile',[fileName fileExtension])); end % Initialize formChunk structure: formChunk = struct('chunkID',[],'chunkSize',[],'formType',[],... 'chunkArray',[]); % Read FORM chunk data: formChunk.chunkID = read_text(fid,4); if ~strcmp(formChunk.chunkID,'FORM'), error(error_message('invalidFileFormat',fileExtension)); end formChunk.chunkSize = fread(fid,1,'int32'); formType = read_text(fid,4); if ~any(strcmp(formType,{'AIFF' 'AIFC'})), error(error_message('invalidFileFormat',fileExtension)); end formChunk.formType = formType; % Since the order of chunks is not guaranteed, first skip through the % file and read just the chunkIDs and chunkSizes: iChunk = 0; chunkIDArray = {}; chunkSizeArray = {}; chunkDataIndex = []; nextChunkID = read_text(fid,4); while ~feof(fid), iChunk = iChunk+1; chunkIDArray{iChunk} = nextChunkID; chunkSize = fread(fid,1,'int32'); chunkSizeArray{iChunk} = chunkSize; chunkDataIndex(iChunk) = ftell(fid); fseek(fid,chunkSize+rem(chunkSize,2),'cof'); nextChunkID = read_text(fid,4); end % Check for the presence of required chunks: if ~ismember('COMM',chunkIDArray), error(error_message('missingChunk','COMM',formType)); end if strcmp(formType,'AIFC') && ~ismember('FVER',chunkIDArray), error(error_message('missingChunk','FVER',formType)); end % Check for unknown chunks and order chunks based on chunk precedence: if strcmp(formType,'AIFF'), [isChunk,orderIndex] = ismember(chunkIDArray,aiffChunkPrecedence); else [isChunk,orderIndex] = ismember(chunkIDArray,aifcChunkPrecedence); end if ~all(isChunk), unknownChunks = [chunkIDArray(~isChunk); ... repmat({', '},1,sum(~isChunk)-1) {'.'}]; orderIndex = orderIndex(isChunk); chunkIDArray = chunkIDArray(isChunk); chunkSizeArray = chunkSizeArray(isChunk); chunkDataIndex = chunkDataIndex(isChunk); warning('aiffread:unknownChunk',... ['The following chunk IDs are unknown for an ' formType ... ' file and will be ignored: ' unknownChunks{:}]); end [index,orderIndex] = sort(orderIndex); chunkIDArray = chunkIDArray(orderIndex); chunkSizeArray = chunkSizeArray(orderIndex); chunkDataIndex = chunkDataIndex(orderIndex); % Check for chunks that should not appear more than once: index = unique(index(diff(index) < 1)); if strcmp(formType,'AIFF'), repeatChunks = aiffChunkPrecedence(aiffChunkLimits(index) == 1); else repeatChunks = aifcChunkPrecedence(aifcChunkLimits(index) == 1); end if ~isempty(repeatChunks), repeatChunks = [repeatChunks; ... repmat({', '},1,numel(repeatChunks)-1) {'.'}]; error(error_message('repeatChunk',formType,[repeatChunks{:}])); end % Initialize chunkArray data: formChunk.chunkArray = struct('chunkID',chunkIDArray,... 'chunkSize',chunkSizeArray,... 'chunkData',[]); % Read the data for each chunk: for iChunk = 1:numel(chunkIDArray), chunkData = []; fseek(fid,chunkDataIndex(iChunk),'bof'); switch chunkIDArray{iChunk}, case '[c] ', % Copyright Chunk chunkData.text = read_text(fid,chunkSizeArray{iChunk}); case 'AESD', % Audio Recording Chunk chunkData.aesChannelStatusData = ... fread(fid,[chunkSizeArray{iChunk} 1],'*uint8'); case 'ANNO', % Annotation Chunk chunkData.text = read_text(fid,chunkSizeArray{iChunk}); case 'APPL', % Application Specific Chunk chunkData.applicationSignature = read_text(fid,4); chunkData.data = fread(fid,[chunkSizeArray{iChunk}-4 1],'*int8'); case 'AUTH', % Author Chunk chunkData.text = read_text(fid,chunkSizeArray{iChunk}); case 'COMM', % Common Chunk nChannels = fread(fid,1,'int16'); chunkData.nChannels = nChannels; nSampleFrames = fread(fid,1,'uint32'); if (nSampleFrames > 0) && ~ismember('SSND',chunkIDArray), error(error_message('missingChunk','SSND',formType)); end chunkData.nSampleFrames = nSampleFrames; nBits = fread(fid,1,'int16'); chunkData.sampleSize = nBits; exponent = fread(fid,1,'uint16'); highMantissa = fread(fid,1,'uint32'); lowMantissa = fread(fid,1,'uint32'); Fs = extended2double(exponent,highMantissa,lowMantissa); chunkData.sampleRate = Fs; if strcmp(formType,'AIFF'), compressionType = 'NONE'; else compressionType = read_text(fid,4); chunkData.compressionType = compressionType; chunkData.compressionName = read_pstring(fid); end case 'COMT', % Comments Chunk nComments = fread(fid,1,'uint16'); chunkData.nComments = nComments; chunkData.commentArray = struct('timeStamp',cell(nComments,1),... 'marker',[],'count',[],'text',[]); for iComment = 1:nComments, chunkData.commentArray(iComment) = read_comment(fid); end case 'FVER', % Format Version Chunk (AIFC/AIFF-C only) timeStamp = fread(fid,1,'uint32'); if timeStamp ~= 2726318400, warning('aiffread:unknownVersion',... ['File contains an unrecognized version of the ' ... 'AIFC/AIFF-C standard.']); end chunkData.timeStamp = timeStamp; case 'INST', % Instrument Chunk chunkData.baseNote = fread(fid,1,'int8'); chunkData.detune = fread(fid,1,'int8'); chunkData.lowNote = fread(fid,1,'int8'); chunkData.highNote = fread(fid,1,'int8'); chunkData.lowVelocity = fread(fid,1,'int8'); chunkData.highVelocity = fread(fid,1,'int8'); chunkData.gain = fread(fid,1,'int16'); chunkData.sustainLoop = read_loop(fid); chunkData.releaseLoop = read_loop(fid); case 'MARK', % Marker Chunk nMarkers = fread(fid,1,'uint16'); chunkData.nMarkers = nMarkers; chunkData.markerArray = struct('id',cell(nMarkers,1),... 'position',[],'markerName',[]); for iMarker = 1:nMarkers, chunkData.markerArray(iMarker) = read_marker(fid); end markerIDs = [chunkData.markerArray.id]; if any(markerIDs < 1) || (numel(unique(markerIDs)) < nMarkers), warning('aiffread:invalidMarkers',... 'Invalid or repeated marker IDs were detected.'); end case 'MIDI', % MIDI Data Chunk chunkData.midiData = fread(fid,[chunkSizeArray{iChunk} 1],... '*uint8'); case 'NAME', % Name Chunk chunkData.text = read_text(fid,chunkSizeArray{iChunk}); case 'SAXL', % Sound Accelerator (SAXEL) Chunk (AIFC/AIFF-C only) nSaxels = fread(fid,1,'uint16'); chunkData.nSaxels = nSaxels; chunkData.saxelArray = struct('id',cell(nSaxels,1),'size',[],... 'saxelData',[]); for iSaxel = 1:nSaxels, chunkData.saxelArray(iSaxel) = read_saxel(fid); end case 'SSND', % Sound Data Chunk nBytes = ceil(nBits/8); chunkData.offset = fread(fid,1,'uint32'); chunkData.blockSize = fread(fid,1,'uint32'); if isempty(indexRange), data = [nSampleFrames nChannels]; elseif strcmp(compressionType,'NONE'), if (chunkSizeArray{iChunk}-8 ~= nChannels*nSampleFrames*nBytes), error(error_message('sizeMismatch')); end fseek(fid,nBytes*nChannels*(indexRange(1)-1),'cof'); nRead = min(indexRange(2),nSampleFrames)-indexRange(1)+1; data = fread(fid,[nChannels nRead],['*bit' int2str(nBytes*8)]).'; if nBits < nBytes*8, data = data./(2^(nBytes*8-nBits)); end else data = fread(fid,[chunkSizeArray{iChunk}-8 1],'*int8'); end end formChunk.chunkArray(iChunk).chunkData = chunkData; end % Close the file: fclose(fid); %~~~Begin nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ %------------------------------------------------------------------------ function errorStruct = error_message(errorCode,varargin) % % Initialize an error message (and close open files, if necessary). % %------------------------------------------------------------------------ % Close open files, if necessary: if ~isempty(fopen(fid)), fclose(fid); end % Initialize error message text: switch errorCode, case 'badArgumentType', errorText = [varargin{1} ' should be of type ' varargin{2} '.']; case 'badIndexValue', errorText = ['Index range must be specified as a scalar or ' ... '2-element vector of positive, non-zero, non-NaN ' ... 'values.']; case 'invalidFile', errorText = ['Could not open file ''' varargin{1} '''.']; case 'invalidFileFormat', errorText = ['Not a valid ' varargin{1} ' file.']; case 'invalidString', errorText = '''size'' is the only valid string argument.'; case 'missingChunk', errorText = ['''' varargin{1} ''' chunk is required for a ' ... varargin{2} ' file.']; case 'notEnoughInputs', errorText = 'Not enough input arguments.'; case 'repeatChunk', errorText = ['The following chunk IDs should not appear more ' ... 'than once in an ' varargin{1} ' file: ' varargin{2}]; case 'sizeMismatch', errorText = 'Data size mismatch between COMM and SSND chunks.'; case 'unknownExtension', errorText = ['Unknown file extension ''' varargin{1} '''.']; end % Create error structure: errorStruct = struct('message',errorText,... 'identifier',['aiffread:' errorCode]); end %~~~End nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ end %~~~Begin subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ %-------------------------------------------------------------------------- function value = extended2double(exponent,highMantissa,lowMantissa) % % Converts an 80-bit extended floating-point type to a double. % %-------------------------------------------------------------------------- signBit = bitand(exponent,32768); exponent = bitand(exponent,32767); highMantissa = bitand(highMantissa,4294967295); lowMantissa = bitand(lowMantissa,4294967295); if (exponent == 0) && (highMantissa == 0) && (lowMantissa == 0), value = 0; elseif exponent == 32767, if (highMantissa > 0) || (lowMantissa > 0), value = nan; else value = inf; end else value = highMantissa*2^(exponent-16414)+lowMantissa*2^(exponent-16446); end if signBit, value = -value; end end %-------------------------------------------------------------------------- function commentStruct = read_comment(fid) % % Reads a structure of comment data from a file. % %-------------------------------------------------------------------------- commentStruct = struct('timeStamp',fread(fid,1,'uint32'),... 'marker',fread(fid,1,'int16'),... 'count',[],'text',[]); charCount = fread(fid,1,'uint16'); commentStruct.count = charCount; commentStruct.text = read_text(fid,charCount); end %-------------------------------------------------------------------------- function loopStruct = read_loop(fid) % % Reads a structure of loop data from a file. % %-------------------------------------------------------------------------- loopStruct = struct('playMode',fread(fid,1,'int16'),... 'beginLoop',fread(fid,1,'int16'),... 'endLoop',fread(fid,1,'int16')); end %-------------------------------------------------------------------------- function markerStruct = read_marker(fid) % % Reads a structure of marker data from a file. % %-------------------------------------------------------------------------- markerStruct = struct('id',fread(fid,1,'int16'),... 'position',fread(fid,1,'uint32'),... 'markerName',read_pstring(fid)); end %-------------------------------------------------------------------------- function pascalString = read_pstring(fid) % % Reads a Pascal-style string from a file, and afterwards shifts the file % pointer ahead by one byte if necessary to make the total number of bytes % read an even number. % %-------------------------------------------------------------------------- charCount = fread(fid,1,'uint8'); pascalString = fread(fid,[1 charCount],'int8=>char'); if rem(charCount+1,2), fseek(fid,1,'cof'); end end %-------------------------------------------------------------------------- function saxelStruct = read_saxel(fid) % % Reads a structure of saxel data from a file. % %-------------------------------------------------------------------------- saxelStruct = struct('id',fread(fid,1,'int16'),'size',[],'saxelData',[]); saxelBytes = fread(fid,1,'uint16'); saxelStruct.size = saxelBytes; saxelStruct.saxelData = fread(fid,[saxelBytes 1],'*int8'); if rem(saxelBytes,2), fseek(fid,1,'cof'); end end %-------------------------------------------------------------------------- function textString = read_text(fid,charCount) % % Reads ASCII text from a file, and afterwards shifts the file pointer % ahead by one byte if necessary to make the total number of bytes read an % even number. % %-------------------------------------------------------------------------- textString = fread(fid,[1 charCount],'int8=>char'); if rem(charCount,2), fseek(fid,1,'cof'); end end %~~~End subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~