Mercurial > hg > camir-aes2014
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolboxes/MIRtoolbox1.3.2/MIRToolbox/aiffread.m Tue Feb 10 15:05:51 2015 +0000 @@ -0,0 +1,575 @@ +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~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \ No newline at end of file