Coverage for src/audioio/audioloader.py: 83%

964 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-19 19:14 +0000

1"""Loading data, metadata, and markers from audio files. 

2 

3- `load_audio()`: load a whole audio file at once. 

4- `metadata()`: read metadata of an audio file. 

5- `markers()`: read markers of an audio file. 

6- class `AudioLoader`: read data from audio files in chunks. 

7 

8The read in data are always numpy arrays of floats ranging between -1 and 1. 

9The arrays are 2-D ndarrays with first axis time and second axis channel, 

10even for single channel data. 

11 

12If an audio file cannot be loaded, you might need to install 

13additional packages. See 

14[installation](https://bendalab.github.io/audioio/installation) for 

15further instructions. 

16 

17For a demo run the module as: 

18``` 

19python -m src.audioio.audioloader audiofile.wav 

20``` 

21""" 

22 

23import os 

24import gc 

25import sys 

26import ctypes 

27import warnings 

28import numpy as np 

29 

30from io import BytesIO 

31from pathlib import Path 

32from datetime import timedelta 

33 

34from .audiomodules import * 

35from .bufferedarray import BufferedArray 

36from .riffmetadata import metadata_riff, markers_riff 

37from .audiometadata import update_gain, add_unwrap, get_datetime 

38from .audiometadata import flatten_metadata, add_metadata, set_starttime 

39from .audiometadata import load_metadata 

40from .audiotools import unwrap 

41 

42 

43def load_wave(filepath): 

44 """Load wav file using the wave module from pythons standard libray. 

45  

46 Documentation 

47 ------------- 

48 https://docs.python.org/3.8/library/wave.html 

49 

50 Parameters 

51 ---------- 

52 filepath: str or Path 

53 The full path and name of the file to load. 

54 

55 Returns 

56 ------- 

57 data: ndarray 

58 All data traces as an 2-D ndarray, first dimension is time, second is channel 

59 rate: float 

60 The sampling rate of the data in Hertz. 

61 

62 Raises 

63 ------ 

64 ImportError 

65 The wave module is not installed 

66 * 

67 Loading of the data failed 

68 """ 

69 if not audio_modules['wave']: 

70 raise ImportError 

71 

72 wf = wave.open(os.fspath(filepath), 'r') # 'with' is not supported by wave 

73 (nchannels, sampwidth, rate, nframes, comptype, compname) = wf.getparams() 

74 buffer = wf.readframes(nframes) 

75 factor = 2.0**(sampwidth*8-1) 

76 if sampwidth == 1: 

77 dtype = 'u1' 

78 buffer = np.frombuffer(buffer, dtype=dtype).reshape(-1, nchannels) 

79 data = buffer.astype('d')/factor - 1.0 

80 else: 

81 dtype = f'i{sampwidth}' 

82 buffer = np.frombuffer(buffer, dtype=dtype).reshape(-1, nchannels) 

83 data = buffer.astype('d')/factor 

84 wf.close() 

85 return data, float(rate) 

86 

87 

88def load_ewave(filepath): 

89 """Load wav file using ewave module. 

90 

91 Documentation 

92 ------------- 

93 https://github.com/melizalab/py-ewave 

94 

95 Parameters 

96 ---------- 

97 filepath: str or Path 

98 The full path and name of the file to load. 

99 

100 Returns 

101 ------- 

102 data: ndarray 

103 All data traces as an 2-D ndarray, first dimension is time, second is channel. 

104 rate: float 

105 The sampling rate of the data in Hertz. 

106 

107 Raises 

108 ------ 

109 ImportError 

110 The ewave module is not installed 

111 * 

112 Loading of the data failed 

113 """ 

114 if not audio_modules['ewave']: 

115 raise ImportError 

116 

117 data = np.array([]) 

118 rate = 0.0 

119 with ewave.open(os.fspath(filepath), 'r') as wf: 

120 rate = wf.sampling_rate 

121 buffer = wf.read() 

122 data = ewave.rescale(buffer, 'float') 

123 if len(data.shape) == 1: 

124 data = np.reshape(data,(-1, 1)) 

125 return data, float(rate) 

126 

127 

128def load_wavfile(filepath): 

129 """Load wav file using scipy.io.wavfile. 

130 

131 Documentation 

132 ------------- 

133 http://docs.scipy.org/doc/scipy/reference/io.html 

134 Does not support blocked read. 

135  

136 Parameters 

137 ---------- 

138 filepath: str or Path 

139 The full path and name of the file to load. 

140 

141 Returns 

142 ------- 

143 data: ndarray 

144 All data traces as an 2-D ndarray, first dimension is time, second is channel. 

145 rate: float 

146 The sampling rate of the data in Hertz. 

147 

148 Raises 

149 ------ 

150 ImportError 

151 The scipy.io module is not installed 

152 * 

153 Loading of the data failed 

154 """ 

155 if not audio_modules['scipy.io.wavfile']: 

156 raise ImportError 

157 

158 warnings.filterwarnings("ignore") 

159 rate, data = wavfile.read(filepath) 

160 warnings.filterwarnings("always") 

161 if data.dtype == np.uint8: 

162 data = data / 128.0 - 1.0 

163 elif np.issubdtype(data.dtype, np.signedinteger): 

164 data = data / (2.0**(data.dtype.itemsize*8-1)) 

165 else: 

166 data = data.astype(np.float64, copy=False) 

167 if len(data.shape) == 1: 

168 data = np.reshape(data,(-1, 1)) 

169 return data, float(rate) 

170 

171 

172def load_soundfile(filepath): 

173 """Load audio file using SoundFile (based on libsndfile). 

174 

175 Documentation 

176 ------------- 

177 http://pysoundfile.readthedocs.org 

178 http://www.mega-nerd.com/libsndfile 

179 

180 Parameters 

181 ---------- 

182 filepath: str or Path 

183 The full path and name of the file to load. 

184 

185 Returns 

186 ------- 

187 data: ndarray 

188 All data traces as an 2-D ndarray, first dimension is time, second is channel. 

189 rate: float 

190 The sampling rate of the data in Hertz. 

191 

192 Raises 

193 ------ 

194 ImportError 

195 The soundfile module is not installed. 

196 * 

197 Loading of the data failed. 

198 """ 

199 if not audio_modules['soundfile']: 

200 raise ImportError 

201 

202 data = np.array([]) 

203 rate = 0.0 

204 with soundfile.SoundFile(filepath, 'r') as sf: 

205 rate = sf.samplerate 

206 data = sf.read(frames=-1, dtype='float64', always_2d=True) 

207 return data, float(rate) 

208 

209 

210def load_wavefile(filepath): 

211 """Load audio file using wavefile (based on libsndfile). 

212 

213 Documentation 

214 ------------- 

215 https://github.com/vokimon/python-wavefile 

216 

217 Parameters 

218 ---------- 

219 filepath: str or Path 

220 The full path and name of the file to load. 

221 

222 Returns 

223 ------- 

224 data: ndarray 

225 All data traces as an 2-D ndarray, first dimension is time, second is channel. 

226 rate: float 

227 The sampling rate of the data in Hertz. 

228 

229 Raises 

230 ------ 

231 ImportError 

232 The wavefile module is not installed. 

233 * 

234 Loading of the data failed. 

235 """ 

236 if not audio_modules['wavefile']: 

237 raise ImportError 

238 

239 rate, data = wavefile.load(os.fspath(filepath)) 

240 return data.astype(np.float64, copy=False).T, float(rate) 

241 

242 

243def load_audioread(filepath): 

244 """Load audio file using audioread. 

245 

246 Documentation 

247 ------------- 

248 https://github.com/beetbox/audioread 

249 

250 Parameters 

251 ---------- 

252 filepath: str or Path 

253 The full path and name of the file to load. 

254 

255 Returns 

256 ------- 

257 data: ndarray 

258 All data traces as an 2-D ndarray, first dimension is time, second is channel. 

259 rate: float 

260 The sampling rate of the data in Hertz. 

261 

262 Raises 

263 ------ 

264 ImportError 

265 The audioread module is not installed. 

266 * 

267 Loading of the data failed. 

268 """ 

269 if not audio_modules['audioread']: 

270 raise ImportError 

271 

272 data = np.array([]) 

273 rate = 0.0 

274 with audioread.audio_open(filepath) as af: 

275 rate = af.samplerate 

276 data = np.zeros((int(np.ceil(af.samplerate*af.duration)), af.channels), 

277 dtype="<i2") 

278 index = 0 

279 for buffer in af: 

280 fulldata = np.frombuffer(buffer, dtype='<i2').reshape(-1, af.channels) 

281 n = fulldata.shape[0] 

282 if index + n > len(data): 

283 n = len(fulldata) - index 

284 if n <= 0: 

285 break 

286 data[index:index+n,:] = fulldata[:n,:] 

287 index += n 

288 return data/(2.0**15-1.0), float(rate) 

289 

290 

291wavpack_wrapper = 0x04 

292""" Flag for WavpackOpenFileInput() to also read the header of the original file. """ 

293 

294wavpack_float = 0x08 

295""" Bit returned by WavpackGetMode() indicating floating point samples. """ 

296 

297 

298def open_wavpack_file(filepath, flags=0): 

299 """Open a WavPack file using the wavpack library. 

300 

301 Parameters 

302 ---------- 

303 filepath: str or Path 

304 The full path and name of the file to open. 

305 flags: int 

306 Flags passed on to WavpackOpenFileInput(). 

307 

308 Returns 

309 ------- 

310 wpc: ctypes.c_void_p 

311 Handle of the open WavPack file. 

312 

313 Raises 

314 ------ 

315 ImportError 

316 The wavpack library is not installed 

317 IOError 

318 Opening the file failed 

319 """ 

320 if not audio_modules['wavpack']: 

321 raise ImportError 

322 

323 error = ctypes.create_string_buffer(160) 

324 wpc = wavpack.WavpackOpenFileInput(os.fsencode(filepath), error, flags, 0) 

325 if not wpc: 

326 raise IOError(f'{filepath}: {error.value.decode("latin-1")}') 

327 return ctypes.c_void_p(wpc) 

328 

329 

330def unpack_wavpack(wpc, nframes, channels): 

331 """Read samples from an open WavPack file. 

332 

333 Parameters 

334 ---------- 

335 wpc: ctypes.c_void_p 

336 Handle of an open WavPack file. 

337 nframes: int 

338 Number of frames to be read. 

339 channels: int 

340 Number of channels of the file. 

341 

342 Returns 

343 ------- 

344 buffer: 2-D ndarray of int32 

345 The samples as signed integers, right justified. At the end of 

346 the file this can be less than `nframes` frames. 

347 """ 

348 buffer = np.zeros((nframes, channels), dtype=np.int32) 

349 n = 0 

350 while n < nframes: 

351 p = buffer[n:,:].ctypes.data_as(ctypes.POINTER(ctypes.c_int32)) 

352 m = wavpack.WavpackUnpackSamples(wpc, p, nframes - n) 

353 if m == 0: # end of file 

354 break 

355 n += m 

356 return buffer[:n,:] 

357 

358 

359def riff_wavpack(filepath): 

360 """Read the header and trailer of the original file from a WavPack file. 

361 

362 The wavpack program stores everything of the file it compresses 

363 that is not a sample, so that metadata and markers of the original 

364 wave file are still available. The samples are not part of the 

365 returned stream, the size of its data chunk is set to zero. 

366 

367 Parameters 

368 ---------- 

369 filepath: str or Path 

370 The full path and name of the WavPack file. 

371 

372 Returns 

373 ------- 

374 sf: BytesIO or None 

375 Header and trailer of the original file as a stream, 

376 None if the WavPack file does not contain a RIFF header. 

377 """ 

378 try: 

379 wpc = open_wavpack_file(filepath, wavpack_wrapper) 

380 except (ImportError, IOError, TypeError): 

381 return None 

382 nheader = wavpack.WavpackGetWrapperBytes(wpc) 

383 frames = wavpack.WavpackGetNumSamples64(wpc) 

384 if frames > 1: 

385 # the trailer is added to the wrapper only after unpacking 

386 # ran into the end of the file: 

387 wavpack.WavpackSeekSample64(wpc, frames - 1) 

388 unpack_wavpack(wpc, 2, wavpack.WavpackGetNumChannels(wpc)) 

389 n = wavpack.WavpackGetWrapperBytes(wpc) 

390 riff = ctypes.string_at(wavpack.WavpackGetWrapperData(wpc), n) if n > 0 else b'' 

391 wavpack.WavpackCloseFile(wpc) 

392 if riff[:4] != b'RIFF': 

393 return None 

394 header = riff[:nheader] 

395 trailer = riff[nheader:] 

396 if header[-8:-4] == b'data': 

397 header = header[:-4] + bytes(4) # no samples in this data chunk 

398 riff = header + trailer 

399 riff = riff[:4] + (len(riff) - 8).to_bytes(4, 'little') + riff[8:] 

400 return BytesIO(riff) 

401 

402 

403def load_wavpack(filepath): 

404 """Load WavPack file using the wavpack library. 

405 

406 Documentation 

407 ------------- 

408 https://www.wavpack.com 

409 

410 Parameters 

411 ---------- 

412 filepath: str or Path 

413 The full path and name of the file to load. 

414 

415 Returns 

416 ------- 

417 data: ndarray 

418 All data traces as an 2-D ndarray, first dimension is time, second is channel 

419 rate: float 

420 The sampling rate of the data in Hertz. 

421 

422 Raises 

423 ------ 

424 ImportError 

425 The wavpack library is not installed 

426 * 

427 Loading of the data failed 

428 """ 

429 if not audio_modules['wavpack']: 

430 raise ImportError 

431 

432 wpc = open_wavpack_file(filepath) 

433 rate = wavpack.WavpackGetSampleRate(wpc) 

434 channels = wavpack.WavpackGetNumChannels(wpc) 

435 frames = wavpack.WavpackGetNumSamples64(wpc) 

436 buffer = unpack_wavpack(wpc, frames, channels) 

437 if wavpack.WavpackGetMode(wpc) & wavpack_float > 0: 

438 data = buffer.view(np.float32).astype('d') 

439 else: 

440 bits = wavpack.WavpackGetBitsPerSample(wpc) 

441 data = buffer.astype('d')/2.0**(bits-1) 

442 wavpack.WavpackCloseFile(wpc) 

443 return data, float(rate) 

444 

445 

446audio_loader_funcs = ( 

447 ('soundfile', load_soundfile), 

448 ('wave', load_wave), 

449 ('wavefile', load_wavefile), 

450 ('wavpack', load_wavpack), 

451 ('ewave', load_ewave), 

452 ('scipy.io.wavfile', load_wavfile), 

453 ('audioread', load_audioread), 

454 ) 

455"""List of implemented load() functions. 

456 

457Each element of the list is a tuple with the module's name and its 

458load() function. 

459 

460""" 

461 

462 

463def load_audio(filepath, verbose=0): 

464 """Call this function to load all channels of audio data from a file. 

465  

466 This function tries different python modules to load the audio file. 

467 

468 Parameters 

469 ---------- 

470 filepath: str or Path 

471 The full path and name of the file to load. 

472 verbose: int 

473 If larger than zero show detailed error/warning messages. 

474 

475 Returns 

476 ------- 

477 data: ndarray 

478 All data traces as an 2-D ndarray, even for single channel data. 

479 First dimension is time, second is channel. 

480 Data values range maximally between -1 and 1. 

481 rate: float 

482 The sampling rate of the data in Hertz. 

483 

484 Raises 

485 ------ 

486 FileNotFoundError 

487 `filepath` is not an existing file. 

488 EOFError 

489 File size of `filepath` is zero. 

490 IOError 

491 Failed to load data. 

492 

493 Examples 

494 -------- 

495 ``` 

496 import matplotlib.pyplot as plt 

497 from audioio import load_audio 

498  

499 data, rate = load_audio('some/audio.wav') 

500 plt.plot(np.arange(len(data))/rate, data[:,0]) 

501 plt.show() 

502 ``` 

503 """ 

504 # check values: 

505 filepath = Path(filepath) 

506 if not filepath.is_file: 

507 raise FileNotFoundError(f'file "{filepath}" not found') 

508 if filepath.stat().st_size <= 0: 

509 raise EOFError(f'file "{filepath}" is empty (size=0)!') 

510 

511 # load an audio file by trying various modules: 

512 not_installed = [] 

513 errors = [f'failed to load data from file "{filepath}":'] 

514 for lib, load_file in audio_loader_funcs: 

515 if not audio_modules[lib]: 

516 if verbose > 1: 

517 print(f'unable to load data from file "{filepath}" using {lib} module: module not available') 

518 not_installed.append(lib) 

519 continue 

520 try: 

521 data, rate = load_file(filepath) 

522 if len(data) > 0: 

523 if verbose > 0: 

524 print(f'loaded data from file "{filepath}" using {lib} module') 

525 if verbose > 1: 

526 print(f' sampling rate: {rate:g} Hz') 

527 print(f' channels : {data.shape[1]}') 

528 print(f' frames : {len(data)}') 

529 return data, rate 

530 except Exception as e: 

531 errors.append(f' {lib} failed: {str(e)}') 

532 if verbose > 1: 

533 print(errors[-1]) 

534 if len(not_installed) > 0: 

535 errors.append('\n You may need to install one of the ' + \ 

536 ', '.join(not_installed) + ' packages.') 

537 raise IOError('\n'.join(errors)) 

538 return np.zeros(0), 0.0 

539 

540 

541def metadata(filepath, store_empty=False): 

542 """Read metadata of an audio file. 

543 

544 Parameters 

545 ---------- 

546 filepath: str or file handle 

547 The audio file from which to read metadata. 

548 store_empty: bool 

549 If `False` do not return meta data with empty values. 

550 

551 Returns 

552 ------- 

553 meta_data: nested dict 

554 Meta data contained in the audio file. Keys of the nested 

555 dictionaries are always strings. If the corresponding values 

556 are dictionaries, then the key is the section name of the 

557 metadata contained in the dictionary. All other types of 

558 values are values for the respective key. In particular they 

559 are strings. But other types like for example ints or floats 

560 are also allowed. See `audioio.audiometadata` module for 

561 available functions to work with such metadata. 

562  

563 Raises 

564 ------ 

565 ValueError 

566 Not a RIFF file. 

567 

568 Examples 

569 -------- 

570 ``` 

571 from audioio import metadata, print_metadata 

572 md = metadata('data.wav') 

573 print_metadata(md) 

574 ``` 

575 

576 """ 

577 md = {} 

578 try: 

579 md = metadata_riff(filepath, store_empty) 

580 except ValueError: # not a RIFF file, but maybe a WavPack file 

581 sf = riff_wavpack(filepath) 

582 if not sf is None: 

583 md = metadata_riff(sf, store_empty) 

584 if not hasattr(filepath, 'readline'): 

585 filepath = Path(filepath) 

586 count = 0 

587 for ext in ['yml', 'txt', 'dat']: 

588 for mf in filepath.parent.glob(filepath.stem + '*meta*.' + ext): 

589 fmd = {} 

590 fmd['Metadata file'] = mf.name 

591 fmd.update(**load_metadata(mf)) 

592 if count == 0: 

593 md['Metadata'] = fmd 

594 else: 

595 md[f'Metadata{count}'] = fmd 

596 count += 1 

597 return md 

598 

599 

600def markers(filepath): 

601 """ Read markers of an audio file. 

602 

603 See `audioio.audiomarkers` module for available functions 

604 to work with markers. 

605 

606 Parameters 

607 ---------- 

608 filepath: str or file handle 

609 The audio file. 

610 

611 Returns 

612 ------- 

613 locs: 2-D ndarray of int 

614 Marker positions (first column) and spans (second column) 

615 for each marker (rows). 

616 labels: 2-D ndarray of string objects 

617 Labels (first column) and texts (second column) 

618 for each marker (rows). 

619 

620 Raises 

621 ------ 

622 ValueError 

623 Not a RIFF file. 

624  

625 Examples 

626 -------- 

627 ``` 

628 from audioio import markers, print_markers 

629 locs, labels = markers('data.wav') 

630 print_markers(locs, labels) 

631 ``` 

632 """ 

633 try: 

634 return markers_riff(filepath) 

635 except ValueError: # not a RIFF file, but maybe a WavPack file 

636 sf = riff_wavpack(filepath) 

637 if sf is None: 

638 return np.zeros((0, 2), dtype=int), np.zeros((0, 2), dtype=object) 

639 return markers_riff(sf) 

640 

641 

642class AudioLoader(BufferedArray): 

643 """Buffered reading of audio data for random access of the data in the file. 

644  

645 The class allows for reading very large audio files or many 

646 sequential audio files that do not fit into memory. 

647 An AudioLoader instance can be used like a huge read-only numpy array, i.e. 

648 ``` 

649 data = AudioLoader('path/to/audio/file.wav') 

650 x = data[10000:20000,0] 

651 ``` 

652 The first index specifies the frame, the second one the channel. 

653 

654 Behind the scenes, `AudioLoader` tries to open the audio file with 

655 all available audio modules until it succeeds (first line). It 

656 then reads data from the file as necessary for the requested data 

657 (second line). Accesing the content of the audio files via a 

658 buffer that holds only a part of the data is managed by the 

659 `BufferedArray` class. 

660 

661 Reading sequentially through the file is always possible. Some 

662 modules, however, (e.g. audioread, needed for mp3 files) can only 

663 read forward. If previous data are requested, then the file is read 

664 from the beginning again. This slows down access to previous data 

665 considerably. Use the `backsize` argument of the open function to 

666 make sure some data are loaded into the buffer before the requested 

667 frame. Then a subsequent access to the data within `backsize` seconds 

668 before that frame can still be handled without the need to reread 

669 the file from the beginning. 

670 

671 Usage 

672 ----- 

673 With context management: 

674 ``` 

675 import audioio as aio 

676 with aio.AudioLoader(filepath, 60.0, 10.0) as data: 

677 # do something with the content of the file: 

678 x = data[0:10000] 

679 y = data[10000:20000] 

680 z = x + y 

681 ``` 

682 

683 For using a specific audio module, here the audioread module: 

684 ``` 

685 data = aio.AudioLoader() 

686 with data.open_audioread(filepath, 60.0, 10.0): 

687 # do something ... 

688 ``` 

689 

690 Use `blocks()` for sequential, blockwise reading and processing: 

691 ``` 

692 from scipy.signal import spectrogram 

693 nfft = 2048 

694 with aio.AudioLoader('some/audio.wav') as data: 

695 for x in data.blocks(100*nfft, nfft//2): 

696 f, t, Sxx = spectrogram(x, fs=data.rate, 

697 nperseg=nfft, noverlap=nfft//2) 

698 ``` 

699 

700 For loop iterates over single frames (1-D arrays containing samples for each channel): 

701 ``` 

702 with aio.AudioLoader('some/audio.wav') as data: 

703 for x in data: 

704 print(x) 

705 ``` 

706  

707 Traditional open and close: 

708 ``` 

709 data = aio.AudioLoader(filepath, 60.0) 

710 x = data[:,:] # read the whole file 

711 data.close() 

712 ``` 

713  

714 this is the same as: 

715 ``` 

716 data = aio.AudioLoader() 

717 data.open(filepath, 60.0) 

718 ... 

719 ``` 

720 

721 Classes inheriting AudioLoader just need to implement 

722 ``` 

723 self.load_audio_buffer(offset, nsamples, pbuffer) 

724 ``` 

725 This function needs to load the supplied `pbuffer` with 

726 `nframes` frames of data starting at frame `offset`. 

727 

728 In the constructor or some kind of opening function, you need to 

729 set some member variables, as described for `BufferedArray`. 

730 

731 For loading metadata and markers, implement the functions 

732 ``` 

733 self._load_metadata(filepath, **kwargs) 

734 self._load_markers(filepath) 

735 ``` 

736  

737 Parameters 

738 ---------- 

739 filepath: str or Path or list of str of list of Path 

740 Name of the file or list of many file names that should be 

741 made accessible as a single array. 

742 buffersize: float 

743 Size of internal buffer in seconds. 

744 backsize: float 

745 Part of the buffer to be loaded before the requested start index in seconds. 

746 verbose: int 

747 If larger than zero show detailed error/warning messages. 

748 store_empty: bool 

749 If `False` do not return meta data with empty values. 

750 meta_kwargs: dict 

751 Keyword arguments that are passed on to the _load_metadata() 

752 function. For audio data the only recognized key is 

753 `store_empty` - see the metadata() function for more infos. 

754 **kwargs: dict 

755 Further keyword arguments that are passed on to the  

756 specific open() functions. 

757 

758 Attributes 

759 ---------- 

760 filepath: Path 

761 Full path of the opened file. In case of many files, the first one. 

762 file_paths: list of Path 

763 List of pathes of the opened files that are made accessible 

764 as a single array. 

765 file_indices: list of int 

766 For each file the index of its first sample. 

767 rate: float 

768 The sampling rate of the data in seconds. 

769 channels: int 

770 The number of channels. 

771 frames: int 

772 The number of frames in the file. Same as `len()`. 

773 format: str or None 

774 Format of the audio file. 

775 encoding: str or None 

776 Encoding/subtype of the audio file. 

777 shape: tuple 

778 Frames and channels of the data. 

779 ndim: int 

780 Number of dimensions: always 2 (frames and channels). 

781 offset: int 

782 Index of first frame in the current buffer. 

783 buffer: ndarray of floats 

784 The curently available data from the file. 

785 ampl_min: float 

786 Minimum amplitude the file format supports. 

787 Always -1.0 for audio data. 

788 ampl_max: float 

789 Maximum amplitude the file format supports. 

790 Always +1.0 for audio data. 

791 

792 Methods 

793 ------- 

794 - `len()`: Number of frames. 

795 - `file_start_times()`: time of first frame of each file in seconds. 

796 - `get_file_index()`: file path and index of frame contained by this file. 

797 - `open()`: Open an audio file by trying available audio modules. 

798 - `open_*()`: Open an audio file with the respective audio module. 

799 - `__getitem__`: Access data of the audio file. 

800 - `update_buffer()`: Update the internal buffer for a range of frames. 

801 - `blocks()`: Generator for blockwise processing of AudioLoader data. 

802 - `file_start_times()`: Time of first frame of each file in seconds. 

803 - `get_file_index()`: File path and index of frame contained by this file. 

804 - `basename()`: Base name of the audio data. 

805 - `format_dict()`: technical infos about how the data are stored. 

806 - `metadata()`: Metadata stored along with the audio data. 

807 - `markers()`: Markers stored along with the audio data. 

808 - `set_unwrap()`: Set parameters for unwrapping clipped data. 

809 - `set_time_delta()`: Set maximum allowed time difference between successive files. 

810 - `close()`: Close the file. 

811 

812 """ 

813 

814 max_open_files = 5 

815 """ Suggestion for maximum number of open file descriptors. """ 

816 

817 max_open_loaders = 10 

818 """ Suggestion for maximum number of AudioLoaders when opening multiple files. """ 

819 

820 def __init__(self, filepath=None, buffersize=10.0, backsize=0.0, 

821 verbose=0, meta_kwargs={}, **kwargs): 

822 super().__init__(verbose=verbose) 

823 self.format = None 

824 self.encoding = None 

825 self._metadata = None 

826 self._locs = None 

827 self._labels = None 

828 self._load_metadata = metadata 

829 self._load_markers = markers 

830 self._metadata_kwargs = meta_kwargs 

831 self.filepath = None 

832 self.file_paths = None 

833 self.file_indices = [] 

834 self._max_time_diff = 1 

835 self.sf = None 

836 self.close = self._close 

837 self.load_buffer = self._load_buffer_unwrap 

838 self.ampl_min = -1.0 

839 self.ampl_max = +1.0 

840 self.unwrap = False 

841 self.unwrap_thresh = 0.0 

842 self.unwrap_clips = False 

843 self.unwrap_ampl = 1.0 

844 self.unwrap_downscale = True 

845 if filepath is not None: 

846 self.open(filepath, buffersize, backsize, verbose, **kwargs) 

847 

848 numpy_encodings = {np.dtype(np.int64): 'PCM_64', 

849 np.dtype(np.int32): 'PCM_32', 

850 np.dtype(np.int16): 'PCM_16', 

851 np.dtype(np.single): 'FLOAT', 

852 np.dtype(np.double): 'DOUBLE', 

853 np.dtype('>f4'): 'FLOAT', 

854 np.dtype('>f8'): 'DOUBLE'} 

855 """ Map numpy dtypes to encodings. 

856 """ 

857 

858 def _close(self): 

859 pass 

860 

861 def __del__(self): 

862 self.close() 

863 

864 def file_start_times(self): 

865 """ Time of first frame of each file in seconds. 

866  

867 Returns 

868 ------- 

869 times: array of float 

870 Time of the first frame of each file relative to buffer start 

871 in seconds. 

872 """ 

873 times = [] 

874 for idx in self.file_indices: 

875 times.append(idx/self.rate) 

876 return np.array(times) 

877 

878 def get_file_index(self, frame): 

879 """ File path and index of frame contained by this file. 

880 

881 Parameters 

882 ---------- 

883 frame: int 

884 Index of frame. 

885  

886 Returns 

887 ------- 

888 filepath: Path 

889 Path of file that contains the frame. 

890 index: int 

891 Index of the frame relative to the first frame 

892 in the containing file. 

893  

894 Raises 

895 ------ 

896 ValueError 

897 Invalid frame index. 

898 """ 

899 if frame < 0 or frame >= self.frames: 

900 raise ValueError('invalid frame') 

901 fname = self.file_paths[0] 

902 index = self.file_indices[0] 

903 for i in reversed(range(len(self.file_indices))): 

904 if self.file_indices[i] <= frame: 

905 fname = self.file_paths[i] 

906 index = self.file_indices[i] 

907 break 

908 return fname, frame - index 

909 

910 def basename(self, path=None): 

911 """ Base name of the audio data. 

912 

913 Parameters 

914 ---------- 

915 path: str or Path or None 

916 Path of the audio file from which a base name is generated. 

917 If `None`, use `self.filepath`. 

918 

919 Returns 

920 ------- 

921 s: str 

922 The name. Defaults to the stem of `path`. 

923 

924 """ 

925 if path is None: 

926 path = self.filepath 

927 return Path(path).stem 

928 

929 def format_dict(self): 

930 """ Technical infos about how the data are stored in the file. 

931 

932 Returns 

933 ------- 

934 fmt: dict 

935 Dictionary with filepath, format, encoding, samplingrate, 

936 channels, frames, and duration of the audio file as strings. 

937 

938 """ 

939 fmt = dict(name=self.basename(), filepath=os.fsdecode(self.filepath)) 

940 if self.format is not None: 

941 fmt['format'] = self.format 

942 if self.encoding is not None: 

943 fmt['encoding'] = self.encoding 

944 fmt.update(dict(samplingrate=f'{self.rate:.0f}Hz', 

945 channels=self.channels, 

946 frames=self.frames, 

947 duration=f'{self.frames/self.rate:.3f}s')) 

948 return fmt 

949 

950 def metadata(self): 

951 """Metadata of the audio file. 

952 

953 Parameters 

954 ---------- 

955 store_empty: bool 

956 If `False` do not add meta data with empty values. 

957 

958 Returns 

959 ------- 

960 meta_data: nested dict 

961 

962 Meta data contained in the audio file. Keys of the nested 

963 dictionaries are always strings. If the corresponding 

964 values are dictionaries, then the key is the section name 

965 of the metadata contained in the dictionary. All other 

966 types of values are values for the respective key. In 

967 particular they are strings. But other types like for 

968 example ints or floats are also allowed. See 

969 `audioio.audiometadata` module for available functions to 

970 work with such metadata. 

971 

972 """ 

973 if self._metadata is None: 

974 if self._load_metadata is None: 

975 self._metadata = {} 

976 else: 

977 self._metadata = self._load_metadata(self.filepath, 

978 **self._metadata_kwargs) 

979 return self._metadata 

980 

981 def markers(self): 

982 """Read markers of the audio file. 

983 

984 See `audioio.audiomarkers` module for available functions 

985 to work with markers. 

986 

987 Returns 

988 ------- 

989 locs: 2-D ndarray of int 

990 Marker positions (first column) and spans (second column) 

991 for each marker (rows). 

992 labels: 2-D ndarray of str objects 

993 Labels (first column) and texts (second column) 

994 for each marker (rows). 

995 """ 

996 if self._locs is None: 

997 if self._load_markers is None: 

998 self._locs = np.zeros((0, 2), dtype=int) 

999 self._labels = np.zeros((0, 2), dtype=object) 

1000 else: 

1001 self._locs, self._labels = self._load_markers(self.filepath) 

1002 return self._locs, self._labels 

1003 

1004 def set_unwrap(self, thresh, clips=False, down_scale=True, unit=''): 

1005 """Set parameters for unwrapping clipped data. 

1006 

1007 See unwrap() function from the audioio package. 

1008 

1009 Parameters 

1010 ---------- 

1011 thresh: float 

1012 Threshold for detecting wrapped data relative to self.unwrap_ampl 

1013 which is initially set to self.ampl_max. 

1014 If zero, do not unwrap. 

1015 clips: bool 

1016 If True, then clip the unwrapped data properly. 

1017 Otherwise, unwrap the data and double the 

1018 minimum and maximum data range 

1019 (self.ampl_min and self.ampl_max). 

1020 down_scale: bool 

1021 If not `clips`, then downscale the signal by a factor of two, 

1022 in order to keep the range between -1 and 1. 

1023 unit: str 

1024 Unit of the data. 

1025 """ 

1026 self.unwrap_ampl = self.ampl_max 

1027 self.unwrap_thresh = thresh 

1028 self.unwrap_clips = clips 

1029 self.unwrap_down_scale = down_scale 

1030 self.unwrap = thresh > 1e-3 

1031 if self.unwrap: 

1032 if self.unwrap_clips: 

1033 add_unwrap(self.metadata(), 

1034 self.unwrap_thresh*self.unwrap_ampl, 

1035 self.unwrap_ampl, unit) 

1036 elif down_scale: 

1037 update_gain(self.metadata(), 0.5) 

1038 add_unwrap(self.metadata(), 

1039 0.5*self.unwrap_thresh*self.unwrap_ampl, 

1040 0.0, unit) 

1041 else: 

1042 self.ampl_min *= 2 

1043 self.ampl_max *= 2 

1044 add_unwrap(self.metadata(), 

1045 self.unwrap_thresh*self.unwrap_ampl, 

1046 0.0, unit) 

1047 

1048 def _load_buffer_unwrap(self, r_offset, r_size, pbuffer): 

1049 """Load new data and unwrap it. 

1050 

1051 Parameters 

1052 ---------- 

1053 r_offset: int 

1054 First frame to be read from file. 

1055 r_size: int 

1056 Number of frames to be read from file. 

1057 pbuffer: ndarray 

1058 Buffer where to store the loaded data. 

1059 """ 

1060 self.load_audio_buffer(r_offset, r_size, pbuffer) 

1061 if self.unwrap: 

1062 # TODO: handle edge effects! 

1063 unwrap(pbuffer, self.unwrap_thresh, self.unwrap_ampl) 

1064 if self.unwrap_clips: 

1065 pbuffer[pbuffer > self.ampl_max] = self.ampl_max 

1066 pbuffer[pbuffer < self.ampl_min] = self.ampl_min 

1067 elif self.unwrap_down_scale: 

1068 pbuffer *= 0.5 

1069 

1070 def set_time_delta(time_delta): 

1071 """ Set maximum allowed time difference between successive files. 

1072 

1073 Parameters 

1074 ---------- 

1075 time_delta: int 

1076 Maximum number of seconds the start time of a recording file is allowed 

1077 to differ from the end of the previous file. 

1078 Default is one second. 

1079 """ 

1080 self._max_time_diff = time_delta 

1081 

1082 # wave interface:  

1083 def open_wave(self, filepath, buffersize=10.0, backsize=0.0, 

1084 verbose=0): 

1085 """Open audio file for reading using the wave module. 

1086 

1087 Note: we assume that setpos() and tell() use integer numbers! 

1088 

1089 Parameters 

1090 ---------- 

1091 filepath: str or Path 

1092 Name of the file. 

1093 buffersize: float 

1094 Size of internal buffer in seconds. 

1095 backsize: float 

1096 Part of the buffer to be loaded before the requested start index in seconds. 

1097 verbose: int 

1098 If larger than zero show detailed error/warning messages. 

1099 

1100 Raises 

1101 ------ 

1102 ImportError 

1103 The wave module is not installed 

1104 """ 

1105 self.verbose = verbose 

1106 if self.verbose > 0: 

1107 print(f'open_wave(filepath) with filepath={filepath}') 

1108 if not audio_modules['wave']: 

1109 self.rate = 0.0 

1110 self.channels = 0 

1111 self.frames = 0 

1112 self.size = 0 

1113 self.shape = (0, 0) 

1114 self.offset = 0 

1115 raise ImportError 

1116 if self.sf is not None: 

1117 self._close_wave() 

1118 self.sf = wave.open(os.fspath(filepath), 'r') 

1119 self.filepath = Path(filepath) 

1120 self.file_paths = [self.filepath] 

1121 self.file_indices = [0] 

1122 self.rate = float(self.sf.getframerate()) 

1123 self.format = 'WAV' 

1124 sampwidth = self.sf.getsampwidth() 

1125 if sampwidth == 1: 

1126 self.dtype = 'u1' 

1127 self.encoding = 'PCM_U8' 

1128 else: 

1129 self.dtype = f'i{sampwidth}' 

1130 self.encoding = f'PCM_{sampwidth*8}' 

1131 self.factor = 1.0/(2.0**(sampwidth*8-1)) 

1132 self.channels = self.sf.getnchannels() 

1133 self.frames = self.sf.getnframes() 

1134 self.shape = (self.frames, self.channels) 

1135 self.size = self.frames * self.channels 

1136 self.bufferframes = int(buffersize*self.rate) 

1137 self.backframes = int(backsize*self.rate) 

1138 self.init_buffer() 

1139 self.close = self._close_wave 

1140 self.load_audio_buffer = self._load_buffer_wave 

1141 # read 1 frame to determine the unit of the position values: 

1142 self.p0 = self.sf.tell() 

1143 self.sf.readframes(1) 

1144 self.pfac = self.sf.tell() - self.p0 

1145 self.sf.setpos(self.p0) 

1146 return self 

1147 

1148 def _close_wave(self): 

1149 """Close the audio file using the wave module. """ 

1150 if self.sf is not None: 

1151 self.sf.close() 

1152 self.sf = None 

1153 

1154 def _load_buffer_wave(self, r_offset, r_size, buffer): 

1155 """Load new data from file using the wave module. 

1156 

1157 Parameters 

1158 ---------- 

1159 r_offset: int 

1160 First frame to be read from file. 

1161 r_size: int 

1162 Number of frames to be read from file. 

1163 buffer: ndarray 

1164 Buffer where to store the loaded data. 

1165 """ 

1166 if self.sf is None: 

1167 self.sf = wave.open(os.fspath(self.filepath), 'r') 

1168 self.sf.setpos(r_offset*self.pfac + self.p0) 

1169 fbuffer = self.sf.readframes(r_size) 

1170 fbuffer = np.frombuffer(fbuffer, dtype=self.dtype).reshape((-1, self.channels)) 

1171 if self.dtype[0] == 'u': 

1172 buffer[:, :] = fbuffer * self.factor - 1.0 

1173 else: 

1174 buffer[:, :] = fbuffer * self.factor 

1175 

1176 

1177 # ewave interface:  

1178 def open_ewave(self, filepath, buffersize=10.0, backsize=0.0, 

1179 verbose=0): 

1180 """Open audio file for reading using the ewave module. 

1181 

1182 Parameters 

1183 ---------- 

1184 filepath: str or Path 

1185 Name of the file. 

1186 buffersize: float 

1187 Size of internal buffer in seconds. 

1188 backsize: float 

1189 Part of the buffer to be loaded before the requested start index in seconds. 

1190 verbose: int 

1191 If larger than zero show detailed error/warning messages. 

1192 

1193 Raises 

1194 ------ 

1195 ImportError 

1196 The ewave module is not installed. 

1197 """ 

1198 self.verbose = verbose 

1199 if self.verbose > 0: 

1200 print(f'open_ewave(filepath) with filepath={filepath}') 

1201 if not audio_modules['ewave']: 

1202 self.rate = 0.0 

1203 self.channels = 0 

1204 self.frames = 0 

1205 self.shape = (0, 0) 

1206 self.size = 0 

1207 self.offset = 0 

1208 raise ImportError 

1209 if self.sf is not None: 

1210 self._close_ewave() 

1211 self.sf = ewave.open(os.fspath(filepath), 'r') 

1212 self.filepath = Path(filepath) 

1213 self.file_paths = [self.filepath] 

1214 self.file_indices = [0] 

1215 self.rate = float(self.sf.sampling_rate) 

1216 self.channels = self.sf.nchannels 

1217 self.frames = self.sf.nframes 

1218 self.shape = (self.frames, self.channels) 

1219 self.size = self.frames * self.channels 

1220 self.format = 'WAV' # or WAVEX? 

1221 self.encoding = self.numpy_encodings[self.sf.dtype] 

1222 self.bufferframes = int(buffersize*self.rate) 

1223 self.backframes = int(backsize*self.rate) 

1224 self.init_buffer() 

1225 self.close = self._close_ewave 

1226 self.load_audio_buffer = self._load_buffer_ewave 

1227 return self 

1228 

1229 def _close_ewave(self): 

1230 """Close the audio file using the ewave module. """ 

1231 if self.sf is not None: 

1232 del self.sf 

1233 self.sf = None 

1234 

1235 def _load_buffer_ewave(self, r_offset, r_size, buffer): 

1236 """Load new data from file using the ewave module. 

1237 

1238 Parameters 

1239 ---------- 

1240 r_offset: int 

1241 First frame to be read from file. 

1242 r_size: int 

1243 Number of frames to be read from file. 

1244 buffer: ndarray 

1245 Buffer where to store the loaded data. 

1246 """ 

1247 if self.sf is None: 

1248 self.sf = ewave.open(os.fspath(self.filepath), 'r') 

1249 fbuffer = self.sf.read(frames=r_size, offset=r_offset, memmap='r') 

1250 fbuffer = ewave.rescale(fbuffer, 'float') 

1251 if len(fbuffer.shape) == 1: 

1252 fbuffer = np.reshape(fbuffer,(-1, 1)) 

1253 buffer[:,:] = fbuffer 

1254 

1255 

1256 # soundfile interface:  

1257 def open_soundfile(self, filepath, buffersize=10.0, backsize=0.0, 

1258 verbose=0): 

1259 """Open audio file for reading using the SoundFile module. 

1260 

1261 Parameters 

1262 ---------- 

1263 filepath: str or Path 

1264 Name of the file. 

1265 bufferframes: float 

1266 Size of internal buffer in seconds. 

1267 backsize: float 

1268 Part of the buffer to be loaded before the requested start index in seconds. 

1269 verbose: int 

1270 If larger than zero show detailed error/warning messages. 

1271 

1272 Raises 

1273 ------ 

1274 ImportError 

1275 The SoundFile module is not installed 

1276 """ 

1277 self.verbose = verbose 

1278 if self.verbose > 0: 

1279 print(f'open_soundfile(filepath) with filepath={filepath}') 

1280 if not audio_modules['soundfile']: 

1281 self.rate = 0.0 

1282 self.channels = 0 

1283 self.frames = 0 

1284 self.shape = (0, 0) 

1285 self.size = 0 

1286 self.offset = 0 

1287 raise ImportError 

1288 if self.sf is not None: 

1289 self._close_soundfile() 

1290 self.sf = soundfile.SoundFile(filepath, 'r') 

1291 self.filepath = Path(filepath) 

1292 self.file_paths = [self.filepath] 

1293 self.file_indices = [0] 

1294 self.rate = float(self.sf.samplerate) 

1295 self.channels = self.sf.channels 

1296 self.frames = 0 

1297 self.size = 0 

1298 if self.sf.seekable(): 

1299 self.frames = self.sf.seek(0, soundfile.SEEK_END) 

1300 self.sf.seek(0, soundfile.SEEK_SET) 

1301 # TODO: if not seekable, we cannot handle that file! 

1302 self.shape = (self.frames, self.channels) 

1303 self.size = self.frames * self.channels 

1304 self.format = self.sf.format 

1305 self.encoding = self.sf.subtype 

1306 self.bufferframes = int(buffersize*self.rate) 

1307 self.backframes = int(backsize*self.rate) 

1308 self.init_buffer() 

1309 self.close = self._close_soundfile 

1310 self.load_audio_buffer = self._load_buffer_soundfile 

1311 return self 

1312 

1313 def _close_soundfile(self): 

1314 """Close the audio file using the SoundFile module. """ 

1315 if self.sf is not None: 

1316 self.sf.close() 

1317 self.sf = None 

1318 

1319 def _load_buffer_soundfile(self, r_offset, r_size, buffer): 

1320 """Load new data from file using the SoundFile module. 

1321 

1322 Parameters 

1323 ---------- 

1324 r_offset: int 

1325 First frame to be read from file. 

1326 r_size: int 

1327 Number of frames to be read from file. 

1328 buffer: ndarray 

1329 Buffer where to store the loaded data. 

1330 """ 

1331 if self.sf is None: 

1332 self.sf = soundfile.SoundFile(self.filepath, 'r') 

1333 self.sf.seek(r_offset, soundfile.SEEK_SET) 

1334 buffer[:, :] = self.sf.read(r_size, always_2d=True) 

1335 

1336 

1337 # wavefile interface:  

1338 def open_wavefile(self, filepath, buffersize=10.0, backsize=0.0, 

1339 verbose=0): 

1340 """Open audio file for reading using the wavefile module. 

1341 

1342 Parameters 

1343 ---------- 

1344 filepath: str or Path 

1345 Name of the file. 

1346 bufferframes: float 

1347 Size of internal buffer in seconds. 

1348 backsize: float 

1349 Part of the buffer to be loaded before the requested start index in seconds. 

1350 verbose: int 

1351 If larger than zero show detailed error/warning messages. 

1352 

1353 Raises 

1354 ------ 

1355 ImportError 

1356 The wavefile module is not installed 

1357 """ 

1358 self.verbose = verbose 

1359 if self.verbose > 0: 

1360 print(f'open_wavefile(filepath) with filepath={filepath}') 

1361 if not audio_modules['wavefile']: 

1362 self.rate = 0.0 

1363 self.channels = 0 

1364 self.frames = 0 

1365 self.shape = (0, 0) 

1366 self.size = 0 

1367 self.offset = 0 

1368 raise ImportError 

1369 if self.sf is not None: 

1370 self._close_wavefile() 

1371 self.sf = wavefile.WaveReader(os.fspath(filepath)) 

1372 self.filepath = Path(filepath) 

1373 self.file_paths = [self.filepath] 

1374 self.file_indices = [0] 

1375 self.rate = float(self.sf.samplerate) 

1376 self.channels = self.sf.channels 

1377 self.frames = self.sf.frames 

1378 self.shape = (self.frames, self.channels) 

1379 self.size = self.frames * self.channels 

1380 # get format and encoding: 

1381 for attr in dir(wavefile.Format): 

1382 v = getattr(wavefile.Format, attr) 

1383 if isinstance(v, int): 

1384 if v & wavefile.Format.TYPEMASK > 0 and \ 

1385 (self.sf.format & wavefile.Format.TYPEMASK) == v: 

1386 self.format = attr 

1387 if v & wavefile.Format.SUBMASK > 0 and \ 

1388 (self.sf.format & wavefile.Format.SUBMASK) == v: 

1389 self.encoding = attr 

1390 # init buffer: 

1391 self.bufferframes = int(buffersize*self.rate) 

1392 self.backframes = int(backsize*self.rate) 

1393 self.init_buffer() 

1394 self.close = self._close_wavefile 

1395 self.load_audio_buffer = self._load_buffer_wavefile 

1396 return self 

1397 

1398 def _close_wavefile(self): 

1399 """Close the audio file using the wavefile module. """ 

1400 if self.sf is not None: 

1401 self.sf.close() 

1402 self.sf = None 

1403 

1404 def _load_buffer_wavefile(self, r_offset, r_size, buffer): 

1405 """Load new data from file using the wavefile module. 

1406 

1407 Parameters 

1408 ---------- 

1409 r_offset: int 

1410 First frame to be read from file. 

1411 r_size: int 

1412 Number of frames to be read from file. 

1413 buffer: ndarray 

1414 Buffer where to store the loaded data. 

1415 """ 

1416 if self.sf is None: 

1417 self.sf = wavefile.WaveReader(os.fspath(self.filepath)) 

1418 self.sf.seek(r_offset, wavefile.Seek.SET) 

1419 fbuffer = self.sf.buffer(r_size, dtype=self.buffer.dtype) 

1420 self.sf.read(fbuffer) 

1421 buffer[:,:] = fbuffer.T 

1422 

1423 

1424 # audioread interface:  

1425 def open_audioread(self, filepath, buffersize=10.0, backsize=0.0, 

1426 verbose=0): 

1427 """Open audio file for reading using the audioread module. 

1428 

1429 Note, that audioread can only read forward, therefore random and 

1430 backward access is really slow. 

1431 

1432 Parameters 

1433 ---------- 

1434 filepath: str or Path 

1435 Name of the file. 

1436 bufferframes: float 

1437 Size of internal buffer in seconds. 

1438 backsize: float 

1439 Part of the buffer to be loaded before the requested start index in seconds. 

1440 verbose: int 

1441 If larger than zero show detailed error/warning messages. 

1442 

1443 Raises 

1444 ------ 

1445 ImportError 

1446 The audioread module is not installed 

1447 """ 

1448 self.verbose = verbose 

1449 if self.verbose > 0: 

1450 print(f'open_audioread(filepath) with filepath={filepath}') 

1451 if not audio_modules['audioread']: 

1452 self.rate = 0.0 

1453 self.channels = 0 

1454 self.frames = 0 

1455 self.shape = (0, 0) 

1456 self.size = 0 

1457 self.offset = 0 

1458 raise ImportError 

1459 if self.sf is not None: 

1460 self._close_audioread() 

1461 self.sf = audioread.audio_open(filepath) 

1462 self.filepath = Path(filepath) 

1463 self.file_paths = [self.filepath] 

1464 self.file_indices = [0] 

1465 self.rate = float(self.sf.samplerate) 

1466 self.channels = self.sf.channels 

1467 self.frames = int(np.ceil(self.rate*self.sf.duration)) 

1468 self.shape = (self.frames, self.channels) 

1469 self.size = self.frames * self.channels 

1470 self.bufferframes = int(buffersize*self.rate) 

1471 self.backframes = int(backsize*self.rate) 

1472 self.init_buffer() 

1473 self.read_buffer = np.zeros((0,0)) 

1474 self.read_offset = 0 

1475 self.close = self._close_audioread 

1476 self.load_audio_buffer = self._load_buffer_audioread 

1477 self.sf_iter = self.sf.__iter__() 

1478 return self 

1479 

1480 def _close_audioread(self): 

1481 """Close the audio file using the audioread module. """ 

1482 if self.sf is not None: 

1483 self.sf.__exit__(None, None, None) 

1484 self.sf = None 

1485 

1486 def _load_buffer_audioread(self, r_offset, r_size, buffer): 

1487 """Load new data from file using the audioread module. 

1488 

1489 audioread can only iterate through a file once and in blocksizes that are 

1490 given by audioread. Therefore we keep yet another buffer: `self.read_buffer` 

1491 at file offset `self.read_offset` containing whatever audioread returned. 

1492 

1493 Parameters 

1494 ---------- 

1495 r_offset: int 

1496 First frame to be read from file. 

1497 r_size: int 

1498 Number of frames to be read from file. 

1499 buffer: ndarray 

1500 Buffer where to store the loaded data. 

1501 """ 

1502 if self.sf is None: 

1503 self.sf = audioread.audio_open(self.filepath) 

1504 b_offset = 0 

1505 if ( self.read_offset + self.read_buffer.shape[0] >= r_offset + r_size 

1506 and self.read_offset < r_offset + r_size ): 

1507 # read_buffer overlaps at the end of the requested interval: 

1508 i = 0 

1509 n = r_offset + r_size - self.read_offset 

1510 if n > r_size: 

1511 i += n - r_size 

1512 n = r_size 

1513 buffer[self.read_offset+i-r_offset:self.read_offset+i+n-r_offset,:] = self.read_buffer[i:i+n,:] / (2.0**15-1.0) 

1514 if self.verbose > 2: 

1515 print(f' recycle {n:6d} frames from the front of the read buffer at {self.read_offset}-{self.read_offset+n} ({self.read_offset-self.offset}-{self.read_offset-self.offset+n} in buffer)') 

1516 r_size -= n 

1517 if r_size <= 0: 

1518 return 

1519 # go back to beginning of file: 

1520 if r_offset < self.read_offset: 

1521 if self.verbose > 2: 

1522 print(' rewind') 

1523 self._close_audioread() 

1524 self.sf = audioread.audio_open(self.filepath) 

1525 self.sf_iter = self.sf.__iter__() 

1526 self.read_buffer = np.zeros((0,0)) 

1527 self.read_offset = 0 

1528 # read to position: 

1529 while self.read_offset + self.read_buffer.shape[0] < r_offset: 

1530 self.read_offset += self.read_buffer.shape[0] 

1531 try: 

1532 if hasattr(self.sf_iter, 'next'): 

1533 fbuffer = self.sf_iter.next() 

1534 else: 

1535 fbuffer = next(self.sf_iter) 

1536 except StopIteration: 

1537 self.read_buffer = np.zeros((0,0)) 

1538 buffer[:,:] = 0.0 

1539 if self.verbose > 1: 

1540 print(f' caught StopIteration, padded buffer with {r_size} zeros') 

1541 break 

1542 self.read_buffer = np.frombuffer(fbuffer, dtype='<i2').reshape(-1, self.channels) 

1543 if self.verbose > 2: 

1544 print(f' read forward by {self.read_buffer.shape[0]} frames') 

1545 # recycle file data: 

1546 if ( self.read_offset + self.read_buffer.shape[0] > r_offset 

1547 and self.read_offset <= r_offset ): 

1548 i = r_offset - self.read_offset 

1549 n = self.read_offset + self.read_buffer.shape[0] - r_offset 

1550 if n > r_size: 

1551 n = r_size 

1552 buffer[:n,:] = self.read_buffer[i:i+n,:] / (2.0**15-1.0) 

1553 if self.verbose > 2: 

1554 print(f' recycle {n:6d} frames from the end of the read buffer at {self.read_offset}-{self.read_offset + self.read_buffer.shape[0]} to {r_offset}-{r_offset+n} ({r_offset-self.offset}-{r_offset+n-self.offset} in buffer)') 

1555 b_offset += n 

1556 r_offset += n 

1557 r_size -= n 

1558 # read data: 

1559 if self.verbose > 2 and r_size > 0: 

1560 print(f' read {r_size:6d} frames at {r_offset}-{r_offset+r_size} ({r_offset-self.offset}-{r_offset+r_size-self.offset} in buffer)') 

1561 while r_size > 0: 

1562 self.read_offset += self.read_buffer.shape[0] 

1563 try: 

1564 if hasattr(self.sf_iter, 'next'): 

1565 fbuffer = self.sf_iter.next() 

1566 else: 

1567 fbuffer = next(self.sf_iter) 

1568 except StopIteration: 

1569 self.read_buffer = np.zeros((0,0)) 

1570 buffer[b_offset:,:] = 0.0 

1571 if self.verbose > 1: 

1572 print(f' caught StopIteration, padded buffer with {r_size} zeros') 

1573 break 

1574 self.read_buffer = np.frombuffer(fbuffer, dtype='<i2').reshape(-1, self.channels) 

1575 n = self.read_buffer.shape[0] 

1576 if n > r_size: 

1577 n = r_size 

1578 if n > 0: 

1579 buffer[b_offset:b_offset+n,:] = self.read_buffer[:n,:] / (2.0**15-1.0) 

1580 if self.verbose > 2: 

1581 print(f' read {n:6d} frames to {r_offset}-{r_offset+n} ({r_offset-self.offset}-{r_offset+n-self.offset} in buffer)') 

1582 b_offset += n 

1583 r_offset += n 

1584 r_size -= n 

1585 

1586 

1587 # wavpack interface: 

1588 def open_wavpack(self, filepath, buffersize=10.0, backsize=0.0, 

1589 verbose=0): 

1590 """Open WavPack file for reading using the wavpack library. 

1591 

1592 Parameters 

1593 ---------- 

1594 filepath: str or Path 

1595 Name of the file. 

1596 buffersize: float 

1597 Size of internal buffer in seconds. 

1598 backsize: float 

1599 Part of the buffer to be loaded before the requested start index in seconds. 

1600 verbose: int 

1601 If larger than zero show detailed error/warning messages. 

1602 

1603 Raises 

1604 ------ 

1605 ImportError 

1606 The wavpack library is not installed 

1607 """ 

1608 self.verbose = verbose 

1609 if self.verbose > 0: 

1610 print(f'open_wavpack(filepath) with filepath={filepath}') 

1611 if not audio_modules['wavpack']: 

1612 self.rate = 0.0 

1613 self.channels = 0 

1614 self.frames = 0 

1615 self.size = 0 

1616 self.shape = (0, 0) 

1617 self.offset = 0 

1618 raise ImportError 

1619 if self.sf is not None: 

1620 self._close_wavpack() 

1621 self.sf = open_wavpack_file(filepath) 

1622 self.filepath = Path(filepath) 

1623 self.file_paths = [self.filepath] 

1624 self.file_indices = [0] 

1625 self.rate = float(wavpack.WavpackGetSampleRate(self.sf)) 

1626 self.format = 'WAVPACK' 

1627 if wavpack.WavpackGetMode(self.sf) & wavpack_float > 0: 

1628 self.encoding = 'FLOAT' 

1629 self.factor = 1.0 

1630 else: 

1631 bits = wavpack.WavpackGetBitsPerSample(self.sf) 

1632 self.encoding = f'PCM_{bits}' 

1633 self.factor = 1.0/(2.0**(bits-1)) 

1634 self.channels = wavpack.WavpackGetNumChannels(self.sf) 

1635 self.frames = wavpack.WavpackGetNumSamples64(self.sf) 

1636 self.shape = (self.frames, self.channels) 

1637 self.size = self.frames * self.channels 

1638 self.bufferframes = int(buffersize*self.rate) 

1639 self.backframes = int(backsize*self.rate) 

1640 self.init_buffer() 

1641 self.close = self._close_wavpack 

1642 self.load_audio_buffer = self._load_buffer_wavpack 

1643 # the frame at which the file continues to unpack: 

1644 self.wavpack_pos = 0 

1645 return self 

1646 

1647 def _close_wavpack(self): 

1648 """Close the audio file using the wavpack library. """ 

1649 if self.sf is not None: 

1650 wavpack.WavpackCloseFile(self.sf) 

1651 self.sf = None 

1652 

1653 def _load_buffer_wavpack(self, r_offset, r_size, buffer): 

1654 """Load new data from file using the wavpack library. 

1655 

1656 Parameters 

1657 ---------- 

1658 r_offset: int 

1659 First frame to be read from file. 

1660 r_size: int 

1661 Number of frames to be read from file. 

1662 buffer: ndarray 

1663 Buffer where to store the loaded data. 

1664 """ 

1665 if self.sf is None: 

1666 self.sf = open_wavpack_file(self.filepath) 

1667 self.wavpack_pos = 0 

1668 if r_offset != self.wavpack_pos: # reading on is cheaper than seeking 

1669 if wavpack.WavpackSeekSample64(self.sf, r_offset) == 0: 

1670 raise IOError(f'failed to seek to frame {r_offset} of file "{self.filepath}"') 

1671 self.wavpack_pos = r_offset 

1672 fbuffer = unpack_wavpack(self.sf, r_size, self.channels) 

1673 self.wavpack_pos += len(fbuffer) 

1674 if self.encoding == 'FLOAT': 

1675 buffer[:len(fbuffer),:] = fbuffer.view(np.float32) 

1676 else: 

1677 buffer[:len(fbuffer),:] = fbuffer * self.factor 

1678 

1679 

1680 # open multiple audio files as one: 

1681 def open_multiple(self, filepaths, buffersize=10.0, backsize=0.0, 

1682 verbose=0, mode='strict', rate=None, channels=None, 

1683 end_indices=None): 

1684 """Open multiple audio files as a single concatenated array. 

1685 

1686 Parameters 

1687 ---------- 

1688 filepaths: list of str or Path 

1689 List of file paths of audio files. 

1690 buffersize: float 

1691 Size of internal buffer in seconds. 

1692 backsize: float 

1693 Part of the buffer to be loaded before the requested start index in seconds. 

1694 verbose: int 

1695 If larger than zero show detailed error/warning messages. 

1696 mode: 'relaxed' or 'strict' 

1697 If 'strict', only concatenate files if they contain 

1698 a start time in their meta data. 

1699 rate: float 

1700 If provided, do a minimal initialization (no checking) 

1701 using the provided sampling rate (in Hertz), channels, 

1702 and end_indices. 

1703 channels: int 

1704 If provided, do a minimal initialization (no checking) 

1705 using the provided rate, number of channels, and end_indices. 

1706 end_indices: sequence of int 

1707 If provided, do a minimal initialization (no checking) 

1708 using the provided rate, channels, and end_indices. 

1709 

1710 Raises 

1711 ------ 

1712 TypeError 

1713 `filepaths` must be a sequence. 

1714 ValueError 

1715 Empty `filepaths`. 

1716 FileNotFoundError 

1717 `filepaths` does not contain a single valid file. 

1718 

1719 """ 

1720 if not isinstance(filepaths, (list, tuple, np.ndarray)): 

1721 raise TypeError('input argument filepaths is not a sequence!') 

1722 if len(filepaths) == 0: 

1723 raise ValueError('input argument filepaths is empy sequence!') 

1724 self.buffersize = buffersize 

1725 self.backsize = backsize 

1726 self.filepath = None 

1727 self.file_paths = [] 

1728 self.open_files = [] 

1729 self.open_loaders = [] 

1730 self.audio_files = [] 

1731 self.collect_counter = 0 

1732 self.frames = 0 

1733 self.start_indices = [] 

1734 self.end_indices = [] 

1735 self.start_time = None 

1736 start_time = None 

1737 self._metadata = {} 

1738 self._locs = np.zeros((0, 2), dtype=int) 

1739 self._labels = np.zeros((0, 2), dtype=object) 

1740 if end_indices is not None: 

1741 self.filepath = Path(filepaths[0]) 

1742 self.file_paths = [Path(fp) for fp in filepaths] 

1743 self.audio_files = [None] * len(filepaths) 

1744 self.frames = end_indices[-1] 

1745 self.start_indices = [0] + list(end_indices[:-1]) 

1746 self.end_indices = end_indices 

1747 self.format = None 

1748 self.encoding = None 

1749 self.rate = rate 

1750 self.channels = channels 

1751 else: 

1752 for filepath in filepaths: 

1753 try: 

1754 a = AudioLoader(filepath, buffersize, backsize, verbose) 

1755 except Exception as e: 

1756 if verbose > 0: 

1757 print(e) 

1758 continue 

1759 # collect metadata: 

1760 md = a.metadata() 

1761 fmd = flatten_metadata(md, True) 

1762 add_metadata(self._metadata, fmd) 

1763 if self.filepath is None: 

1764 # first file: 

1765 self.filepath = a.filepath 

1766 self.format = a.format 

1767 self.encoding = a.encoding 

1768 self.rate = a.rate 

1769 self.channels = a.channels 

1770 self.start_time = get_datetime(md) 

1771 start_time = self.start_time 

1772 stime = self.start_time 

1773 else: 

1774 # check channels and rate: 

1775 error_str = None 

1776 if a.channels != self.channels: 

1777 error_str = f'number of channels differs: ' \ 

1778 f'{a.channels} in {a.filepath} versus ' \ 

1779 f'{self.channels} in {self.filepath}' 

1780 if a.rate != self.rate: 

1781 error_str = f'sampling rates differ: ' \ 

1782 f'{a.rate} in {a.filepath} versus ' \ 

1783 f'{self.rate} in {self.filepath}' 

1784 # check start time of recording: 

1785 stime = get_datetime(md) 

1786 if mode == 'strict' and (start_time is None or stime is None): 

1787 error_str = 'file does not contain a start time in its meta data' 

1788 if start_time is not None and stime is not None and \ 

1789 abs(start_time - stime) > timedelta(seconds=self._max_time_diff): 

1790 error_str = f'start time does not indicate continuous recording: ' \ 

1791 f'expected {start_time} instead of ' \ 

1792 f'{stime} in {a.filepath}' 

1793 if error_str is not None: 

1794 if verbose > 0: 

1795 print(error_str) 

1796 a.close() 

1797 del a 

1798 break 

1799 # markers: 

1800 locs, labels = a.markers() 

1801 locs[:,0] += self.frames 

1802 self._locs = np.vstack((self._locs, locs)) 

1803 self._labels = np.vstack((self._labels, labels)) 

1804 # indices: 

1805 self.start_indices.append(self.frames) 

1806 self.frames += a.frames 

1807 self.end_indices.append(self.frames) 

1808 if stime is not None: 

1809 start_time = stime + timedelta(seconds=a.frames/a.rate) 

1810 # add file to lists: 

1811 self.file_paths.append(a.filepath) 

1812 if len(self.open_files) < AudioLoader.max_open_files: 

1813 self.open_files.append(a) 

1814 else: 

1815 a.close() 

1816 if len(self.open_loaders) < AudioLoader.max_open_loaders: 

1817 self.audio_files.append(a) 

1818 self.open_loaders.append(a) 

1819 else: 

1820 a.close() 

1821 del a 

1822 self.audio_files.append(None) 

1823 if len(self.audio_files) == 0: 

1824 raise FileNotFoundError('input argument filepaths does not contain any valid audio file!') 

1825 # set startime from first file: 

1826 if self.start_time is not None: 

1827 set_starttime(self._metadata, self.start_time) 

1828 # setup infrastructure: 

1829 self.file_indices = self.start_indices 

1830 self.start_indices = np.array(self.start_indices) 

1831 self.end_indices = np.array(self.end_indices) 

1832 self.shape = (self.frames, self.channels) 

1833 self.bufferframes = int(buffersize*self.rate) 

1834 self.backframes = int(backsize*self.rate) 

1835 self.init_buffer() 

1836 self.close = self._close_multiple 

1837 self.load_audio_buffer = self._load_buffer_multiple 

1838 self._load_metadata = None 

1839 self._load_markers = None 

1840 return self 

1841 

1842 def _close_multiple(self): 

1843 """Close all the audio files. """ 

1844 self.open_files = [] 

1845 self.open_loaders = [] 

1846 if hasattr(self, 'audio_files'): 

1847 for a in self.audio_files: 

1848 if a is not None: 

1849 a.close() 

1850 self.audio_files = [] 

1851 self.filepath = None 

1852 self.file_paths = [] 

1853 self.file_indices = [] 

1854 self.start_indices = [] 

1855 self.end_indices = [] 

1856 self.ampl_min = -1.0 

1857 self.ampl_max = +1.0 

1858 del self.audio_files 

1859 del self.open_files 

1860 del self.open_loaders 

1861 del self.start_indices 

1862 del self.end_indices 

1863 

1864 def _load_buffer_multiple(self, r_offset, r_size, buffer): 

1865 """Load new data from the underlying files. 

1866 

1867 Parameters 

1868 ---------- 

1869 r_offset: int 

1870 First frame to be read from file. 

1871 r_size: int 

1872 Number of frames to be read from file. 

1873 buffer: ndarray 

1874 Buffer where to store the loaded data. 

1875 """ 

1876 offs = r_offset 

1877 size = r_size 

1878 boffs = 0 

1879 ai = np.searchsorted(self.end_indices, offs, side='right') 

1880 while size > 0: 

1881 if self.audio_files[ai] is None: 

1882 a = AudioLoader(self.file_paths[ai], 

1883 self.buffersize, self.backsize, 0) 

1884 self.audio_files[ai] = a 

1885 self.open_loaders.append(a) 

1886 self.open_files.append(a) 

1887 if len(self.open_files) > AudioLoader.max_open_files: 

1888 a0 = self.open_files.pop(0) 

1889 a0.close() 

1890 if len(self.open_loaders) > AudioLoader.max_open_loaders: 

1891 a0 = self.open_loaders.pop(0) 

1892 self.audio_files[self.audio_files.index(a0)] = None 

1893 a0.close() 

1894 del a0 

1895 self.collect_counter += 1 

1896 if self.collect_counter > AudioLoader.max_open_loaders//2: 

1897 gc.collect() 

1898 self.collect_counter = 0 

1899 else: 

1900 self.open_loaders.pop(self.open_loaders.index(self.audio_files[ai])) 

1901 self.open_loaders.append(self.audio_files[ai]) 

1902 ai0 = offs - self.start_indices[ai] 

1903 ai1 = offs + size 

1904 if ai1 > self.end_indices[ai]: 

1905 ai1 = self.end_indices[ai] 

1906 ai1 -= self.start_indices[ai] 

1907 n = ai1 - ai0 

1908 self.audio_files[ai].load_audio_buffer(ai0, n, 

1909 buffer[boffs:boffs + n,:]) 

1910 if self.audio_files[ai] in self.open_files: 

1911 self.open_files.pop(self.open_files.index(self.audio_files[ai])) 

1912 self.open_files.append(self.audio_files[ai]) 

1913 if len(self.open_files) > AudioLoader.max_open_files: 

1914 self.open_files[0].close() 

1915 self.open_files.pop(0) 

1916 boffs += n 

1917 offs += n 

1918 size -= n 

1919 ai += 1 

1920 

1921 

1922 def open(self, filepath, buffersize=10.0, backsize=0.0, 

1923 verbose=0, **kwargs): 

1924 """Open audio file for reading. 

1925 

1926 Parameters 

1927 ---------- 

1928 filepath: str or Path or list of str or Path 

1929 Path of the file or list of many file paths that should be 

1930 made accessible as a single array. 

1931 buffersize: float 

1932 Size of internal buffer in seconds. 

1933 backsize: float 

1934 Part of the buffer to be loaded before the requested start index in seconds. 

1935 verbose: int 

1936 If larger than zero show detailed error/warning messages. 

1937 **kwargs: dict 

1938 Further keyword arguments that are passed on to the  

1939 specific opening functions. Only used by open_multiple() so far. 

1940 

1941 Raises 

1942 ------ 

1943 FileNotFoundError 

1944 `filepath` is not an existing file. 

1945 EOFError 

1946 File size of `filepath` is zero. 

1947 IOError 

1948 Failed to load data. 

1949 

1950 """ 

1951 self.buffer = np.array([]) 

1952 self.rate = 0.0 

1953 if isinstance(filepath, (list, tuple, np.ndarray)): 

1954 if len(filepath) > 1: 

1955 self.open_multiple(filepath, buffersize, backsize, 

1956 verbose - 1, **kwargs) 

1957 if len(self.file_paths) > 1: 

1958 return self 

1959 filepath = self.file_paths[0] 

1960 self.close() 

1961 else: 

1962 filepath = filepath[0] 

1963 filepath = Path(filepath) 

1964 if not filepath.is_file(): 

1965 raise FileNotFoundError(f'file "{filepath}" not found') 

1966 if filepath.stat().st_size <= 0: 

1967 raise EOFError(f'file "{filepath}" is empty (size=0)!') 

1968 # list of implemented open functions: 

1969 audio_open_funcs = ( 

1970 ('soundfile', self.open_soundfile), 

1971 ('wave', self.open_wave), 

1972 ('wavefile', self.open_wavefile), 

1973 ('wavpack', self.open_wavpack), 

1974 ('ewave', self.open_ewave), 

1975 ('audioread', self.open_audioread), 

1976 ) 

1977 # open an audio file by trying various modules: 

1978 not_installed = [] 

1979 errors = [f'failed to load data from file "{filepath}":'] 

1980 for lib, open_file in audio_open_funcs: 

1981 if not audio_modules[lib]: 

1982 if verbose > 1: 

1983 print(f'unable to load data from file "{filepath}" using {lib} module: module not available') 

1984 not_installed.append(lib) 

1985 continue 

1986 try: 

1987 open_file(filepath, buffersize, backsize, 

1988 verbose - 1, **kwargs) 

1989 if self.frames > 0: 

1990 if verbose > 0: 

1991 print(f'opened audio file "{filepath}" using {lib}') 

1992 if verbose > 1: 

1993 if self.format is not None: 

1994 print(f' format : {self.format}') 

1995 if self.encoding is not None: 

1996 print(f' encoding : {self.encoding}') 

1997 print(f' sampling rate: {self.rate} Hz') 

1998 print(f' channels : {self.channels}') 

1999 print(f' frames : {self.frames}') 

2000 return self 

2001 except Exception as e: 

2002 errors.append(f' {lib} failed: {str(e)}') 

2003 if verbose > 1: 

2004 print(errors[-1]) 

2005 if len(not_installed) > 0: 

2006 errors.append('\n You may need to install one of the ' + \ 

2007 ', '.join(not_installed) + ' packages.') 

2008 raise IOError('\n'.join(errors)) 

2009 return self 

2010 

2011 

2012def demo(file_path, plot): 

2013 """Demo of the audioloader functions. 

2014 

2015 Parameters 

2016 ---------- 

2017 file_path: str 

2018 File path of an audio file. 

2019 plot: bool 

2020 If True also plot the loaded data. 

2021 """ 

2022 print('') 

2023 print("try load_audio:") 

2024 full_data, rate = load_audio(file_path, 1) 

2025 if plot: 

2026 plt.plot(np.arange(len(full_data))/rate, full_data[:,0]) 

2027 plt.show() 

2028 

2029 if audio_modules['soundfile'] and audio_modules['audioread']: 

2030 print('') 

2031 print("cross check:") 

2032 data1, rate1 = load_soundfile(file_path) 

2033 data2, rate2 = load_audioread(file_path) 

2034 n = min((len(data1), len(data2))) 

2035 print(f"rms difference is {np.std(data1[:n]-data2[:n])}") 

2036 if plot: 

2037 plt.plot(np.arange(len(data1))/rate1, data1[:,0]) 

2038 plt.plot(np.arange(len(data2))/rate2, data2[:,0]) 

2039 plt.show() 

2040 

2041 print('') 

2042 print("try AudioLoader:") 

2043 with AudioLoader(file_path, 4.0, 1.0, verbose=1) as data: 

2044 print(f'samplerate: {data.rate:0f}Hz') 

2045 print(f'channels: {data.channels} {data.shape[1]}') 

2046 print(f'frames: {len(data)} {data.shape[0]}') 

2047 nframes = int(1.5*data.rate) 

2048 # check access: 

2049 print('check random single frame access') 

2050 for inx in np.random.randint(0, len(data), 1000): 

2051 if np.any(np.abs(full_data[inx] - data[inx]) > 2.0**(-14)): 

2052 print('single random frame access failed', inx, full_data[inx], data[inx]) 

2053 print('check random frame slice access') 

2054 for inx in np.random.randint(0, len(data)-nframes, 1000): 

2055 if np.any(np.abs(full_data[inx:inx+nframes] - data[inx:inx+nframes]) > 2.0**(-14)): 

2056 print('random frame slice access failed', inx) 

2057 print('check frame slice access forward') 

2058 for inx in range(0, len(data)-nframes, 10): 

2059 if np.any(np.abs(full_data[inx:inx+nframes] - data[inx:inx+nframes]) > 2.0**(-14)): 

2060 print('frame slice access forward failed', inx) 

2061 print('check frame slice access backward') 

2062 for inx in range(len(data)-nframes, 0, -10): 

2063 if np.any(np.abs(full_data[inx:inx+nframes] - data[inx:inx+nframes]) > 2.0**(-14)): 

2064 print('frame slice access backward failed', inx) 

2065 # forward: 

2066 for i in range(0, len(data), nframes): 

2067 print(f'forward {i}-{i+nframes}') 

2068 x = data[i:i+nframes,0] 

2069 if plot: 

2070 plt.plot((i+np.arange(len(x)))/rate, x) 

2071 plt.show() 

2072 # and backwards: 

2073 for i in reversed(range(0, len(data), nframes)): 

2074 print(f'backward {i}-{i+nframes}') 

2075 x = data[i:i+nframes,0] 

2076 if plot: 

2077 plt.plot((i+np.arange(len(x)))/rate, x) 

2078 plt.show() 

2079 

2080 

2081def main(*args): 

2082 """Call demo with command line arguments. 

2083 

2084 Parameters 

2085 ---------- 

2086 args: list of str 

2087 Command line arguments as provided by sys.argv[1:] 

2088 """ 

2089 print("Checking audioloader module ...") 

2090 

2091 help = False 

2092 plot = False 

2093 file_path = None 

2094 mod = False 

2095 for arg in args: 

2096 if mod: 

2097 if not select_module(arg): 

2098 print(f'can not select module {arg} that is not installed') 

2099 return 

2100 mod = False 

2101 elif arg == '-h': 

2102 help = True 

2103 break 

2104 elif arg == '-p': 

2105 plot = True 

2106 elif arg == '-m': 

2107 mod = True 

2108 else: 

2109 file_path = arg 

2110 break 

2111 

2112 if help: 

2113 print('') 

2114 print('Usage:') 

2115 print(' python -m src.audioio.audioloader [-m <module>] [-p] <audio/file.wav>') 

2116 print(' -m: audio module to be used') 

2117 print(' -p: plot loaded data') 

2118 return 

2119 

2120 if plot: 

2121 import matplotlib.pyplot as plt 

2122 

2123 demo(file_path, plot) 

2124 

2125 

2126if __name__ == "__main__": 

2127 main(*sys.argv[1:])