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

948 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-01 19:42 +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 .audiotools import unwrap 

40 

41 

42def load_wave(filepath): 

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

44  

45 Documentation 

46 ------------- 

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

48 

49 Parameters 

50 ---------- 

51 filepath: str or Path 

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

53 

54 Returns 

55 ------- 

56 data: ndarray 

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

58 rate: float 

59 The sampling rate of the data in Hertz. 

60 

61 Raises 

62 ------ 

63 ImportError 

64 The wave module is not installed 

65 * 

66 Loading of the data failed 

67 """ 

68 if not audio_modules['wave']: 

69 raise ImportError 

70 

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

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

73 buffer = wf.readframes(nframes) 

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

75 if sampwidth == 1: 

76 dtype = 'u1' 

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

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

79 else: 

80 dtype = f'i{sampwidth}' 

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

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

83 wf.close() 

84 return data, float(rate) 

85 

86 

87def load_ewave(filepath): 

88 """Load wav file using ewave module. 

89 

90 Documentation 

91 ------------- 

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

93 

94 Parameters 

95 ---------- 

96 filepath: str or Path 

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

98 

99 Returns 

100 ------- 

101 data: ndarray 

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

103 rate: float 

104 The sampling rate of the data in Hertz. 

105 

106 Raises 

107 ------ 

108 ImportError 

109 The ewave module is not installed 

110 * 

111 Loading of the data failed 

112 """ 

113 if not audio_modules['ewave']: 

114 raise ImportError 

115 

116 data = np.array([]) 

117 rate = 0.0 

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

119 rate = wf.sampling_rate 

120 buffer = wf.read() 

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

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

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

124 return data, float(rate) 

125 

126 

127def load_wavfile(filepath): 

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

129 

130 Documentation 

131 ------------- 

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

133 Does not support blocked read. 

134  

135 Parameters 

136 ---------- 

137 filepath: str or Path 

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

139 

140 Returns 

141 ------- 

142 data: ndarray 

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

144 rate: float 

145 The sampling rate of the data in Hertz. 

146 

147 Raises 

148 ------ 

149 ImportError 

150 The scipy.io module is not installed 

151 * 

152 Loading of the data failed 

153 """ 

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

155 raise ImportError 

156 

157 warnings.filterwarnings("ignore") 

158 rate, data = wavfile.read(filepath) 

159 warnings.filterwarnings("always") 

160 if data.dtype == np.uint8: 

161 data = data / 128.0 - 1.0 

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

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

164 else: 

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

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

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

168 return data, float(rate) 

169 

170 

171def load_soundfile(filepath): 

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

173 

174 Documentation 

175 ------------- 

176 http://pysoundfile.readthedocs.org 

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

178 

179 Parameters 

180 ---------- 

181 filepath: str or Path 

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

183 

184 Returns 

185 ------- 

186 data: ndarray 

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

188 rate: float 

189 The sampling rate of the data in Hertz. 

190 

191 Raises 

192 ------ 

193 ImportError 

194 The soundfile module is not installed. 

195 * 

196 Loading of the data failed. 

197 """ 

198 if not audio_modules['soundfile']: 

199 raise ImportError 

200 

201 data = np.array([]) 

202 rate = 0.0 

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

204 rate = sf.samplerate 

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

206 return data, float(rate) 

207 

208 

209def load_wavefile(filepath): 

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

211 

212 Documentation 

213 ------------- 

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

215 

216 Parameters 

217 ---------- 

218 filepath: str or Path 

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

220 

221 Returns 

222 ------- 

223 data: ndarray 

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

225 rate: float 

226 The sampling rate of the data in Hertz. 

227 

228 Raises 

229 ------ 

230 ImportError 

231 The wavefile module is not installed. 

232 * 

233 Loading of the data failed. 

234 """ 

235 if not audio_modules['wavefile']: 

236 raise ImportError 

237 

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

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

240 

241 

242def load_audioread(filepath): 

243 """Load audio file using audioread. 

244 

245 Documentation 

246 ------------- 

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

248 

249 Parameters 

250 ---------- 

251 filepath: str or Path 

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

253 

254 Returns 

255 ------- 

256 data: ndarray 

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

258 rate: float 

259 The sampling rate of the data in Hertz. 

260 

261 Raises 

262 ------ 

263 ImportError 

264 The audioread module is not installed. 

265 * 

266 Loading of the data failed. 

267 """ 

268 if not audio_modules['audioread']: 

269 raise ImportError 

270 

271 data = np.array([]) 

272 rate = 0.0 

273 with audioread.audio_open(filepath) as af: 

274 rate = af.samplerate 

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

276 dtype="<i2") 

277 index = 0 

278 for buffer in af: 

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

280 n = fulldata.shape[0] 

281 if index + n > len(data): 

282 n = len(fulldata) - index 

283 if n <= 0: 

284 break 

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

286 index += n 

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

288 

289 

290wavpack_wrapper = 0x04 

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

292 

293wavpack_float = 0x08 

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

295 

296 

297def open_wavpack_file(filepath, flags=0): 

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

299 

300 Parameters 

301 ---------- 

302 filepath: str or Path 

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

304 flags: int 

305 Flags passed on to WavpackOpenFileInput(). 

306 

307 Returns 

308 ------- 

309 wpc: ctypes.c_void_p 

310 Handle of the open WavPack file. 

311 

312 Raises 

313 ------ 

314 ImportError 

315 The wavpack library is not installed 

316 IOError 

317 Opening the file failed 

318 """ 

319 if not audio_modules['wavpack']: 

320 raise ImportError 

321 

322 error = ctypes.create_string_buffer(160) 

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

324 if not wpc: 

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

326 return ctypes.c_void_p(wpc) 

327 

328 

329def unpack_wavpack(wpc, nframes, channels): 

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

331 

332 Parameters 

333 ---------- 

334 wpc: ctypes.c_void_p 

335 Handle of an open WavPack file. 

336 nframes: int 

337 Number of frames to be read. 

338 channels: int 

339 Number of channels of the file. 

340 

341 Returns 

342 ------- 

343 buffer: 2-D ndarray of int32 

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

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

346 """ 

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

348 n = 0 

349 while n < nframes: 

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

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

352 if m == 0: # end of file 

353 break 

354 n += m 

355 return buffer[:n,:] 

356 

357 

358def riff_wavpack(filepath): 

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

360 

361 The wavpack program stores everything of the file it compresses 

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

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

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

365 

366 Parameters 

367 ---------- 

368 filepath: str or Path 

369 The full path and name of the WavPack file. 

370 

371 Returns 

372 ------- 

373 sf: BytesIO or None 

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

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

376 """ 

377 try: 

378 wpc = open_wavpack_file(filepath, wavpack_wrapper) 

379 except (ImportError, IOError, TypeError): 

380 return None 

381 nheader = wavpack.WavpackGetWrapperBytes(wpc) 

382 frames = wavpack.WavpackGetNumSamples64(wpc) 

383 if frames > 1: 

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

385 # ran into the end of the file: 

386 wavpack.WavpackSeekSample64(wpc, frames - 1) 

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

388 n = wavpack.WavpackGetWrapperBytes(wpc) 

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

390 wavpack.WavpackCloseFile(wpc) 

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

392 return None 

393 header = riff[:nheader] 

394 trailer = riff[nheader:] 

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

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

397 riff = header + trailer 

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

399 return BytesIO(riff) 

400 

401 

402def load_wavpack(filepath): 

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

404 

405 Documentation 

406 ------------- 

407 https://www.wavpack.com 

408 

409 Parameters 

410 ---------- 

411 filepath: str or Path 

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

413 

414 Returns 

415 ------- 

416 data: ndarray 

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

418 rate: float 

419 The sampling rate of the data in Hertz. 

420 

421 Raises 

422 ------ 

423 ImportError 

424 The wavpack library is not installed 

425 * 

426 Loading of the data failed 

427 """ 

428 if not audio_modules['wavpack']: 

429 raise ImportError 

430 

431 wpc = open_wavpack_file(filepath) 

432 rate = wavpack.WavpackGetSampleRate(wpc) 

433 channels = wavpack.WavpackGetNumChannels(wpc) 

434 frames = wavpack.WavpackGetNumSamples64(wpc) 

435 buffer = unpack_wavpack(wpc, frames, channels) 

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

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

438 else: 

439 bits = wavpack.WavpackGetBitsPerSample(wpc) 

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

441 wavpack.WavpackCloseFile(wpc) 

442 return data, float(rate) 

443 

444 

445audio_loader_funcs = ( 

446 ('soundfile', load_soundfile), 

447 ('wave', load_wave), 

448 ('wavefile', load_wavefile), 

449 ('wavpack', load_wavpack), 

450 ('ewave', load_ewave), 

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

452 ('audioread', load_audioread), 

453 ) 

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

455 

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

457load() function. 

458 

459""" 

460 

461 

462def load_audio(filepath, verbose=0): 

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

464  

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

466 

467 Parameters 

468 ---------- 

469 filepath: str or Path 

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

471 verbose: int 

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

473 

474 Returns 

475 ------- 

476 data: ndarray 

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

478 First dimension is time, second is channel. 

479 Data values range maximally between -1 and 1. 

480 rate: float 

481 The sampling rate of the data in Hertz. 

482 

483 Raises 

484 ------ 

485 FileNotFoundError 

486 `filepath` is not an existing file. 

487 EOFError 

488 File size of `filepath` is zero. 

489 IOError 

490 Failed to load data. 

491 

492 Examples 

493 -------- 

494 ``` 

495 import matplotlib.pyplot as plt 

496 from audioio import load_audio 

497  

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

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

500 plt.show() 

501 ``` 

502 """ 

503 # check values: 

504 filepath = Path(filepath) 

505 if not filepath.is_file: 

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

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

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

509 

510 # load an audio file by trying various modules: 

511 not_installed = [] 

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

513 for lib, load_file in audio_loader_funcs: 

514 if not audio_modules[lib]: 

515 if verbose > 1: 

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

517 not_installed.append(lib) 

518 continue 

519 try: 

520 data, rate = load_file(filepath) 

521 if len(data) > 0: 

522 if verbose > 0: 

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

524 if verbose > 1: 

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

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

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

528 return data, rate 

529 except Exception as e: 

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

531 if verbose > 1: 

532 print(errors[-1]) 

533 if len(not_installed) > 0: 

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

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

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

537 return np.zeros(0), 0.0 

538 

539 

540def metadata(filepath, store_empty=False): 

541 """Read metadata of an audio file. 

542 

543 Parameters 

544 ---------- 

545 filepath: str or file handle 

546 The audio file from which to read metadata. 

547 store_empty: bool 

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

549 

550 Returns 

551 ------- 

552 meta_data: nested dict 

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

554 dictionaries are always strings. If the corresponding values 

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

556 metadata contained in the dictionary. All other types of 

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

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

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

560 available functions to work with such metadata. 

561  

562 Raises 

563 ------ 

564 ValueError 

565 Not a RIFF file. 

566 

567 Examples 

568 -------- 

569 ``` 

570 from audioio import metadata, print_metadata 

571 md = metadata('data.wav') 

572 print_metadata(md) 

573 ``` 

574 

575 """ 

576 try: 

577 return metadata_riff(filepath, store_empty) 

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

579 sf = riff_wavpack(filepath) 

580 if sf is None: 

581 return {} 

582 return metadata_riff(sf, store_empty) 

583 

584 

585def markers(filepath): 

586 """ Read markers of an audio file. 

587 

588 See `audioio.audiomarkers` module for available functions 

589 to work with markers. 

590 

591 Parameters 

592 ---------- 

593 filepath: str or file handle 

594 The audio file. 

595 

596 Returns 

597 ------- 

598 locs: 2-D ndarray of int 

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

600 for each marker (rows). 

601 labels: 2-D ndarray of string objects 

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

603 for each marker (rows). 

604 

605 Raises 

606 ------ 

607 ValueError 

608 Not a RIFF file. 

609  

610 Examples 

611 -------- 

612 ``` 

613 from audioio import markers, print_markers 

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

615 print_markers(locs, labels) 

616 ``` 

617 """ 

618 try: 

619 return markers_riff(filepath) 

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

621 sf = riff_wavpack(filepath) 

622 if sf is None: 

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

624 return markers_riff(sf) 

625 

626 

627class AudioLoader(BufferedArray): 

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

629  

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

631 sequential audio files that do not fit into memory. 

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

633 ``` 

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

635 x = data[10000:20000,0] 

636 ``` 

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

638 

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

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

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

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

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

644 `BufferedArray` class. 

645 

646 Reading sequentially through the file is always possible. Some 

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

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

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

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

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

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

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

654 the file from the beginning. 

655 

656 Usage 

657 ----- 

658 With context management: 

659 ``` 

660 import audioio as aio 

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

662 # do something with the content of the file: 

663 x = data[0:10000] 

664 y = data[10000:20000] 

665 z = x + y 

666 ``` 

667 

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

669 ``` 

670 data = aio.AudioLoader() 

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

672 # do something ... 

673 ``` 

674 

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

676 ``` 

677 from scipy.signal import spectrogram 

678 nfft = 2048 

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

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

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

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

683 ``` 

684 

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

686 ``` 

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

688 for x in data: 

689 print(x) 

690 ``` 

691  

692 Traditional open and close: 

693 ``` 

694 data = aio.AudioLoader(filepath, 60.0) 

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

696 data.close() 

697 ``` 

698  

699 this is the same as: 

700 ``` 

701 data = aio.AudioLoader() 

702 data.open(filepath, 60.0) 

703 ... 

704 ``` 

705 

706 Classes inheriting AudioLoader just need to implement 

707 ``` 

708 self.load_audio_buffer(offset, nsamples, pbuffer) 

709 ``` 

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

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

712 

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

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

715 

716 For loading metadata and markers, implement the functions 

717 ``` 

718 self._load_metadata(filepath, **kwargs) 

719 self._load_markers(filepath) 

720 ``` 

721  

722 Parameters 

723 ---------- 

724 filepath: str or Path or list of str of list of Path 

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

726 made accessible as a single array. 

727 buffersize: float 

728 Size of internal buffer in seconds. 

729 backsize: float 

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

731 verbose: int 

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

733 store_empty: bool 

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

735 meta_kwargs: dict 

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

737 function. For audio data the only recognized key is 

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

739 **kwargs: dict 

740 Further keyword arguments that are passed on to the  

741 specific open() functions. 

742 

743 Attributes 

744 ---------- 

745 filepath: Path 

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

747 file_paths: list of Path 

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

749 as a single array. 

750 file_indices: list of int 

751 For each file the index of its first sample. 

752 rate: float 

753 The sampling rate of the data in seconds. 

754 channels: int 

755 The number of channels. 

756 frames: int 

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

758 format: str or None 

759 Format of the audio file. 

760 encoding: str or None 

761 Encoding/subtype of the audio file. 

762 shape: tuple 

763 Frames and channels of the data. 

764 ndim: int 

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

766 offset: int 

767 Index of first frame in the current buffer. 

768 buffer: ndarray of floats 

769 The curently available data from the file. 

770 ampl_min: float 

771 Minimum amplitude the file format supports. 

772 Always -1.0 for audio data. 

773 ampl_max: float 

774 Maximum amplitude the file format supports. 

775 Always +1.0 for audio data. 

776 

777 Methods 

778 ------- 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

796 

797 """ 

798 

799 max_open_files = 5 

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

801 

802 max_open_loaders = 10 

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

804 

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

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

807 super().__init__(verbose=verbose) 

808 self.format = None 

809 self.encoding = None 

810 self._metadata = None 

811 self._locs = None 

812 self._labels = None 

813 self._load_metadata = metadata 

814 self._load_markers = markers 

815 self._metadata_kwargs = meta_kwargs 

816 self.filepath = None 

817 self.file_paths = None 

818 self.file_indices = [] 

819 self._max_time_diff = 1 

820 self.sf = None 

821 self.close = self._close 

822 self.load_buffer = self._load_buffer_unwrap 

823 self.ampl_min = -1.0 

824 self.ampl_max = +1.0 

825 self.unwrap = False 

826 self.unwrap_thresh = 0.0 

827 self.unwrap_clips = False 

828 self.unwrap_ampl = 1.0 

829 self.unwrap_downscale = True 

830 if filepath is not None: 

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

832 

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

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

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

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

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

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

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

840 """ Map numpy dtypes to encodings. 

841 """ 

842 

843 def _close(self): 

844 pass 

845 

846 def __del__(self): 

847 self.close() 

848 

849 def file_start_times(self): 

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

851  

852 Returns 

853 ------- 

854 times: array of float 

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

856 in seconds. 

857 """ 

858 times = [] 

859 for idx in self.file_indices: 

860 times.append(idx/self.rate) 

861 return np.array(times) 

862 

863 def get_file_index(self, frame): 

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

865 

866 Parameters 

867 ---------- 

868 frame: int 

869 Index of frame. 

870  

871 Returns 

872 ------- 

873 filepath: Path 

874 Path of file that contains the frame. 

875 index: int 

876 Index of the frame relative to the first frame 

877 in the containing file. 

878  

879 Raises 

880 ------ 

881 ValueError 

882 Invalid frame index. 

883 """ 

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

885 raise ValueError('invalid frame') 

886 fname = self.file_paths[0] 

887 index = self.file_indices[0] 

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

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

890 fname = self.file_paths[i] 

891 index = self.file_indices[i] 

892 break 

893 return fname, frame - index 

894 

895 def basename(self, path=None): 

896 """ Base name of the audio data. 

897 

898 Parameters 

899 ---------- 

900 path: str or Path or None 

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

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

903 

904 Returns 

905 ------- 

906 s: str 

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

908 

909 """ 

910 if path is None: 

911 path = self.filepath 

912 return Path(path).stem 

913 

914 def format_dict(self): 

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

916 

917 Returns 

918 ------- 

919 fmt: dict 

920 Dictionary with filepath, format, encoding, samplingrate, 

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

922 

923 """ 

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

925 if self.format is not None: 

926 fmt['format'] = self.format 

927 if self.encoding is not None: 

928 fmt['encoding'] = self.encoding 

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

930 channels=self.channels, 

931 frames=self.frames, 

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

933 return fmt 

934 

935 def metadata(self): 

936 """Metadata of the audio file. 

937 

938 Parameters 

939 ---------- 

940 store_empty: bool 

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

942 

943 Returns 

944 ------- 

945 meta_data: nested dict 

946 

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

948 dictionaries are always strings. If the corresponding 

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

950 of the metadata contained in the dictionary. All other 

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

952 particular they are strings. But other types like for 

953 example ints or floats are also allowed. See 

954 `audioio.audiometadata` module for available functions to 

955 work with such metadata. 

956 

957 """ 

958 if self._metadata is None: 

959 if self._load_metadata is None: 

960 self._metadata = {} 

961 else: 

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

963 **self._metadata_kwargs) 

964 return self._metadata 

965 

966 def markers(self): 

967 """Read markers of the audio file. 

968 

969 See `audioio.audiomarkers` module for available functions 

970 to work with markers. 

971 

972 Returns 

973 ------- 

974 locs: 2-D ndarray of int 

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

976 for each marker (rows). 

977 labels: 2-D ndarray of str objects 

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

979 for each marker (rows). 

980 """ 

981 if self._locs is None: 

982 if self._load_markers is None: 

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

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

985 else: 

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

987 return self._locs, self._labels 

988 

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

990 """Set parameters for unwrapping clipped data. 

991 

992 See unwrap() function from the audioio package. 

993 

994 Parameters 

995 ---------- 

996 thresh: float 

997 Threshold for detecting wrapped data relative to self.unwrap_ampl 

998 which is initially set to self.ampl_max. 

999 If zero, do not unwrap. 

1000 clips: bool 

1001 If True, then clip the unwrapped data properly. 

1002 Otherwise, unwrap the data and double the 

1003 minimum and maximum data range 

1004 (self.ampl_min and self.ampl_max). 

1005 down_scale: bool 

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

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

1008 unit: str 

1009 Unit of the data. 

1010 """ 

1011 self.unwrap_ampl = self.ampl_max 

1012 self.unwrap_thresh = thresh 

1013 self.unwrap_clips = clips 

1014 self.unwrap_down_scale = down_scale 

1015 self.unwrap = thresh > 1e-3 

1016 if self.unwrap: 

1017 if self.unwrap_clips: 

1018 add_unwrap(self.metadata(), 

1019 self.unwrap_thresh*self.unwrap_ampl, 

1020 self.unwrap_ampl, unit) 

1021 elif down_scale: 

1022 update_gain(self.metadata(), 0.5) 

1023 add_unwrap(self.metadata(), 

1024 0.5*self.unwrap_thresh*self.unwrap_ampl, 

1025 0.0, unit) 

1026 else: 

1027 self.ampl_min *= 2 

1028 self.ampl_max *= 2 

1029 add_unwrap(self.metadata(), 

1030 self.unwrap_thresh*self.unwrap_ampl, 

1031 0.0, unit) 

1032 

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

1034 """Load new data and unwrap it. 

1035 

1036 Parameters 

1037 ---------- 

1038 r_offset: int 

1039 First frame to be read from file. 

1040 r_size: int 

1041 Number of frames to be read from file. 

1042 pbuffer: ndarray 

1043 Buffer where to store the loaded data. 

1044 """ 

1045 self.load_audio_buffer(r_offset, r_size, pbuffer) 

1046 if self.unwrap: 

1047 # TODO: handle edge effects! 

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

1049 if self.unwrap_clips: 

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

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

1052 elif self.unwrap_down_scale: 

1053 pbuffer *= 0.5 

1054 

1055 def set_time_delta(time_delta): 

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

1057 

1058 Parameters 

1059 ---------- 

1060 time_delta: int 

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

1062 to differ from the end of the previous file. 

1063 Default is one second. 

1064 """ 

1065 self._max_time_diff = time_delta 

1066 

1067 # wave interface:  

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

1069 verbose=0): 

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

1071 

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

1073 

1074 Parameters 

1075 ---------- 

1076 filepath: str or Path 

1077 Name of the file. 

1078 buffersize: float 

1079 Size of internal buffer in seconds. 

1080 backsize: float 

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

1082 verbose: int 

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

1084 

1085 Raises 

1086 ------ 

1087 ImportError 

1088 The wave module is not installed 

1089 """ 

1090 self.verbose = verbose 

1091 if self.verbose > 0: 

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

1093 if not audio_modules['wave']: 

1094 self.rate = 0.0 

1095 self.channels = 0 

1096 self.frames = 0 

1097 self.size = 0 

1098 self.shape = (0, 0) 

1099 self.offset = 0 

1100 raise ImportError 

1101 if self.sf is not None: 

1102 self._close_wave() 

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

1104 self.filepath = Path(filepath) 

1105 self.file_paths = [self.filepath] 

1106 self.file_indices = [0] 

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

1108 self.format = 'WAV' 

1109 sampwidth = self.sf.getsampwidth() 

1110 if sampwidth == 1: 

1111 self.dtype = 'u1' 

1112 self.encoding = 'PCM_U8' 

1113 else: 

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

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

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

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

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

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

1120 self.size = self.frames * self.channels 

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

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

1123 self.init_buffer() 

1124 self.close = self._close_wave 

1125 self.load_audio_buffer = self._load_buffer_wave 

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

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

1128 self.sf.readframes(1) 

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

1130 self.sf.setpos(self.p0) 

1131 return self 

1132 

1133 def _close_wave(self): 

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

1135 if self.sf is not None: 

1136 self.sf.close() 

1137 self.sf = None 

1138 

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

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

1141 

1142 Parameters 

1143 ---------- 

1144 r_offset: int 

1145 First frame to be read from file. 

1146 r_size: int 

1147 Number of frames to be read from file. 

1148 buffer: ndarray 

1149 Buffer where to store the loaded data. 

1150 """ 

1151 if self.sf is None: 

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

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

1154 fbuffer = self.sf.readframes(r_size) 

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

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

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

1158 else: 

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

1160 

1161 

1162 # ewave interface:  

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

1164 verbose=0): 

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

1166 

1167 Parameters 

1168 ---------- 

1169 filepath: str or Path 

1170 Name of the file. 

1171 buffersize: float 

1172 Size of internal buffer in seconds. 

1173 backsize: float 

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

1175 verbose: int 

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

1177 

1178 Raises 

1179 ------ 

1180 ImportError 

1181 The ewave module is not installed. 

1182 """ 

1183 self.verbose = verbose 

1184 if self.verbose > 0: 

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

1186 if not audio_modules['ewave']: 

1187 self.rate = 0.0 

1188 self.channels = 0 

1189 self.frames = 0 

1190 self.shape = (0, 0) 

1191 self.size = 0 

1192 self.offset = 0 

1193 raise ImportError 

1194 if self.sf is not None: 

1195 self._close_ewave() 

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

1197 self.filepath = Path(filepath) 

1198 self.file_paths = [self.filepath] 

1199 self.file_indices = [0] 

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

1201 self.channels = self.sf.nchannels 

1202 self.frames = self.sf.nframes 

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

1204 self.size = self.frames * self.channels 

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

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

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

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

1209 self.init_buffer() 

1210 self.close = self._close_ewave 

1211 self.load_audio_buffer = self._load_buffer_ewave 

1212 return self 

1213 

1214 def _close_ewave(self): 

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

1216 if self.sf is not None: 

1217 del self.sf 

1218 self.sf = None 

1219 

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

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

1222 

1223 Parameters 

1224 ---------- 

1225 r_offset: int 

1226 First frame to be read from file. 

1227 r_size: int 

1228 Number of frames to be read from file. 

1229 buffer: ndarray 

1230 Buffer where to store the loaded data. 

1231 """ 

1232 if self.sf is None: 

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

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

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

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

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

1238 buffer[:,:] = fbuffer 

1239 

1240 

1241 # soundfile interface:  

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

1243 verbose=0): 

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

1245 

1246 Parameters 

1247 ---------- 

1248 filepath: str or Path 

1249 Name of the file. 

1250 bufferframes: float 

1251 Size of internal buffer in seconds. 

1252 backsize: float 

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

1254 verbose: int 

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

1256 

1257 Raises 

1258 ------ 

1259 ImportError 

1260 The SoundFile module is not installed 

1261 """ 

1262 self.verbose = verbose 

1263 if self.verbose > 0: 

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

1265 if not audio_modules['soundfile']: 

1266 self.rate = 0.0 

1267 self.channels = 0 

1268 self.frames = 0 

1269 self.shape = (0, 0) 

1270 self.size = 0 

1271 self.offset = 0 

1272 raise ImportError 

1273 if self.sf is not None: 

1274 self._close_soundfile() 

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

1276 self.filepath = Path(filepath) 

1277 self.file_paths = [self.filepath] 

1278 self.file_indices = [0] 

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

1280 self.channels = self.sf.channels 

1281 self.frames = 0 

1282 self.size = 0 

1283 if self.sf.seekable(): 

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

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

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

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

1288 self.size = self.frames * self.channels 

1289 self.format = self.sf.format 

1290 self.encoding = self.sf.subtype 

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

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

1293 self.init_buffer() 

1294 self.close = self._close_soundfile 

1295 self.load_audio_buffer = self._load_buffer_soundfile 

1296 return self 

1297 

1298 def _close_soundfile(self): 

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

1300 if self.sf is not None: 

1301 self.sf.close() 

1302 self.sf = None 

1303 

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

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

1306 

1307 Parameters 

1308 ---------- 

1309 r_offset: int 

1310 First frame to be read from file. 

1311 r_size: int 

1312 Number of frames to be read from file. 

1313 buffer: ndarray 

1314 Buffer where to store the loaded data. 

1315 """ 

1316 if self.sf is None: 

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

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

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

1320 

1321 

1322 # wavefile interface:  

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

1324 verbose=0): 

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

1326 

1327 Parameters 

1328 ---------- 

1329 filepath: str or Path 

1330 Name of the file. 

1331 bufferframes: float 

1332 Size of internal buffer in seconds. 

1333 backsize: float 

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

1335 verbose: int 

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

1337 

1338 Raises 

1339 ------ 

1340 ImportError 

1341 The wavefile module is not installed 

1342 """ 

1343 self.verbose = verbose 

1344 if self.verbose > 0: 

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

1346 if not audio_modules['wavefile']: 

1347 self.rate = 0.0 

1348 self.channels = 0 

1349 self.frames = 0 

1350 self.shape = (0, 0) 

1351 self.size = 0 

1352 self.offset = 0 

1353 raise ImportError 

1354 if self.sf is not None: 

1355 self._close_wavefile() 

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

1357 self.filepath = Path(filepath) 

1358 self.file_paths = [self.filepath] 

1359 self.file_indices = [0] 

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

1361 self.channels = self.sf.channels 

1362 self.frames = self.sf.frames 

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

1364 self.size = self.frames * self.channels 

1365 # get format and encoding: 

1366 for attr in dir(wavefile.Format): 

1367 v = getattr(wavefile.Format, attr) 

1368 if isinstance(v, int): 

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

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

1371 self.format = attr 

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

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

1374 self.encoding = attr 

1375 # init buffer: 

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

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

1378 self.init_buffer() 

1379 self.close = self._close_wavefile 

1380 self.load_audio_buffer = self._load_buffer_wavefile 

1381 return self 

1382 

1383 def _close_wavefile(self): 

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

1385 if self.sf is not None: 

1386 self.sf.close() 

1387 self.sf = None 

1388 

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

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

1391 

1392 Parameters 

1393 ---------- 

1394 r_offset: int 

1395 First frame to be read from file. 

1396 r_size: int 

1397 Number of frames to be read from file. 

1398 buffer: ndarray 

1399 Buffer where to store the loaded data. 

1400 """ 

1401 if self.sf is None: 

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

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

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

1405 self.sf.read(fbuffer) 

1406 buffer[:,:] = fbuffer.T 

1407 

1408 

1409 # audioread interface:  

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

1411 verbose=0): 

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

1413 

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

1415 backward access is really slow. 

1416 

1417 Parameters 

1418 ---------- 

1419 filepath: str or Path 

1420 Name of the file. 

1421 bufferframes: float 

1422 Size of internal buffer in seconds. 

1423 backsize: float 

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

1425 verbose: int 

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

1427 

1428 Raises 

1429 ------ 

1430 ImportError 

1431 The audioread module is not installed 

1432 """ 

1433 self.verbose = verbose 

1434 if self.verbose > 0: 

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

1436 if not audio_modules['audioread']: 

1437 self.rate = 0.0 

1438 self.channels = 0 

1439 self.frames = 0 

1440 self.shape = (0, 0) 

1441 self.size = 0 

1442 self.offset = 0 

1443 raise ImportError 

1444 if self.sf is not None: 

1445 self._close_audioread() 

1446 self.sf = audioread.audio_open(filepath) 

1447 self.filepath = Path(filepath) 

1448 self.file_paths = [self.filepath] 

1449 self.file_indices = [0] 

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

1451 self.channels = self.sf.channels 

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

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

1454 self.size = self.frames * self.channels 

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

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

1457 self.init_buffer() 

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

1459 self.read_offset = 0 

1460 self.close = self._close_audioread 

1461 self.load_audio_buffer = self._load_buffer_audioread 

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

1463 return self 

1464 

1465 def _close_audioread(self): 

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

1467 if self.sf is not None: 

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

1469 self.sf = None 

1470 

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

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

1473 

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

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

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

1477 

1478 Parameters 

1479 ---------- 

1480 r_offset: int 

1481 First frame to be read from file. 

1482 r_size: int 

1483 Number of frames to be read from file. 

1484 buffer: ndarray 

1485 Buffer where to store the loaded data. 

1486 """ 

1487 if self.sf is None: 

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

1489 b_offset = 0 

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

1491 and self.read_offset < r_offset + r_size ): 

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

1493 i = 0 

1494 n = r_offset + r_size - self.read_offset 

1495 if n > r_size: 

1496 i += n - r_size 

1497 n = r_size 

1498 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) 

1499 if self.verbose > 2: 

1500 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)') 

1501 r_size -= n 

1502 if r_size <= 0: 

1503 return 

1504 # go back to beginning of file: 

1505 if r_offset < self.read_offset: 

1506 if self.verbose > 2: 

1507 print(' rewind') 

1508 self._close_audioread() 

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

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

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

1512 self.read_offset = 0 

1513 # read to position: 

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

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

1516 try: 

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

1518 fbuffer = self.sf_iter.next() 

1519 else: 

1520 fbuffer = next(self.sf_iter) 

1521 except StopIteration: 

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

1523 buffer[:,:] = 0.0 

1524 if self.verbose > 1: 

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

1526 break 

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

1528 if self.verbose > 2: 

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

1530 # recycle file data: 

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

1532 and self.read_offset <= r_offset ): 

1533 i = r_offset - self.read_offset 

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

1535 if n > r_size: 

1536 n = r_size 

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

1538 if self.verbose > 2: 

1539 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)') 

1540 b_offset += n 

1541 r_offset += n 

1542 r_size -= n 

1543 # read data: 

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

1545 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)') 

1546 while r_size > 0: 

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

1548 try: 

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

1550 fbuffer = self.sf_iter.next() 

1551 else: 

1552 fbuffer = next(self.sf_iter) 

1553 except StopIteration: 

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

1555 buffer[b_offset:,:] = 0.0 

1556 if self.verbose > 1: 

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

1558 break 

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

1560 n = self.read_buffer.shape[0] 

1561 if n > r_size: 

1562 n = r_size 

1563 if n > 0: 

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

1565 if self.verbose > 2: 

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

1567 b_offset += n 

1568 r_offset += n 

1569 r_size -= n 

1570 

1571 

1572 # wavpack interface: 

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

1574 verbose=0): 

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

1576 

1577 Parameters 

1578 ---------- 

1579 filepath: str or Path 

1580 Name of the file. 

1581 buffersize: float 

1582 Size of internal buffer in seconds. 

1583 backsize: float 

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

1585 verbose: int 

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

1587 

1588 Raises 

1589 ------ 

1590 ImportError 

1591 The wavpack library is not installed 

1592 """ 

1593 self.verbose = verbose 

1594 if self.verbose > 0: 

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

1596 if not audio_modules['wavpack']: 

1597 self.rate = 0.0 

1598 self.channels = 0 

1599 self.frames = 0 

1600 self.size = 0 

1601 self.shape = (0, 0) 

1602 self.offset = 0 

1603 raise ImportError 

1604 if self.sf is not None: 

1605 self._close_wavpack() 

1606 self.sf = open_wavpack_file(filepath) 

1607 self.filepath = Path(filepath) 

1608 self.file_paths = [self.filepath] 

1609 self.file_indices = [0] 

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

1611 self.format = 'WAVPACK' 

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

1613 self.encoding = 'FLOAT' 

1614 self.factor = 1.0 

1615 else: 

1616 bits = wavpack.WavpackGetBitsPerSample(self.sf) 

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

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

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

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

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

1622 self.size = self.frames * self.channels 

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

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

1625 self.init_buffer() 

1626 self.close = self._close_wavpack 

1627 self.load_audio_buffer = self._load_buffer_wavpack 

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

1629 self.wavpack_pos = 0 

1630 return self 

1631 

1632 def _close_wavpack(self): 

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

1634 if self.sf is not None: 

1635 wavpack.WavpackCloseFile(self.sf) 

1636 self.sf = None 

1637 

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

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

1640 

1641 Parameters 

1642 ---------- 

1643 r_offset: int 

1644 First frame to be read from file. 

1645 r_size: int 

1646 Number of frames to be read from file. 

1647 buffer: ndarray 

1648 Buffer where to store the loaded data. 

1649 """ 

1650 if self.sf is None: 

1651 self.sf = open_wavpack_file(self.filepath) 

1652 self.wavpack_pos = 0 

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

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

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

1656 self.wavpack_pos = r_offset 

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

1658 self.wavpack_pos += len(fbuffer) 

1659 if self.encoding == 'FLOAT': 

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

1661 else: 

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

1663 

1664 

1665 # open multiple audio files as one: 

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

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

1668 end_indices=None): 

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

1670 

1671 Parameters 

1672 ---------- 

1673 filepaths: list of str or Path 

1674 List of file paths of audio files. 

1675 buffersize: float 

1676 Size of internal buffer in seconds. 

1677 backsize: float 

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

1679 verbose: int 

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

1681 mode: 'relaxed' or 'strict' 

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

1683 a start time in their meta data. 

1684 rate: float 

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

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

1687 and end_indices. 

1688 channels: int 

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

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

1691 end_indices: sequence of int 

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

1693 using the provided rate, channels, and end_indices. 

1694 

1695 Raises 

1696 ------ 

1697 TypeError 

1698 `filepaths` must be a sequence. 

1699 ValueError 

1700 Empty `filepaths`. 

1701 FileNotFoundError 

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

1703 

1704 """ 

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

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

1707 if len(filepaths) == 0: 

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

1709 self.buffersize = buffersize 

1710 self.backsize = backsize 

1711 self.filepath = None 

1712 self.file_paths = [] 

1713 self.open_files = [] 

1714 self.open_loaders = [] 

1715 self.audio_files = [] 

1716 self.collect_counter = 0 

1717 self.frames = 0 

1718 self.start_indices = [] 

1719 self.end_indices = [] 

1720 self.start_time = None 

1721 start_time = None 

1722 self._metadata = {} 

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

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

1725 if end_indices is not None: 

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

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

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

1729 self.frames = end_indices[-1] 

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

1731 self.end_indices = end_indices 

1732 self.format = None 

1733 self.encoding = None 

1734 self.rate = rate 

1735 self.channels = channels 

1736 else: 

1737 for filepath in filepaths: 

1738 try: 

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

1740 except Exception as e: 

1741 if verbose > 0: 

1742 print(e) 

1743 continue 

1744 # collect metadata: 

1745 md = a.metadata() 

1746 fmd = flatten_metadata(md, True) 

1747 add_metadata(self._metadata, fmd) 

1748 if self.filepath is None: 

1749 # first file: 

1750 self.filepath = a.filepath 

1751 self.format = a.format 

1752 self.encoding = a.encoding 

1753 self.rate = a.rate 

1754 self.channels = a.channels 

1755 self.start_time = get_datetime(md) 

1756 start_time = self.start_time 

1757 stime = self.start_time 

1758 else: 

1759 # check channels and rate: 

1760 error_str = None 

1761 if a.channels != self.channels: 

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

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

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

1765 if a.rate != self.rate: 

1766 error_str = f'sampling rates differ: ' \ 

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

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

1769 # check start time of recording: 

1770 stime = get_datetime(md) 

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

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

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

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

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

1776 f'expected {start_time} instead of ' \ 

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

1778 if error_str is not None: 

1779 if verbose > 0: 

1780 print(error_str) 

1781 a.close() 

1782 del a 

1783 break 

1784 # markers: 

1785 locs, labels = a.markers() 

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

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

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

1789 # indices: 

1790 self.start_indices.append(self.frames) 

1791 self.frames += a.frames 

1792 self.end_indices.append(self.frames) 

1793 if stime is not None: 

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

1795 # add file to lists: 

1796 self.file_paths.append(a.filepath) 

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

1798 self.open_files.append(a) 

1799 else: 

1800 a.close() 

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

1802 self.audio_files.append(a) 

1803 self.open_loaders.append(a) 

1804 else: 

1805 a.close() 

1806 del a 

1807 self.audio_files.append(None) 

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

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

1810 # set startime from first file: 

1811 if self.start_time is not None: 

1812 set_starttime(self._metadata, self.start_time) 

1813 # setup infrastructure: 

1814 self.file_indices = self.start_indices 

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

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

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

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

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

1820 self.init_buffer() 

1821 self.close = self._close_multiple 

1822 self.load_audio_buffer = self._load_buffer_multiple 

1823 self._load_metadata = None 

1824 self._load_markers = None 

1825 return self 

1826 

1827 def _close_multiple(self): 

1828 """Close all the audio files. """ 

1829 self.open_files = [] 

1830 self.open_loaders = [] 

1831 if hasattr(self, 'audio_files'): 

1832 for a in self.audio_files: 

1833 if a is not None: 

1834 a.close() 

1835 self.audio_files = [] 

1836 self.filepath = None 

1837 self.file_paths = [] 

1838 self.file_indices = [] 

1839 self.start_indices = [] 

1840 self.end_indices = [] 

1841 del self.audio_files 

1842 del self.open_files 

1843 del self.open_loaders 

1844 del self.start_indices 

1845 del self.end_indices 

1846 

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

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

1849 

1850 Parameters 

1851 ---------- 

1852 r_offset: int 

1853 First frame to be read from file. 

1854 r_size: int 

1855 Number of frames to be read from file. 

1856 buffer: ndarray 

1857 Buffer where to store the loaded data. 

1858 """ 

1859 offs = r_offset 

1860 size = r_size 

1861 boffs = 0 

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

1863 while size > 0: 

1864 if self.audio_files[ai] is None: 

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

1866 self.buffersize, self.backsize, 0) 

1867 self.audio_files[ai] = a 

1868 self.open_loaders.append(a) 

1869 self.open_files.append(a) 

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

1871 a0 = self.open_files.pop(0) 

1872 a0.close() 

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

1874 a0 = self.open_loaders.pop(0) 

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

1876 a0.close() 

1877 del a0 

1878 self.collect_counter += 1 

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

1880 gc.collect() 

1881 self.collect_counter = 0 

1882 else: 

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

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

1885 ai0 = offs - self.start_indices[ai] 

1886 ai1 = offs + size 

1887 if ai1 > self.end_indices[ai]: 

1888 ai1 = self.end_indices[ai] 

1889 ai1 -= self.start_indices[ai] 

1890 n = ai1 - ai0 

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

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

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

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

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

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

1897 self.open_files[0].close() 

1898 self.open_files.pop(0) 

1899 boffs += n 

1900 offs += n 

1901 size -= n 

1902 ai += 1 

1903 

1904 

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

1906 verbose=0, **kwargs): 

1907 """Open audio file for reading. 

1908 

1909 Parameters 

1910 ---------- 

1911 filepath: str or Path or list of str or Path 

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

1913 made accessible as a single array. 

1914 buffersize: float 

1915 Size of internal buffer in seconds. 

1916 backsize: float 

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

1918 verbose: int 

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

1920 **kwargs: dict 

1921 Further keyword arguments that are passed on to the  

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

1923 

1924 Raises 

1925 ------ 

1926 FileNotFoundError 

1927 `filepath` is not an existing file. 

1928 EOFError 

1929 File size of `filepath` is zero. 

1930 IOError 

1931 Failed to load data. 

1932 

1933 """ 

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

1935 self.rate = 0.0 

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

1937 if len(filepath) > 1: 

1938 self.open_multiple(filepath, buffersize, backsize, 

1939 verbose - 1, **kwargs) 

1940 if len(self.file_paths) > 1: 

1941 return self 

1942 filepath = self.file_paths[0] 

1943 self.close() 

1944 else: 

1945 filepath = filepath[0] 

1946 filepath = Path(filepath) 

1947 if not filepath.is_file(): 

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

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

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

1951 # list of implemented open functions: 

1952 audio_open_funcs = ( 

1953 ('soundfile', self.open_soundfile), 

1954 ('wave', self.open_wave), 

1955 ('wavefile', self.open_wavefile), 

1956 ('wavpack', self.open_wavpack), 

1957 ('ewave', self.open_ewave), 

1958 ('audioread', self.open_audioread), 

1959 ) 

1960 # open an audio file by trying various modules: 

1961 not_installed = [] 

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

1963 for lib, open_file in audio_open_funcs: 

1964 if not audio_modules[lib]: 

1965 if verbose > 1: 

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

1967 not_installed.append(lib) 

1968 continue 

1969 try: 

1970 open_file(filepath, buffersize, backsize, 

1971 verbose - 1, **kwargs) 

1972 if self.frames > 0: 

1973 if verbose > 0: 

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

1975 if verbose > 1: 

1976 if self.format is not None: 

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

1978 if self.encoding is not None: 

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

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

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

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

1983 return self 

1984 except Exception as e: 

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

1986 if verbose > 1: 

1987 print(errors[-1]) 

1988 if len(not_installed) > 0: 

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

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

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

1992 return self 

1993 

1994 

1995def demo(file_path, plot): 

1996 """Demo of the audioloader functions. 

1997 

1998 Parameters 

1999 ---------- 

2000 file_path: str 

2001 File path of an audio file. 

2002 plot: bool 

2003 If True also plot the loaded data. 

2004 """ 

2005 print('') 

2006 print("try load_audio:") 

2007 full_data, rate = load_audio(file_path, 1) 

2008 if plot: 

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

2010 plt.show() 

2011 

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

2013 print('') 

2014 print("cross check:") 

2015 data1, rate1 = load_soundfile(file_path) 

2016 data2, rate2 = load_audioread(file_path) 

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

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

2019 if plot: 

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

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

2022 plt.show() 

2023 

2024 print('') 

2025 print("try AudioLoader:") 

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

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

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

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

2030 nframes = int(1.5*data.rate) 

2031 # check access: 

2032 print('check random single frame access') 

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

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

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

2036 print('check random frame slice access') 

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

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

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

2040 print('check frame slice access forward') 

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

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

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

2044 print('check frame slice access backward') 

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

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

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

2048 # forward: 

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

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

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

2052 if plot: 

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

2054 plt.show() 

2055 # and backwards: 

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

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

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

2059 if plot: 

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

2061 plt.show() 

2062 

2063 

2064def main(*args): 

2065 """Call demo with command line arguments. 

2066 

2067 Parameters 

2068 ---------- 

2069 args: list of str 

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

2071 """ 

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

2073 

2074 help = False 

2075 plot = False 

2076 file_path = None 

2077 mod = False 

2078 for arg in args: 

2079 if mod: 

2080 if not select_module(arg): 

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

2082 return 

2083 mod = False 

2084 elif arg == '-h': 

2085 help = True 

2086 break 

2087 elif arg == '-p': 

2088 plot = True 

2089 elif arg == '-m': 

2090 mod = True 

2091 else: 

2092 file_path = arg 

2093 break 

2094 

2095 if help: 

2096 print('') 

2097 print('Usage:') 

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

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

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

2101 return 

2102 

2103 if plot: 

2104 import matplotlib.pyplot as plt 

2105 

2106 demo(file_path, plot) 

2107 

2108 

2109if __name__ == "__main__": 

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