comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:e9a9cd732c1e
1 function [data,Fs,nBits,formChunk] = aiffread(filePath,indexRange)
2 %AIFFREAD Read AIFF (Audio Interchange File Format) sound file.
3 % Y = AIFFREAD(FILE) reads an AIFF file specified by the string FILE,
4 % returning the sampled data in Y. The ".aif" extension is appended if no
5 % extension is given.
6 %
7 % [Y,FS,NBITS,CHUNKDATA] = AIFFREAD(FILE) returns the sample rate (FS) in
8 % Hertz, the number of bits per sample (NBITS) used to encode the data in
9 % the file, and a complete structure of the chunk data (CHUNKDATA)
10 % contained in the AIFF file (minus the actual audio data returned in Y).
11 % See below for a description of CHUNKDATA.
12 %
13 % [...] = AIFFREAD(FILE,N) returns only the first N samples from each
14 % channel in the file.
15 %
16 % [...] = AIFFREAD(FILE,[N1 N2]) returns only samples N1 through N2 from
17 % each channel in the file.
18 %
19 % [SIZ,...] = AIFFREAD(FILE,'size') returns the size of the audio data
20 % contained in the file in place of the actual audio data, where
21 % SIZ = [nSampleFrames nChannels].
22 %
23 %-NOTES--------------------------------------------------------------------
24 %
25 % A note on compressed files:
26 %
27 % Both AIFF and AIFC/AIFF-C (compressed) file types can be read by
28 % AIFFREAD, but the data returned for AIFC/AIFF-C files will be the
29 % raw, compressed data (i.e. AIFFREAD loads the data from the file
30 % without modification). Currently, since there are many compression
31 % formats, it is the responsibility of the user to uncompress the
32 % sound data using parameters defined in the COMM chunk (contained in
33 % the returned CHUNKDATA structure). When loading AIFC/AIFF-C files,
34 % any optional numerical subranges are ignored and the entire set of
35 % compressed data is returned as a column vector of signed bytes
36 % (INT8 type).
37 %
38 % A note on the CHUNKDATA structure:
39 %
40 % The CHUNKDATA structure has the following fields:
41 %
42 % -'chunkID': The 4-character ID for the chunk. This will always be
43 % the string 'FORM'.
44 % -'chunkSize': The size (in bytes) of the remaining data in the
45 % file.
46 % -'formType': A 4-character string for the file type that should
47 % be either 'AIFF' or 'AIFC'.
48 % -'chunkArray': An array of structures, one entry for every chunk
49 % that is in the file (not counting this parent FORM
50 % chunk). The fields of this structure are:
51 % -'chunkID': The 4-character ID for the chunk.
52 % -'chunkSize': The size (in bytes) of the
53 % remaining data in the chunk.
54 % -'chunkData': A structure of data for the
55 % chunk. The form of this data for
56 % a given chunkID can be found at
57 % the links given below for the
58 % file format standards.
59 %
60 % The data portion of certain chunks may not have a clearly defined
61 % format, or that format may be dependent on the implementation or
62 % application that will be using the data. In such cases, the data
63 % returned for that chunk in the CHUNKDATA structure will be in a raw
64 % format (vectors of signed or unsigned 8-bit integers) and it will be
65 % up to the user/application to parse and format this data correctly.
66 % The following is a list of such AIFF/AIFF-C chunks:
67 %
68 % -Audio Recording Chunk (chunkID = 'AESD'): The chunkData
69 % structure has one field 'aesChannelStatusData' that stores a
70 % column vector of 24 8-bit unsigned integers.
71 % -Application Specific Chunk (chunkID = 'APPL'): The chunkData
72 % structure has two fields. 'applicationSignature' stores a
73 % 4-character string identifying the application. 'data' stores a
74 % column vector of 8-bit signed integers.
75 % -MIDI Data Chunk (chunkID = 'MIDI'): The chunkData structure has
76 % one field 'midiData' that stores a column vector of 8-bit
77 % unsigned integers.
78 % -Sound Accelerator (SAXEL) Chunk (chunkID = 'SAXL'): There is no
79 % finalized format for Saxel chunks, so the chunkData structure
80 % follows the draft format given in Appendix D of the AIFF-C
81 % standard.
82 %
83 % Description for the AIFF standard can be found here:
84 %
85 % http://muratnkonar.com/aiff/index.html
86 %
87 % Descriptions for the AIFC/AIFF-C standard can be found here:
88 %
89 % http://www.cnpbagwell.com/aiff-c.txt
90 % http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/...
91 % AIFF-C.9.26.91.pdf
92
93 % Author: Ken Eaton
94 % Last modified: 3/17/09
95 %--------------------------------------------------------------------------
96
97 % Initializations:
98
99 aiffChunkPrecedence = {'COMM' 'SSND' 'MARK' 'INST' 'COMT' 'NAME' ...
100 'AUTH' '[c] ' 'ANNO' 'AESD' 'MIDI' 'APPL'};
101 aiffChunkLimits = [1 1 1 1 1 1 1 1 inf 1 inf inf];
102 aifcChunkPrecedence = {'FVER' 'COMM' 'INST' 'SAXL' 'COMT' 'MARK' ...
103 'SSND' 'NAME' 'AUTH' '[c] ' 'ANNO' 'AESD' ...
104 'MIDI' 'APPL'};
105 aifcChunkLimits = [1 1 1 inf 1 1 1 1 1 1 inf 1 inf inf];
106 fid = -1;
107
108 % Check the number of input arguments:
109
110 switch nargin,
111 case 0,
112 error(error_message('notEnoughInputs'));
113 case 1,
114 indexRange = [1 inf];
115 end
116
117 % Check the file name input argument:
118
119 if ~ischar(filePath),
120 error(error_message('badArgumentType','File name','char'));
121 end
122 [filePath,fileName,fileExtension] = fileparts(filePath);
123 if isempty(fileExtension),
124 fileExtension = '.aif';
125 end
126 if ~any(strcmpi(fileExtension,{'.aif' '.afc' '.aiff' '.aifc'})),
127 error(error_message('unknownExtension',fileExtension));
128 end
129
130 % Check the optional input argument:
131
132 if isnumeric(indexRange), % Numeric range specification
133
134 indexRange = double(indexRange);
135 nRange = numel(indexRange);
136 if (nRange > 2) || any(indexRange < 1) || any(isnan(indexRange)),
137 error(error_message('badIndexValue'));
138 end
139 indexRange = [ones(nRange ~= 2) round(indexRange) inf(nRange == 0)];
140
141 elseif ischar(indexRange), % Specification for returning just the size
142
143 if ~strncmpi(indexRange,'size',numel(indexRange)),
144 error(error_message('invalidString'));
145 end
146 indexRange = [];
147
148 else % Invalid input
149
150 error(error_message('badArgumentType','Optional argument',...
151 'numeric or char'));
152
153 end
154
155 % Check that the file exists and can be opened:
156
157 fid = fopen(fullfile(filePath,[fileName fileExtension]),'r','b');
158 if fid == -1,
159 error(error_message('invalidFile',[fileName fileExtension]));
160 end
161
162 % Initialize formChunk structure:
163
164 formChunk = struct('chunkID',[],'chunkSize',[],'formType',[],...
165 'chunkArray',[]);
166
167 % Read FORM chunk data:
168
169 formChunk.chunkID = read_text(fid,4);
170 if ~strcmp(formChunk.chunkID,'FORM'),
171 error(error_message('invalidFileFormat',fileExtension));
172 end
173 formChunk.chunkSize = fread(fid,1,'int32');
174 formType = read_text(fid,4);
175 if ~any(strcmp(formType,{'AIFF' 'AIFC'})),
176 error(error_message('invalidFileFormat',fileExtension));
177 end
178 formChunk.formType = formType;
179
180 % Since the order of chunks is not guaranteed, first skip through the
181 % file and read just the chunkIDs and chunkSizes:
182
183 iChunk = 0;
184 chunkIDArray = {};
185 chunkSizeArray = {};
186 chunkDataIndex = [];
187 nextChunkID = read_text(fid,4);
188 while ~feof(fid),
189 iChunk = iChunk+1;
190 chunkIDArray{iChunk} = nextChunkID;
191 chunkSize = fread(fid,1,'int32');
192 chunkSizeArray{iChunk} = chunkSize;
193 chunkDataIndex(iChunk) = ftell(fid);
194 fseek(fid,chunkSize+rem(chunkSize,2),'cof');
195 nextChunkID = read_text(fid,4);
196 end
197
198 % Check for the presence of required chunks:
199
200 if ~ismember('COMM',chunkIDArray),
201 error(error_message('missingChunk','COMM',formType));
202 end
203 if strcmp(formType,'AIFC') && ~ismember('FVER',chunkIDArray),
204 error(error_message('missingChunk','FVER',formType));
205 end
206
207 % Check for unknown chunks and order chunks based on chunk precedence:
208
209 if strcmp(formType,'AIFF'),
210 [isChunk,orderIndex] = ismember(chunkIDArray,aiffChunkPrecedence);
211 else
212 [isChunk,orderIndex] = ismember(chunkIDArray,aifcChunkPrecedence);
213 end
214 if ~all(isChunk),
215 unknownChunks = [chunkIDArray(~isChunk); ...
216 repmat({', '},1,sum(~isChunk)-1) {'.'}];
217 orderIndex = orderIndex(isChunk);
218 chunkIDArray = chunkIDArray(isChunk);
219 chunkSizeArray = chunkSizeArray(isChunk);
220 chunkDataIndex = chunkDataIndex(isChunk);
221 warning('aiffread:unknownChunk',...
222 ['The following chunk IDs are unknown for an ' formType ...
223 ' file and will be ignored: ' unknownChunks{:}]);
224 end
225 [index,orderIndex] = sort(orderIndex);
226 chunkIDArray = chunkIDArray(orderIndex);
227 chunkSizeArray = chunkSizeArray(orderIndex);
228 chunkDataIndex = chunkDataIndex(orderIndex);
229
230 % Check for chunks that should not appear more than once:
231
232 index = unique(index(diff(index) < 1));
233 if strcmp(formType,'AIFF'),
234 repeatChunks = aiffChunkPrecedence(aiffChunkLimits(index) == 1);
235 else
236 repeatChunks = aifcChunkPrecedence(aifcChunkLimits(index) == 1);
237 end
238 if ~isempty(repeatChunks),
239 repeatChunks = [repeatChunks; ...
240 repmat({', '},1,numel(repeatChunks)-1) {'.'}];
241 error(error_message('repeatChunk',formType,[repeatChunks{:}]));
242 end
243
244 % Initialize chunkArray data:
245
246 formChunk.chunkArray = struct('chunkID',chunkIDArray,...
247 'chunkSize',chunkSizeArray,...
248 'chunkData',[]);
249
250 % Read the data for each chunk:
251
252 for iChunk = 1:numel(chunkIDArray),
253 chunkData = [];
254 fseek(fid,chunkDataIndex(iChunk),'bof');
255 switch chunkIDArray{iChunk},
256
257 case '[c] ', % Copyright Chunk
258
259 chunkData.text = read_text(fid,chunkSizeArray{iChunk});
260
261 case 'AESD', % Audio Recording Chunk
262
263 chunkData.aesChannelStatusData = ...
264 fread(fid,[chunkSizeArray{iChunk} 1],'*uint8');
265
266 case 'ANNO', % Annotation Chunk
267
268 chunkData.text = read_text(fid,chunkSizeArray{iChunk});
269
270 case 'APPL', % Application Specific Chunk
271
272 chunkData.applicationSignature = read_text(fid,4);
273 chunkData.data = fread(fid,[chunkSizeArray{iChunk}-4 1],'*int8');
274
275 case 'AUTH', % Author Chunk
276
277 chunkData.text = read_text(fid,chunkSizeArray{iChunk});
278
279 case 'COMM', % Common Chunk
280
281 nChannels = fread(fid,1,'int16');
282 chunkData.nChannels = nChannels;
283 nSampleFrames = fread(fid,1,'uint32');
284 if (nSampleFrames > 0) && ~ismember('SSND',chunkIDArray),
285 error(error_message('missingChunk','SSND',formType));
286 end
287 chunkData.nSampleFrames = nSampleFrames;
288 nBits = fread(fid,1,'int16');
289 chunkData.sampleSize = nBits;
290 exponent = fread(fid,1,'uint16');
291 highMantissa = fread(fid,1,'uint32');
292 lowMantissa = fread(fid,1,'uint32');
293 Fs = extended2double(exponent,highMantissa,lowMantissa);
294 chunkData.sampleRate = Fs;
295 if strcmp(formType,'AIFF'),
296 compressionType = 'NONE';
297 else
298 compressionType = read_text(fid,4);
299 chunkData.compressionType = compressionType;
300 chunkData.compressionName = read_pstring(fid);
301 end
302
303 case 'COMT', % Comments Chunk
304
305 nComments = fread(fid,1,'uint16');
306 chunkData.nComments = nComments;
307 chunkData.commentArray = struct('timeStamp',cell(nComments,1),...
308 'marker',[],'count',[],'text',[]);
309 for iComment = 1:nComments,
310 chunkData.commentArray(iComment) = read_comment(fid);
311 end
312
313 case 'FVER', % Format Version Chunk (AIFC/AIFF-C only)
314
315 timeStamp = fread(fid,1,'uint32');
316 if timeStamp ~= 2726318400,
317 warning('aiffread:unknownVersion',...
318 ['File contains an unrecognized version of the ' ...
319 'AIFC/AIFF-C standard.']);
320 end
321 chunkData.timeStamp = timeStamp;
322
323 case 'INST', % Instrument Chunk
324
325 chunkData.baseNote = fread(fid,1,'int8');
326 chunkData.detune = fread(fid,1,'int8');
327 chunkData.lowNote = fread(fid,1,'int8');
328 chunkData.highNote = fread(fid,1,'int8');
329 chunkData.lowVelocity = fread(fid,1,'int8');
330 chunkData.highVelocity = fread(fid,1,'int8');
331 chunkData.gain = fread(fid,1,'int16');
332 chunkData.sustainLoop = read_loop(fid);
333 chunkData.releaseLoop = read_loop(fid);
334
335 case 'MARK', % Marker Chunk
336
337 nMarkers = fread(fid,1,'uint16');
338 chunkData.nMarkers = nMarkers;
339 chunkData.markerArray = struct('id',cell(nMarkers,1),...
340 'position',[],'markerName',[]);
341 for iMarker = 1:nMarkers,
342 chunkData.markerArray(iMarker) = read_marker(fid);
343 end
344 markerIDs = [chunkData.markerArray.id];
345 if any(markerIDs < 1) || (numel(unique(markerIDs)) < nMarkers),
346 warning('aiffread:invalidMarkers',...
347 'Invalid or repeated marker IDs were detected.');
348 end
349
350 case 'MIDI', % MIDI Data Chunk
351
352 chunkData.midiData = fread(fid,[chunkSizeArray{iChunk} 1],...
353 '*uint8');
354
355 case 'NAME', % Name Chunk
356
357 chunkData.text = read_text(fid,chunkSizeArray{iChunk});
358
359 case 'SAXL', % Sound Accelerator (SAXEL) Chunk (AIFC/AIFF-C only)
360
361 nSaxels = fread(fid,1,'uint16');
362 chunkData.nSaxels = nSaxels;
363 chunkData.saxelArray = struct('id',cell(nSaxels,1),'size',[],...
364 'saxelData',[]);
365 for iSaxel = 1:nSaxels,
366 chunkData.saxelArray(iSaxel) = read_saxel(fid);
367 end
368
369 case 'SSND', % Sound Data Chunk
370
371 nBytes = ceil(nBits/8);
372 chunkData.offset = fread(fid,1,'uint32');
373 chunkData.blockSize = fread(fid,1,'uint32');
374 if isempty(indexRange),
375 data = [nSampleFrames nChannels];
376 elseif strcmp(compressionType,'NONE'),
377 if (chunkSizeArray{iChunk}-8 ~= nChannels*nSampleFrames*nBytes),
378 error(error_message('sizeMismatch'));
379 end
380 fseek(fid,nBytes*nChannels*(indexRange(1)-1),'cof');
381 nRead = min(indexRange(2),nSampleFrames)-indexRange(1)+1;
382 data = fread(fid,[nChannels nRead],['*bit' int2str(nBytes*8)]).';
383 if nBits < nBytes*8,
384 data = data./(2^(nBytes*8-nBits));
385 end
386 else
387 data = fread(fid,[chunkSizeArray{iChunk}-8 1],'*int8');
388 end
389
390 end
391 formChunk.chunkArray(iChunk).chunkData = chunkData;
392 end
393
394 % Close the file:
395
396 fclose(fid);
397
398 %~~~Begin nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
399
400 %------------------------------------------------------------------------
401 function errorStruct = error_message(errorCode,varargin)
402 %
403 % Initialize an error message (and close open files, if necessary).
404 %
405 %------------------------------------------------------------------------
406
407 % Close open files, if necessary:
408
409 if ~isempty(fopen(fid)),
410 fclose(fid);
411 end
412
413 % Initialize error message text:
414
415 switch errorCode,
416 case 'badArgumentType',
417 errorText = [varargin{1} ' should be of type ' varargin{2} '.'];
418 case 'badIndexValue',
419 errorText = ['Index range must be specified as a scalar or ' ...
420 '2-element vector of positive, non-zero, non-NaN ' ...
421 'values.'];
422 case 'invalidFile',
423 errorText = ['Could not open file ''' varargin{1} '''.'];
424 case 'invalidFileFormat',
425 errorText = ['Not a valid ' varargin{1} ' file.'];
426 case 'invalidString',
427 errorText = '''size'' is the only valid string argument.';
428 case 'missingChunk',
429 errorText = ['''' varargin{1} ''' chunk is required for a ' ...
430 varargin{2} ' file.'];
431 case 'notEnoughInputs',
432 errorText = 'Not enough input arguments.';
433 case 'repeatChunk',
434 errorText = ['The following chunk IDs should not appear more ' ...
435 'than once in an ' varargin{1} ' file: ' varargin{2}];
436 case 'sizeMismatch',
437 errorText = 'Data size mismatch between COMM and SSND chunks.';
438 case 'unknownExtension',
439 errorText = ['Unknown file extension ''' varargin{1} '''.'];
440 end
441
442 % Create error structure:
443
444 errorStruct = struct('message',errorText,...
445 'identifier',['aiffread:' errorCode]);
446
447 end
448
449 %~~~End nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
450
451 end
452
453 %~~~Begin subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
454
455 %--------------------------------------------------------------------------
456 function value = extended2double(exponent,highMantissa,lowMantissa)
457 %
458 % Converts an 80-bit extended floating-point type to a double.
459 %
460 %--------------------------------------------------------------------------
461
462 signBit = bitand(exponent,32768);
463 exponent = bitand(exponent,32767);
464 highMantissa = bitand(highMantissa,4294967295);
465 lowMantissa = bitand(lowMantissa,4294967295);
466 if (exponent == 0) && (highMantissa == 0) && (lowMantissa == 0),
467 value = 0;
468 elseif exponent == 32767,
469 if (highMantissa > 0) || (lowMantissa > 0),
470 value = nan;
471 else
472 value = inf;
473 end
474 else
475 value = highMantissa*2^(exponent-16414)+lowMantissa*2^(exponent-16446);
476 end
477 if signBit,
478 value = -value;
479 end
480
481 end
482
483 %--------------------------------------------------------------------------
484 function commentStruct = read_comment(fid)
485 %
486 % Reads a structure of comment data from a file.
487 %
488 %--------------------------------------------------------------------------
489
490 commentStruct = struct('timeStamp',fread(fid,1,'uint32'),...
491 'marker',fread(fid,1,'int16'),...
492 'count',[],'text',[]);
493 charCount = fread(fid,1,'uint16');
494 commentStruct.count = charCount;
495 commentStruct.text = read_text(fid,charCount);
496
497 end
498
499 %--------------------------------------------------------------------------
500 function loopStruct = read_loop(fid)
501 %
502 % Reads a structure of loop data from a file.
503 %
504 %--------------------------------------------------------------------------
505
506 loopStruct = struct('playMode',fread(fid,1,'int16'),...
507 'beginLoop',fread(fid,1,'int16'),...
508 'endLoop',fread(fid,1,'int16'));
509
510 end
511
512 %--------------------------------------------------------------------------
513 function markerStruct = read_marker(fid)
514 %
515 % Reads a structure of marker data from a file.
516 %
517 %--------------------------------------------------------------------------
518
519 markerStruct = struct('id',fread(fid,1,'int16'),...
520 'position',fread(fid,1,'uint32'),...
521 'markerName',read_pstring(fid));
522
523 end
524
525 %--------------------------------------------------------------------------
526 function pascalString = read_pstring(fid)
527 %
528 % Reads a Pascal-style string from a file, and afterwards shifts the file
529 % pointer ahead by one byte if necessary to make the total number of bytes
530 % read an even number.
531 %
532 %--------------------------------------------------------------------------
533
534 charCount = fread(fid,1,'uint8');
535 pascalString = fread(fid,[1 charCount],'int8=>char');
536 if rem(charCount+1,2),
537 fseek(fid,1,'cof');
538 end
539
540 end
541
542 %--------------------------------------------------------------------------
543 function saxelStruct = read_saxel(fid)
544 %
545 % Reads a structure of saxel data from a file.
546 %
547 %--------------------------------------------------------------------------
548
549 saxelStruct = struct('id',fread(fid,1,'int16'),'size',[],'saxelData',[]);
550 saxelBytes = fread(fid,1,'uint16');
551 saxelStruct.size = saxelBytes;
552 saxelStruct.saxelData = fread(fid,[saxelBytes 1],'*int8');
553 if rem(saxelBytes,2),
554 fseek(fid,1,'cof');
555 end
556
557 end
558
559 %--------------------------------------------------------------------------
560 function textString = read_text(fid,charCount)
561 %
562 % Reads ASCII text from a file, and afterwards shifts the file pointer
563 % ahead by one byte if necessary to make the total number of bytes read an
564 % even number.
565 %
566 %--------------------------------------------------------------------------
567
568 textString = fread(fid,[1 charCount],'int8=>char');
569 if rem(charCount,2),
570 fseek(fid,1,'cof');
571 end
572
573 end
574
575 %~~~End subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~