-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathaiffread.m
575 lines (477 loc) · 20.3 KB
/
aiffread.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
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~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~