-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathXMidasBlueReader.m
384 lines (346 loc) · 14.5 KB
/
XMidasBlueReader.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
classdef XMidasBlueReader < handle
%XMIDASBLUEREADER Read X-Midas BLUE file
% Reads X-Midas blue files
properties
hcb
ext_header
end
properties(SetAccess = private)
bluefile
dataOffset
end
methods
function obj = XMidasBlueReader(bluefile)
%XMIDASBLUEREADER Construct a reader for the bluefile
% Creates a class that progressively reads samples from
% an X-Midas BLUE file
obj.bluefile = bluefile;
% Open the file, read headers
[fid, msg] = fopen(obj.bluefile, 'r');
if fid < 0
disp(msg)
return
end
obj.hcb = XMidasBlueReader.HCB(fid);
obj.ext_header = XMidasBlueReader.ExtendedHeader(fid, obj.hcb);
obj.resetRead();
% Close file.
fclose(fid);
end
function [samples] = read(obj, numSamples)
%READ Read numSamples from the current position.
% Returns a vector or matrix of the data according to the
% hcb information.
if nargin < 2
numSmaples = 1;
end
% Open the file, skip to read pointer
[fid, msg] = fopen(obj.bluefile, 'r');
if fid < 0
throw(MException(...
'XMidasBlueReader:Read', ...
sprintf('Unable to read file: %s', msg)));
end
% Convert format size to number of elements per sample
elementsPerSample = XMidasBlueReader.FormatSize(obj.hcb.format(1));
% Convert format type to the type string and size (bytes)
[elementPrecisionType, elementPrecisionBytes] = ...
XMidasBlueReader.FormatType(obj.hcb.format(2));
% Update effectivePrecisionType for array inputs
elementPrecisionEffectiveType = elementPrecisionType;
if prod(elementsPerSample) > 1
% Reformats 'char' to '8*char' if there are 8 elements per
% sample, for example.
elementPrecisionEffectiveType = sprintf(...
'%d*%s', prod(elementsPerSample), elementPrecisionType);
end
% Move read position.
fseek(fid, obj.dataOffset, 'bof');
% From header and sample calculations, calculate number of
% bytes per sample, check remaining data length, then read if
% possible.
bytesPerSample = elementPrecisionBytes * prod(elementsPerSample);
bytesRead = obj.dataOffset - obj.hcb.data_start;
bytesRemaining = obj.hcb.data_size - bytesRead;
if bytesPerSample * numSamples > bytesRemaining
throw(MException(...
'XMidasBlueReader:Read', ...
sprintf(...
'Requested read is longer than remaining: (%d vs. %d)', ...
bytesPerSample * numSamples, bytesRemaining) ...
));
end
% Read and move read pointer'
samples = fread(fid, [elementsPerSample, numSamples], elementPrecisionEffectiveType);
if obj.hcb.format(1) == 'C'
% Reformat if complex data
samples = complex(samples(1,:), samples(2,:));
end
obj.dataOffset = obj.dataOffset + (bytesPerSample * numSamples);
% Close file
fclose(fid);
end
function resetRead(obj)
%RESETREAD Resets the read pointer back to the start
% Moves data read pointer to the start of data.
obj.dataOffset = obj.hcb.data_start;
end
function rewind(obj, numSamples)
%REWIND Move the data playback backwards.
% Changes the data offset to go back an appropriate number
% of bytes based on the data information in header.
% Treat no argument as shorthand for fully rewind.
if nargin < 1
obj.resetRead();
return
end
% If already rewound, no reason to do anything.
if obj.dataOffset == obj.hcb.data_start
return;
end
% Convert format size to number of elements per sample
elementsPerSample = XMidasBlueReader.FormatSize(obj.hcb.format(1));
% Convert format type to the type string and size (bytes)
[~, elementPrecisionBytes] = XMidasBlueReader.FormatType(obj.hcb.format(2));
bytesPerSample = elementPrecisionBytes * prod(elementsPerSample);
% Move backwards stopping at the data
moveBytes = bytesPerSample * numSamples;
obj.dataOffset = max([obj.dataOffset - moveBytes, obj.hcb.data_start]);
end
function b = begin(obj)
%BEGIN Returns true if at beginning of data
% Returns true if the internal data pointer is at the
% beginning of the data according to the header.
b = obj.dataOffset == obj.hcb.data_start;
end
function f = end(obj)
%END Returns true if at the end of the data
% Returns true if the internal data pointer is at the
% end of the data according to the header.
f = obj.dataOffset == (obj.hcb.data_start + obj.hcb.data_size);
end
end
methods(Static)
function [hcb] = HCB(fid)
%HCB Reads a hcb control block structure
% Uses the file descriptor to read the hcb Control Block of an
% X-Midas BLUE file. If no file handle is provided, it
% returns a generic HCB structure.
hcb = struct( ...
'version', '', ...
'head_rep', '', ...
'data_rep', '', ...
'detached', 0, ...
'protected', 0, ...
'pipe', 0, ...
'ext_start', 0, ...
'ext_size', 0, ...
'data_start', 0.0, ...
'data_size', 0.0, ...
'type', 0, ...
'format', '', ...
'flagmask', 0, ...
'timecode', 0.0, ...
'inlet', 0, ...
'outlets', 0, ...
'outmask', 0, ...
'pipeloc', 0, ...
'pipesize', 0, ...
'in_byte', 0.0, ...
'out_byte', 0.0, ...
'outbytes', [], ...
'keylength', 0, ...
'keywords', int8([]), ...
'adjunct', int8([]) ...
);
if fid < 0
return
end
% Read the descriptor
fseek(fid, 0, 'bof');
hcb.version = fread(fid, 4, '*char')';
hcb.head_rep = fread(fid, 4, '*char')';
hcb.data_rep = fread(fid, 4, '*char')';
hcb.detached = fread(fid, 1, 'int32');
hcb.protected = fread(fid, 1, 'int32');
hcb.pipe = fread(fid, 1, 'int32');
hcb.ext_start = fread(fid, 1, 'int32');
hcb.ext_size = fread(fid, 1, 'int32');
hcb.data_start = fread(fid, 1, 'double');
hcb.data_size = fread(fid, 1, 'double');
hcb.type = fread(fid, 1, 'int32');
% Check the type for ones we support.
if ~ismember(hcb.type, [1000, 2000])
e = MException(...
'BLUE:HCBType', ...
sprintf('Unsupported HCB.type: %d', hcb.type));
throw(e);
end
hcb.format = fread(fid, 2, '*char')';
hcb.flagmask = fread(fid, 1, 'int16');
hcb.timecode = fread(fid, 1, 'double');
hcb.inlet = fread(fid, 1, 'int16');
hcb.outlets = fread(fid, 1, 'int16');
hcb.outmask = fread(fid, 1, 'int32');
hcb.pipeloc = fread(fid, 1, 'int32');
hcb.pipesize = fread(fid, 1, 'int32');
hcb.in_byte = fread(fid, 1, 'double');
hcb.out_byte = fread(fid, 1, 'double');
hcb.outbytes = fread(fid, 8, 'double'); % 8 8 byte values
hcb.keylength = fread(fid, 1, 'int32');
hcb.keywords = fread(fid, 92, '*char')'; % 92 1-byte values
% Interpret the adjunct
switch hcb.type
case 1000
hcb.adjunct = struct(...
'xstart', fread(fid, 1, 'double'), ...
'xdelta', fread(fid, 1, 'double'), ...
'xunits', fread(fid, 1, 'int32') ...
);
case 2000
hcb.adjunct = struct(...
'xstart', fread(fid, 1, 'double'), ...
'xdelta', fread(fid, 1, 'double'), ...
'xunits', fread(fid, 1, 'int32'), ...
'subsize', fread(fid, 1, 'int32'), ...
'ystart', fread(fid, 1, 'double'), ...
'ydelta', fread(fid, 1, 'double'), ...
'yunits', fread(fid, 1, 'int32') ...
);
end
end % end HCB
function [ext_header] = ExtendedHeader(fid, hcb)
if fid < 0
return;
end
% Move read pointer to ext_start*512
fseek(fid, hcb.ext_start * 512, 'bof');
ext_header = [];
bytesRemaining = hcb.ext_size;
while bytesRemaining > 0
key = struct(...
'lkey', fread(fid, 1, 'int32'),... % 4 bytes
'lext', fread(fid, 1, 'int16'),... % 2 bytes
'ltag', fread(fid, 1, 'int8'), ... % 1 byte
'type', fread(fid, 1, '*char'), ... % 1 Byte
'value', 0, ... % Variable, lkey-lext * sizeof(type)
'tag', ''); % Variable, ltag bytes
[valType, valTypeBytes] = XMidasBlueReader.FormatType(key.type);
valCount = (key.lkey - key.lext) / valTypeBytes;
key.value = fread(fid, valCount, valType)';
key.tag = fread(fid, key.ltag, '*char')';
% Calculate remainder for 8-byte boundary
total = 4 + 2 + 1 + 1 + (key.lkey - key.lext) + key.ltag;
remainder = 8 - rem(total, 8);
if remainder > 0
fseek(fid, remainder, 'cof');
end
% Subtract from bytes remaining and add to keywords
bytesRemaining = bytesRemaining - key.lkey;
ext_header = [ext_header, key];
end
end
function [typeStr, bytes] = FormatType(formatType)
%FORMATTYPE Converts the format type character
% Convert format type to the fread string name and size in
% bytes.
switch formatType
case 'B'
% 8-bit int
typeStr = 'int8';
bytes = 1;
case 'I'
% 16-bit int
typeStr = 'int16';
bytes = 2;
case 'L'
% 32-bit int
typeStr = 'int32';
bytes = 4;
case 'X'
% 64-bit int
typeStr = 'int64';
bytes = 8;
case 'F'
% 32-bit float
typeStr = 'single';
bytes = 4;
case 'D'
% 64-bit float
typeStr = 'double';
bytes = 8;
case 'A'
% ASCII 8 characters or variable length
typeStr = '*char';
bytes = 1;
otherwise
e = MException(...
'XMidasBlueReader:HCB:Format', ...
sprintf('Unsupported HCB format type: %s', formatType));
throw(e);
end
end % FormatType
function [eps] = FormatSize(formatSize)
%FORMATSIZE Converts HCB format size
% Convert format size to number of elements per sample
switch formatSize
case 'S'
% Scalar
eps = 1;
case 'C'
% Complex (real, imaginary pairs)
eps = 2;
case 'V'
% Vector (x,y,z)
eps = 3;
case 'Q'
% Quad (x,y,z, time)
eps = 4;
case 'M'
% Matrix 3x3 (9 elements)
eps = [3,3];
case 'T'
% Transform Matrix 4x4 (16 elements)
eps = [4,4];
case '1'
% 1 element per point
eps = 1;
case '2'
% 2 elements per point
eps = 2;
case '3'
% 3 elements per point
eps = 3;
case '4'
% 4 elements per point
eps = 4;
case '5'
% 5 elements per point
eps = 5;
case '6'
% 6 elements per point
eps = 6;
case '7'
% 7 elements per point
eps = 7;
case '8'
% 8 elements per point
eps = 8;
case '9'
% 9 elements per point
eps = 9;
case 'X'
% 10 elements per point
eps = 10;
case 'A'
% 32 elements per point
eps = 32;
otherwise
e = MException(...
'XMidasBlueReader:HCB:Format', ...
sprintf('Unsupported HCB format size: %s', formatSize));
throw(e);
end
end
end
end