|
3 | 3 | % Import an XDF file.
|
4 | 4 | % [Streams,FileHeader] = load_xdf(Filename, Options...)
|
5 | 5 | %
|
6 |
| -% This is a MATLAB importer for mult-stream XDF (Extensible Data Format) recordings. All |
| 6 | +% This is a MATLAB importer for multi-stream XDF (Extensible Data Format) recordings. All |
7 | 7 | % information covered by the XDF 1.0 specification is imported, plus any additional meta-data
|
8 | 8 | % associated with streams or with the container file itself.
|
9 | 9 | %
|
|
231 | 231 | f = fopen(filename,'r','ieee-le.l64'); % file handle
|
232 | 232 | closer = onCleanup(@()close_file(f,filename)); % object that closes the file when the function exits
|
233 | 233 |
|
| 234 | +filesize = getfield(dir(filename),'bytes'); |
234 | 235 |
|
235 | 236 | % there is a fast C mex file for the inner loop, but it's
|
236 | 237 | % not necessarily available for every platform
|
|
264 | 265 | end
|
265 | 266 |
|
266 | 267 |
|
267 |
| -% ====================== |
268 |
| -% === parse the file === |
269 |
| -% ====================== |
| 268 | +% ====================================================== |
| 269 | +% === parse the file ([SomeText] refers to XDF Spec) === |
| 270 | +% ====================================================== |
270 | 271 |
|
271 | 272 | % read [MagicCode]
|
272 | 273 | if ~strcmp(fread(f,4,'*char')','XDF:')
|
|
279 | 280 | % read [NumLengthBytes], [Length]
|
280 | 281 | len = double(read_varlen_int(f));
|
281 | 282 | if ~len
|
282 |
| - break; end |
| 283 | + if ftell(f) < filesize-1024 |
| 284 | + fprintf(' got zero-length chunk, scanning forward to next boundary chunk.\n'); |
| 285 | + scan_forward(f); |
| 286 | + continue; |
| 287 | + else |
| 288 | + if opts.Verbose |
| 289 | + fprintf(' reached end of file.\n'); end |
| 290 | + break; |
| 291 | + end |
| 292 | + end |
283 | 293 | % read [Tag]
|
284 |
| - switch fread(f,1,'uint16') |
| 294 | + tag = fread(f,1,'uint16'); |
| 295 | + if opts.Verbose |
| 296 | + fprintf(' read tag: %i at %d bytes, length=%d\n',tag,ftell(f),len); end |
| 297 | + switch tag |
285 | 298 | case 3 % read [Samples] chunk
|
286 | 299 | try
|
287 | 300 | % read [StreamId]
|
|
328 | 341 | temp(id).time_stamps{end+1} = timestamps;
|
329 | 342 | catch e
|
330 | 343 | % an error occurred (perhaps a chopped-off file): emit a warning
|
331 |
| - % and return the file up to this point |
332 |
| - warning(e.identifier, '%s', e.message); |
333 |
| - break; |
| 344 | + % and scan forward to the next recognized chunk. |
| 345 | + fprintf(' got error "%s" (%s), scanning forward to next boundary chunk.\n',e.identifier,e.message); |
| 346 | + scan_forward(f); |
334 | 347 | end
|
335 | 348 | case 2 % read [StreamHeader] chunk
|
336 | 349 | % read [StreamId]
|
|
372 | 385 | case 1 % read [FileHeader] chunk
|
373 | 386 | fileheader = parse_xml_struct(fread(f,len-2,'*char')');
|
374 | 387 | case 4 % read [ClockOffset] chunk
|
375 |
| - % read [StreamId] |
376 |
| - id = idmap(fread(f,1,'uint32')); |
377 |
| - % read [CollectionTime] |
378 |
| - temp(id).clock_times(end+1) = fread(f,1,'double'); |
379 |
| - % read [OffsetValue] |
380 |
| - temp(id).clock_values(end+1) = fread(f,1,'double'); |
| 388 | + try |
| 389 | + % read [StreamId] |
| 390 | + id = idmap(fread(f,1,'uint32')); |
| 391 | + % read [CollectionTime] |
| 392 | + temp(id).clock_times(end+1) = fread(f,1,'double'); |
| 393 | + % read [OffsetValue] |
| 394 | + temp(id).clock_values(end+1) = fread(f,1,'double'); |
| 395 | + catch e |
| 396 | + % an error occurred (perhaps a chopped-off file): emit a |
| 397 | + % warning and scan forward to the next recognized chunk |
| 398 | + fprintf(' got error "%s" (%s), scanning forward to next boundary chunk.\n',e.identifier,e.message); |
| 399 | + scan_forward(f); |
| 400 | + end |
| 401 | + case 5 % read [Boundary] chunk |
| 402 | + fread(f, len-2, '*uint8'); |
381 | 403 | otherwise
|
382 |
| - % skip other chunk types (Boundary, ...) |
| 404 | + % skip other chunk types |
383 | 405 | fread(f,len-2,'*uint8');
|
384 | 406 | end
|
385 | 407 | end
|
|
513 | 535 | % delays)
|
514 | 536 | if opts.Verbose
|
515 | 537 | disp(' performing jitter removal...'); end
|
516 |
| - |
517 |
| - |
518 | 538 | for k=1:length(temp)
|
519 |
| - |
520 | 539 | if ~isempty(temp(k).time_stamps) && temp(k).srate
|
521 | 540 |
|
522 |
| - |
523 | 541 | if isfield(streams{k}.info.desc, 'synchronization') && ...
|
524 | 542 | isfield(streams{k}.info.desc.synchronization, 'can_drop_samples') && ...
|
525 | 543 | strcmp(streams{k}.info.desc.synchronization.can_drop_samples, 'true')
|
|
575 | 593 | if ~isempty(temp(k).time_stamps)
|
576 | 594 | temp(k).effective_srate = (length(temp(k).time_stamps) - 1) / (temp(k).time_stamps(end) - temp(k).time_stamps(1));
|
577 | 595 | else
|
578 |
| - temp(k).effective_srate = 0; |
| 596 | + temp(k).effective_srate = 0; % BCILAB sets this to NaN |
579 | 597 | end
|
580 | 598 | % transfer the information into the output structs
|
581 | 599 | streams{k}.info.effective_srate = temp(k).effective_srate;
|
|
0 commit comments