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

697 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-16 18:31 +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 sys 

24import warnings 

25import os.path 

26import numpy as np 

27from datetime import timedelta 

28from .audiomodules import * 

29from .bufferedarray import BufferedArray 

30from .riffmetadata import metadata_riff, markers_riff 

31from .audiometadata import update_gain, add_unwrap, get_datetime 

32from .audiometadata import flatten_metadata, add_metadata, set_starttime 

33from .audiotools import unwrap 

34 

35 

36def load_wave(filepath): 

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

38  

39 Documentation 

40 ------------- 

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

42 

43 Parameters 

44 ---------- 

45 filepath: str 

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

47 

48 Returns 

49 ------- 

50 data: ndarray 

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

52 rate: float 

53 The sampling rate of the data in Hertz. 

54 

55 Raises 

56 ------ 

57 ImportError 

58 The wave module is not installed 

59 * 

60 Loading of the data failed 

61 """ 

62 if not audio_modules['wave']: 

63 raise ImportError 

64 

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

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

67 buffer = wf.readframes(nframes) 

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

69 if sampwidth == 1: 

70 dtype = 'u1' 

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

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

73 else: 

74 dtype = f'i{sampwidth}' 

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

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

77 wf.close() 

78 return data, float(rate) 

79 

80 

81def load_ewave(filepath): 

82 """Load wav file using ewave module. 

83 

84 Documentation 

85 ------------- 

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

87 

88 Parameters 

89 ---------- 

90 filepath: str 

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

92 

93 Returns 

94 ------- 

95 data: ndarray 

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

97 rate: float 

98 The sampling rate of the data in Hertz. 

99 

100 Raises 

101 ------ 

102 ImportError 

103 The ewave module is not installed 

104 * 

105 Loading of the data failed 

106 """ 

107 if not audio_modules['ewave']: 

108 raise ImportError 

109 

110 data = np.array([]) 

111 rate = 0.0 

112 with ewave.open(filepath, 'r') as wf: 

113 rate = wf.sampling_rate 

114 buffer = wf.read() 

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

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

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

118 return data, float(rate) 

119 

120 

121def load_wavfile(filepath): 

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

123 

124 Documentation 

125 ------------- 

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

127 Does not support blocked read. 

128  

129 Parameters 

130 ---------- 

131 filepath: str 

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

133 

134 Returns 

135 ------- 

136 data: ndarray 

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

138 rate: float 

139 The sampling rate of the data in Hertz. 

140 

141 Raises 

142 ------ 

143 ImportError 

144 The scipy.io module is not installed 

145 * 

146 Loading of the data failed 

147 """ 

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

149 raise ImportError 

150 

151 warnings.filterwarnings("ignore") 

152 rate, data = wavfile.read(filepath) 

153 warnings.filterwarnings("always") 

154 if data.dtype == np.uint8: 

155 data = data / 128.0 - 1.0 

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

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

158 else: 

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

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

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

162 return data, float(rate) 

163 

164 

165def load_soundfile(filepath): 

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

167 

168 Documentation 

169 ------------- 

170 http://pysoundfile.readthedocs.org 

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

172 

173 Parameters 

174 ---------- 

175 filepath: str 

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

177 

178 Returns 

179 ------- 

180 data: ndarray 

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

182 rate: float 

183 The sampling rate of the data in Hertz. 

184 

185 Raises 

186 ------ 

187 ImportError 

188 The soundfile module is not installed. 

189 * 

190 Loading of the data failed. 

191 """ 

192 if not audio_modules['soundfile']: 

193 raise ImportError 

194 

195 data = np.array([]) 

196 rate = 0.0 

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

198 rate = sf.samplerate 

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

200 return data, float(rate) 

201 

202 

203def load_wavefile(filepath): 

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

205 

206 Documentation 

207 ------------- 

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

209 

210 Parameters 

211 ---------- 

212 filepath: str 

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

214 

215 Returns 

216 ------- 

217 data: ndarray 

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

219 rate: float 

220 The sampling rate of the data in Hertz. 

221 

222 Raises 

223 ------ 

224 ImportError 

225 The wavefile module is not installed. 

226 * 

227 Loading of the data failed. 

228 """ 

229 if not audio_modules['wavefile']: 

230 raise ImportError 

231 

232 rate, data = wavefile.load(filepath) 

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

234 

235 

236def load_audioread(filepath): 

237 """Load audio file using audioread. 

238 

239 Documentation 

240 ------------- 

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

242 

243 Parameters 

244 ---------- 

245 filepath: str 

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

247 

248 Returns 

249 ------- 

250 data: ndarray 

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

252 rate: float 

253 The sampling rate of the data in Hertz. 

254 

255 Raises 

256 ------ 

257 ImportError 

258 The audioread module is not installed. 

259 * 

260 Loading of the data failed. 

261 """ 

262 if not audio_modules['audioread']: 

263 raise ImportError 

264 

265 data = np.array([]) 

266 rate = 0.0 

267 with audioread.audio_open(filepath) as af: 

268 rate = af.samplerate 

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

270 dtype="<i2") 

271 index = 0 

272 for buffer in af: 

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

274 n = fulldata.shape[0] 

275 if index + n > len(data): 

276 n = len(fulldata) - index 

277 if n <= 0: 

278 break 

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

280 index += n 

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

282 

283 

284audio_loader_funcs = ( 

285 ('soundfile', load_soundfile), 

286 ('wave', load_wave), 

287 ('wavefile', load_wavefile), 

288 ('ewave', load_ewave), 

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

290 ('audioread', load_audioread), 

291 ) 

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

293 

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

295load() function. 

296 

297""" 

298 

299 

300def load_audio(filepath, verbose=0): 

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

302  

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

304 

305 Parameters 

306 ---------- 

307 filepath: str 

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

309 verbose: int 

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

311 

312 Returns 

313 ------- 

314 data: ndarray 

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

316 First dimension is time, second is channel. 

317 Data values range maximally between -1 and 1. 

318 rate: float 

319 The sampling rate of the data in Hertz. 

320 

321 Raises 

322 ------ 

323 ValueError 

324 Empty `filepath`. 

325 FileNotFoundError 

326 `filepath` is not an existing file. 

327 EOFError 

328 File size of `filepath` is zero. 

329 IOError 

330 Failed to load data. 

331 

332 Examples 

333 -------- 

334 ``` 

335 import matplotlib.pyplot as plt 

336 from audioio import load_audio 

337  

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

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

340 plt.show() 

341 ``` 

342 """ 

343 # check values: 

344 if filepath is None or len(filepath) == 0: 

345 raise ValueError('input argument filepath is empty string!') 

346 if not os.path.isfile(filepath): 

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

348 if os.path.getsize(filepath) <= 0: 

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

350 

351 # load an audio file by trying various modules: 

352 not_installed = [] 

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

354 for lib, load_file in audio_loader_funcs: 

355 if not audio_modules[lib]: 

356 if verbose > 1: 

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

358 not_installed.append(lib) 

359 continue 

360 try: 

361 data, rate = load_file(filepath) 

362 if len(data) > 0: 

363 if verbose > 0: 

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

365 if verbose > 1: 

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

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

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

369 return data, rate 

370 except Exception as e: 

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

372 if verbose > 1: 

373 print(errors[-1]) 

374 if len(not_installed) > 0: 

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

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

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

378 return np.zeros(0), 0.0 

379 

380 

381def metadata(filepath, store_empty=False): 

382 """Read metadata of an audio file. 

383 

384 Parameters 

385 ---------- 

386 filepath: str or file handle 

387 The audio file from which to read metadata. 

388 store_empty: bool 

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

390 

391 Returns 

392 ------- 

393 meta_data: nested dict 

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

395 dictionaries are always strings. If the corresponding values 

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

397 metadata contained in the dictionary. All other types of 

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

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

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

401 available functions to work with such metadata. 

402 

403 Examples 

404 -------- 

405 ``` 

406 from audioio import metadata, print_metadata 

407 md = metadata('data.wav') 

408 print_metadata(md) 

409 ``` 

410 

411 """ 

412 try: 

413 return metadata_riff(filepath, store_empty) 

414 except ValueError: # not a RIFF file 

415 return {} 

416 

417 

418def markers(filepath): 

419 """ Read markers of an audio file. 

420 

421 See `audioio.audiomarkers` module for available functions 

422 to work with markers. 

423 

424 Parameters 

425 ---------- 

426 filepath: str or file handle 

427 The audio file. 

428 

429 Returns 

430 ------- 

431 locs: 2-D ndarray of int 

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

433 for each marker (rows). 

434 labels: 2-D ndarray of string objects 

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

436 for each marker (rows). 

437 

438 Examples 

439 -------- 

440 ``` 

441 from audioio import markers, print_markers 

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

443 print_markers(locs, labels) 

444 ``` 

445 """ 

446 try: 

447 return markers_riff(filepath) 

448 except ValueError: # not a RIFF file 

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

450 

451 

452class AudioLoader(BufferedArray): 

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

454  

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

456 sequential audio files that do not fit into memory. 

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

458 ``` 

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

460 x = data[10000:20000,0] 

461 ``` 

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

463 

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

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

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

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

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

469 `BufferedArray` class. 

470 

471 Reading sequentially through the file is always possible. Some 

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

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

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

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

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

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

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

479 the file from the beginning. 

480 

481 Usage 

482 ----- 

483 With context management: 

484 ``` 

485 import audioio as aio 

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

487 # do something with the content of the file: 

488 x = data[0:10000] 

489 y = data[10000:20000] 

490 z = x + y 

491 ``` 

492 

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

494 ``` 

495 data = aio.AudioLoader() 

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

497 # do something ... 

498 ``` 

499 

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

501 ``` 

502 from scipy.signal import spectrogram 

503 nfft = 2048 

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

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

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

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

508 ``` 

509 

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

511 ``` 

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

513 for x in data: 

514 print(x) 

515 ``` 

516  

517 Traditional open and close: 

518 ``` 

519 data = aio.AudioLoader(filepath, 60.0) 

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

521 data.close() 

522 ``` 

523  

524 this is the same as: 

525 ``` 

526 data = aio.AudioLoader() 

527 data.open(filepath, 60.0) 

528 ... 

529 ``` 

530 

531 Classes inheriting AudioLoader just need to implement 

532 ``` 

533 self.load_audio_buffer(offset, nsamples, pbuffer) 

534 ``` 

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

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

537 

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

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

540 

541 For loading metadata and markers, implement the functions 

542 ``` 

543 self._load_metadata(filepath, **kwargs) 

544 self._load_markers(filepath) 

545 ``` 

546  

547 Parameters 

548 ---------- 

549 filepath: str 

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

551 made accessible as a single array. 

552 buffersize: float 

553 Size of internal buffer in seconds. 

554 backsize: float 

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

556 verbose: int 

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

558 store_empty: bool 

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

560 

561 Attributes 

562 ---------- 

563 filepath: str or list of str 

564 Name and path of the opened file, or list of many file names 

565 that are made accessible as a single array. 

566 rate: float 

567 The sampling rate of the data in seconds. 

568 channels: int 

569 The number of channels. 

570 frames: int 

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

572 format: str or None 

573 Format of the audio file. 

574 encoding: str or None 

575 Encoding/subtype of the audio file. 

576 shape: tuple 

577 Frames and channels of the data. 

578 ndim: int 

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

580 offset: int 

581 Index of first frame in the current buffer. 

582 buffer: ndarray of floats 

583 The curently available data from the file. 

584 ampl_min: float 

585 Minimum amplitude the file format supports. 

586 Always -1.0 for audio data. 

587 ampl_max: float 

588 Maximum amplitude the file format supports. 

589 Always +1.0 for audio data. 

590 

591 Methods 

592 ------- 

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

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

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

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

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

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

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

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

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

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

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

604 

605 """ 

606 

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

608 verbose=0, **meta_kwargs): 

609 super().__init__(verbose=verbose) 

610 self.format = None 

611 self.encoding = None 

612 self._metadata = None 

613 self._locs = None 

614 self._labels = None 

615 self._load_metadata = metadata 

616 self._load_markers = markers 

617 self._metadata_kwargs = meta_kwargs 

618 self.filepath = None 

619 self.sf = None 

620 self.close = self._close 

621 self.load_buffer = self._load_buffer_unwrap 

622 self.ampl_min = -1.0 

623 self.ampl_max = +1.0 

624 self.unwrap = False 

625 self.unwrap_thresh = 0.0 

626 self.unwrap_clips = False 

627 self.unwrap_ampl = 1.0 

628 self.unwrap_downscale = True 

629 if filepath is not None: 

630 self.open(filepath, buffersize, backsize, verbose) 

631 

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

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

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

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

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

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

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

639 """ Map numpy dtypes to encodings. 

640 """ 

641 

642 def _close(self): 

643 pass 

644 

645 def __del__(self): 

646 self.close() 

647 

648 def format_dict(self): 

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

650 

651 Returns 

652 ------- 

653 fmt: dict 

654 Dictionary with filepath, format, encoding, samplingrate, 

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

656 

657 """ 

658 fmt = dict(filepath=self.filepath) 

659 if self.format is not None: 

660 fmt['format'] = self.format 

661 if self.encoding is not None: 

662 fmt['encoding'] = self.encoding 

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

664 channels=self.channels, 

665 frames=self.frames, 

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

667 return fmt 

668 

669 def metadata(self): 

670 """Metadata of the audio file. 

671 

672 Parameters 

673 ---------- 

674 store_empty: bool 

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

676 

677 Returns 

678 ------- 

679 meta_data: nested dict 

680 

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

682 dictionaries are always strings. If the corresponding 

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

684 of the metadata contained in the dictionary. All other 

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

686 particular they are strings. But other types like for 

687 example ints or floats are also allowed. See 

688 `audioio.audiometadata` module for available functions to 

689 work with such metadata. 

690 

691 """ 

692 if self._metadata is None: 

693 if self._load_metadata is None: 

694 self._metadata = {} 

695 else: 

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

697 **self._metadata_kwargs) 

698 return self._metadata 

699 

700 def markers(self): 

701 """Read markers of the audio file. 

702 

703 See `audioio.audiomarkers` module for available functions 

704 to work with markers. 

705 

706 Returns 

707 ------- 

708 locs: 2-D ndarray of int 

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

710 for each marker (rows). 

711 labels: 2-D ndarray of str objects 

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

713 for each marker (rows). 

714 """ 

715 if self._locs is None: 

716 if self._load_markers is None: 

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

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

719 else: 

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

721 return self._locs, self._labels 

722 

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

724 """Set parameters for unwrapping clipped data. 

725 

726 See unwrap() function from the audioio package. 

727 

728 Parameters 

729 ---------- 

730 thresh: float 

731 Threshold for detecting wrapped data relative to self.unwrap_ampl 

732 which is initially set to self.ampl_max. 

733 If zero, do not unwrap. 

734 clips: bool 

735 If True, then clip the unwrapped data properly. 

736 Otherwise, unwrap the data and double the 

737 minimum and maximum data range 

738 (self.ampl_min and self.ampl_max). 

739 down_scale: bool 

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

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

742 unit: str 

743 Unit of the data. 

744 """ 

745 self.unwrap_ampl = self.ampl_max 

746 self.unwrap_thresh = thresh 

747 self.unwrap_clips = clips 

748 self.unwrap_down_scale = down_scale 

749 self.unwrap = thresh > 1e-3 

750 if self.unwrap: 

751 if self.unwrap_clips: 

752 add_unwrap(self.metadata(), 

753 self.unwrap_thresh*self.unwrap_ampl, 

754 self.unwrap_ampl, unit) 

755 elif down_scale: 

756 update_gain(self.metadata(), 0.5) 

757 add_unwrap(self.metadata(), 

758 0.5*self.unwrap_thresh*self.unwrap_ampl, 

759 0.0, unit) 

760 else: 

761 self.ampl_min *= 2 

762 self.ampl_max *= 2 

763 add_unwrap(self.metadata(), 

764 self.unwrap_thresh*self.unwrap_ampl, 

765 0.0, unit) 

766 

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

768 """Load new data and unwrap it. 

769 

770 Parameters 

771 ---------- 

772 r_offset: int 

773 First frame to be read from file. 

774 r_size: int 

775 Number of frames to be read from file. 

776 pbuffer: ndarray 

777 Buffer where to store the loaded data. 

778 """ 

779 self.load_audio_buffer(r_offset, r_size, pbuffer) 

780 if self.unwrap: 

781 # TODO: handle edge effects! 

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

783 if self.unwrap_clips: 

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

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

786 elif self.unwrap_down_scale: 

787 pbuffer *= 0.5 

788 

789 

790 # wave interface:  

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

792 verbose=0): 

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

794 

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

796 

797 Parameters 

798 ---------- 

799 filepath: str 

800 Name of the file. 

801 buffersize: float 

802 Size of internal buffer in seconds. 

803 backsize: float 

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

805 verbose: int 

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

807 

808 Raises 

809 ------ 

810 ImportError 

811 The wave module is not installed 

812 """ 

813 self.verbose = verbose 

814 if self.verbose > 0: 

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

816 if not audio_modules['wave']: 

817 self.rate = 0.0 

818 self.channels = 0 

819 self.frames = 0 

820 self.size = 0 

821 self.shape = (0, 0) 

822 self.offset = 0 

823 raise ImportError 

824 if self.sf is not None: 

825 self._close_wave() 

826 self.sf = wave.open(filepath, 'r') 

827 self.filepath = filepath 

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

829 self.format = 'WAV' 

830 sampwidth = self.sf.getsampwidth() 

831 if sampwidth == 1: 

832 self.dtype = 'u1' 

833 self.encoding = 'PCM_U8' 

834 else: 

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

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

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

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

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

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

841 self.size = self.frames * self.channels 

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

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

844 self.init_buffer() 

845 self.close = self._close_wave 

846 self.load_audio_buffer = self._load_buffer_wave 

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

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

849 self.sf.readframes(1) 

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

851 self.sf.setpos(self.p0) 

852 return self 

853 

854 def _close_wave(self): 

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

856 if self.sf is not None: 

857 self.sf.close() 

858 self.sf = None 

859 

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

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

862 

863 Parameters 

864 ---------- 

865 r_offset: int 

866 First frame to be read from file. 

867 r_size: int 

868 Number of frames to be read from file. 

869 buffer: ndarray 

870 Buffer where to store the loaded data. 

871 """ 

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

873 fbuffer = self.sf.readframes(r_size) 

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

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

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

877 else: 

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

879 

880 

881 # ewave interface:  

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

883 verbose=0): 

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

885 

886 Parameters 

887 ---------- 

888 filepath: str 

889 Name of the file. 

890 buffersize: float 

891 Size of internal buffer in seconds. 

892 backsize: float 

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

894 verbose: int 

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

896 

897 Raises 

898 ------ 

899 ImportError 

900 The ewave module is not installed. 

901 """ 

902 self.verbose = verbose 

903 if self.verbose > 0: 

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

905 if not audio_modules['ewave']: 

906 self.rate = 0.0 

907 self.channels = 0 

908 self.frames = 0 

909 self.shape = (0, 0) 

910 self.size = 0 

911 self.offset = 0 

912 raise ImportError 

913 if self.sf is not None: 

914 self._close_ewave() 

915 self.sf = ewave.open(filepath, 'r') 

916 self.filepath = filepath 

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

918 self.channels = self.sf.nchannels 

919 self.frames = self.sf.nframes 

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

921 self.size = self.frames * self.channels 

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

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

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

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

926 self.init_buffer() 

927 self.close = self._close_ewave 

928 self.load_audio_buffer = self._load_buffer_ewave 

929 return self 

930 

931 def _close_ewave(self): 

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

933 if self.sf is not None: 

934 del self.sf 

935 self.sf = None 

936 

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

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

939 

940 Parameters 

941 ---------- 

942 r_offset: int 

943 First frame to be read from file. 

944 r_size: int 

945 Number of frames to be read from file. 

946 buffer: ndarray 

947 Buffer where to store the loaded data. 

948 """ 

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

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

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

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

953 buffer[:,:] = fbuffer 

954 

955 

956 # soundfile interface:  

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

958 verbose=0): 

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

960 

961 Parameters 

962 ---------- 

963 filepath: str 

964 Name of the file. 

965 bufferframes: float 

966 Size of internal buffer in seconds. 

967 backsize: float 

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

969 verbose: int 

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

971 

972 Raises 

973 ------ 

974 ImportError 

975 The SoundFile module is not installed 

976 """ 

977 self.verbose = verbose 

978 if self.verbose > 0: 

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

980 if not audio_modules['soundfile']: 

981 self.rate = 0.0 

982 self.channels = 0 

983 self.frames = 0 

984 self.shape = (0, 0) 

985 self.size = 0 

986 self.offset = 0 

987 raise ImportError 

988 if self.sf is not None: 

989 self._close_soundfile() 

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

991 self.filepath = filepath 

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

993 self.channels = self.sf.channels 

994 self.frames = 0 

995 self.size = 0 

996 if self.sf.seekable(): 

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

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

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

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

1001 self.size = self.frames * self.channels 

1002 self.format = self.sf.format 

1003 self.encoding = self.sf.subtype 

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

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

1006 self.init_buffer() 

1007 self.close = self._close_soundfile 

1008 self.load_audio_buffer = self._load_buffer_soundfile 

1009 return self 

1010 

1011 def _close_soundfile(self): 

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

1013 if self.sf is not None: 

1014 self.sf.close() 

1015 self.sf = None 

1016 

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

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

1019 

1020 Parameters 

1021 ---------- 

1022 r_offset: int 

1023 First frame to be read from file. 

1024 r_size: int 

1025 Number of frames to be read from file. 

1026 buffer: ndarray 

1027 Buffer where to store the loaded data. 

1028 """ 

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

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

1031 

1032 

1033 # wavefile interface:  

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

1035 verbose=0): 

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

1037 

1038 Parameters 

1039 ---------- 

1040 filepath: str 

1041 Name of the file. 

1042 bufferframes: float 

1043 Size of internal buffer in seconds. 

1044 backsize: float 

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

1046 verbose: int 

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

1048 

1049 Raises 

1050 ------ 

1051 ImportError 

1052 The wavefile module is not installed 

1053 """ 

1054 self.verbose = verbose 

1055 if self.verbose > 0: 

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

1057 if not audio_modules['wavefile']: 

1058 self.rate = 0.0 

1059 self.channels = 0 

1060 self.frames = 0 

1061 self.shape = (0, 0) 

1062 self.size = 0 

1063 self.offset = 0 

1064 raise ImportError 

1065 if self.sf is not None: 

1066 self._close_wavefile() 

1067 self.sf = wavefile.WaveReader(filepath) 

1068 self.filepath = filepath 

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

1070 self.channels = self.sf.channels 

1071 self.frames = self.sf.frames 

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

1073 self.size = self.frames * self.channels 

1074 # get format and encoding: 

1075 for attr in dir(wavefile.Format): 

1076 v = getattr(wavefile.Format, attr) 

1077 if isinstance(v, int): 

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

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

1080 self.format = attr 

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

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

1083 self.encoding = attr 

1084 # init buffer: 

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

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

1087 self.init_buffer() 

1088 self.close = self._close_wavefile 

1089 self.load_audio_buffer = self._load_buffer_wavefile 

1090 return self 

1091 

1092 def _close_wavefile(self): 

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

1094 if self.sf is not None: 

1095 self.sf.close() 

1096 self.sf = None 

1097 

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

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

1100 

1101 Parameters 

1102 ---------- 

1103 r_offset: int 

1104 First frame to be read from file. 

1105 r_size: int 

1106 Number of frames to be read from file. 

1107 buffer: ndarray 

1108 Buffer where to store the loaded data. 

1109 """ 

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

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

1112 self.sf.read(fbuffer) 

1113 buffer[:,:] = fbuffer.T 

1114 

1115 

1116 # audioread interface:  

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

1118 verbose=0): 

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

1120 

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

1122 backward access is really slow. 

1123 

1124 Parameters 

1125 ---------- 

1126 filepath: str 

1127 Name of the file. 

1128 bufferframes: float 

1129 Size of internal buffer in seconds. 

1130 backsize: float 

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

1132 verbose: int 

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

1134 

1135 Raises 

1136 ------ 

1137 ImportError 

1138 The audioread module is not installed 

1139 """ 

1140 self.verbose = verbose 

1141 if self.verbose > 0: 

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

1143 if not audio_modules['audioread']: 

1144 self.rate = 0.0 

1145 self.channels = 0 

1146 self.frames = 0 

1147 self.shape = (0, 0) 

1148 self.size = 0 

1149 self.offset = 0 

1150 raise ImportError 

1151 if self.sf is not None: 

1152 self._close_audioread() 

1153 self.sf = audioread.audio_open(filepath) 

1154 self.filepath = filepath 

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

1156 self.channels = self.sf.channels 

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

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

1159 self.size = self.frames * self.channels 

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

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

1162 self.init_buffer() 

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

1164 self.read_offset = 0 

1165 self.close = self._close_audioread 

1166 self.load_audio_buffer = self._load_buffer_audioread 

1167 self.filepath = filepath 

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

1169 return self 

1170 

1171 def _close_audioread(self): 

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

1173 if self.sf is not None: 

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

1175 self.sf = None 

1176 

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

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

1179 

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

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

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

1183 

1184 Parameters 

1185 ---------- 

1186 r_offset: int 

1187 First frame to be read from file. 

1188 r_size: int 

1189 Number of frames to be read from file. 

1190 buffer: ndarray 

1191 Buffer where to store the loaded data. 

1192 """ 

1193 b_offset = 0 

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

1195 and self.read_offset < r_offset + r_size ): 

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

1197 i = 0 

1198 n = r_offset + r_size - self.read_offset 

1199 if n > r_size: 

1200 i += n - r_size 

1201 n = r_size 

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

1203 if self.verbose > 2: 

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

1205 r_size -= n 

1206 if r_size <= 0: 

1207 return 

1208 # go back to beginning of file: 

1209 if r_offset < self.read_offset: 

1210 if self.verbose > 2: 

1211 print(' rewind') 

1212 self._close_audioread() 

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

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

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

1216 self.read_offset = 0 

1217 # read to position: 

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

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

1220 try: 

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

1222 fbuffer = self.sf_iter.next() 

1223 else: 

1224 fbuffer = next(self.sf_iter) 

1225 except StopIteration: 

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

1227 buffer[:,:] = 0.0 

1228 if self.verbose > 1: 

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

1230 break 

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

1232 if self.verbose > 2: 

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

1234 # recycle file data: 

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

1236 and self.read_offset <= r_offset ): 

1237 i = r_offset - self.read_offset 

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

1239 if n > r_size: 

1240 n = r_size 

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

1242 if self.verbose > 2: 

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

1244 b_offset += n 

1245 r_offset += n 

1246 r_size -= n 

1247 # read data: 

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

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

1250 while r_size > 0: 

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

1252 try: 

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

1254 fbuffer = self.sf_iter.next() 

1255 else: 

1256 fbuffer = next(self.sf_iter) 

1257 except StopIteration: 

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

1259 buffer[b_offset:,:] = 0.0 

1260 if self.verbose > 1: 

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

1262 break 

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

1264 n = self.read_buffer.shape[0] 

1265 if n > r_size: 

1266 n = r_size 

1267 if n > 0: 

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

1269 if self.verbose > 2: 

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

1271 b_offset += n 

1272 r_offset += n 

1273 r_size -= n 

1274 

1275 

1276 # open multiple audio files as one: 

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

1278 verbose=0): 

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

1280 

1281 Parameters 

1282 ---------- 

1283 filepaths: list of str 

1284 List of file names of audio files. 

1285 buffersize: float 

1286 Size of internal buffer in seconds. 

1287 backsize: float 

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

1289 verbose: int 

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

1291 

1292 Raises 

1293 ------ 

1294 TypeError 

1295 `filepaths` must be a sequence. 

1296 ValueError 

1297 Empty `filepaths`. 

1298 FileNotFoundError 

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

1300 

1301 """ 

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

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

1304 if len(filepaths) == 0: 

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

1306 self.audio_files = [] 

1307 self.start_indices = [] 

1308 for filepath in filepaths: 

1309 try: 

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

1311 self.audio_files. append(a) 

1312 except Exception as e: 

1313 if verbose > 0: 

1314 print(e) 

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

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

1317 # check contingency and set start indices: 

1318 a0 = self.audio_files[0] 

1319 self.filepath = a0.filepath 

1320 self.format = a0.format 

1321 self.encoding = a0.encoding 

1322 self.rate = a0.rate 

1323 self.channels = a0.channels 

1324 self.frames = 0 

1325 self.start_indices = [] 

1326 self.end_indices = [] 

1327 md = a0.metadata() 

1328 start_time = get_datetime(md) 

1329 self._metadata = {} 

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

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

1332 for a in self.audio_files: 

1333 if a.channels != self.channels: 

1334 raise ValueError(f'number of channels differs: ' 

1335 f'{a.channels} in {a.filepath} versus ' 

1336 f'{self.channels} in {self.filepath}') 

1337 if a.rate != self.rate: 

1338 raise ValueError(f'sampling rates differ: ' 

1339 f'{a.rate} in {a.filepath} versus ' 

1340 f'{self.rate} in {self.filepath}') 

1341 # metadata: 

1342 md = a.metadata() 

1343 fmd = flatten_metadata(md, True) 

1344 add_metadata(self._metadata, fmd) 

1345 # check start time of recording: 

1346 stime = get_datetime(md) 

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

1348 abs(start_time - stime) > timedelta(seconds=1): 

1349 raise ValueError(f'start time does not indicate continuous recording: ' 

1350 f'expected {start_time} instead of ' 

1351 f'{stime} in {a.filepath}') 

1352 # markers: 

1353 locs, labels = a.markers() 

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

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

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

1357 # indices: 

1358 self.start_indices.append(self.frames) 

1359 self.frames += a.frames 

1360 self.end_indices.append(self.frames) 

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

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

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

1364 # set startime from first file: 

1365 start_time = get_datetime(a0.metadata()) 

1366 set_starttime(self._metadata, start_time) 

1367 # setup infrastructure: 

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

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

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

1371 self.init_buffer() 

1372 self.close = self._close_multiple 

1373 self.load_audio_buffer = self._load_buffer_multiple 

1374 self._load_metadata = None 

1375 self._load_markers = None 

1376 return self 

1377 

1378 def _close_multiple(self): 

1379 """Close all the audio files. """ 

1380 for a in self.audio_files: 

1381 a.close() 

1382 self.audio_files = [] 

1383 self.start_indices = [] 

1384 self.end_indices = [] 

1385 

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

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

1388 

1389 Parameters 

1390 ---------- 

1391 r_offset: int 

1392 First frame to be read from file. 

1393 r_size: int 

1394 Number of frames to be read from file. 

1395 buffer: ndarray 

1396 Buffer where to store the loaded data. 

1397 """ 

1398 offs = r_offset 

1399 size = r_size 

1400 boffs = 0 

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

1402 while size > 0: 

1403 ai0 = offs - self.start_indices[ai] 

1404 ai1 = offs + size 

1405 if ai1 > self.end_indices[ai]: 

1406 ai1 = self.end_indices[ai] 

1407 ai1 -= self.start_indices[ai] 

1408 n = ai1 - ai0 

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

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

1411 boffs += n 

1412 offs += n 

1413 size -= n 

1414 ai += 1 

1415 

1416 

1417 def open(self, filepath, buffersize=10.0, backsize=0.0, verbose=0): 

1418 """Open audio file for reading. 

1419 

1420 Parameters 

1421 ---------- 

1422 filepath: str or list of str 

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

1424 made accessible as a single array. 

1425 buffersize: float 

1426 Size of internal buffer in seconds. 

1427 backsize: float 

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

1429 verbose: int 

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

1431 

1432 Raises 

1433 ------ 

1434 ValueError 

1435 Empty `filepath`. 

1436 FileNotFoundError 

1437 `filepath` is not an existing file. 

1438 EOFError 

1439 File size of `filepath` is zero. 

1440 IOError 

1441 Failed to load data. 

1442 

1443 """ 

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

1445 self.rate = 0.0 

1446 if not filepath: 

1447 raise ValueError('input argument filepath is empty string!') 

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

1449 return self.open_multiple(filepath, buffersize, backsize, verbose) 

1450 if not os.path.isfile(filepath): 

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

1452 if os.path.getsize(filepath) <= 0: 

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

1454 # list of implemented open functions: 

1455 audio_open_funcs = ( 

1456 ('soundfile', self.open_soundfile), 

1457 ('wave', self.open_wave), 

1458 ('wavefile', self.open_wavefile), 

1459 ('ewave', self.open_ewave), 

1460 ('audioread', self.open_audioread), 

1461 ) 

1462 # open an audio file by trying various modules: 

1463 not_installed = [] 

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

1465 for lib, open_file in audio_open_funcs: 

1466 if not audio_modules[lib]: 

1467 if verbose > 1: 

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

1469 not_installed.append(lib) 

1470 continue 

1471 try: 

1472 open_file(filepath, buffersize, backsize, verbose-1) 

1473 if self.frames > 0: 

1474 if verbose > 0: 

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

1476 if verbose > 1: 

1477 if self.format is not None: 

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

1479 if self.encoding is not None: 

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

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

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

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

1484 return self 

1485 except Exception as e: 

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

1487 if verbose > 1: 

1488 print(errors[-1]) 

1489 if len(not_installed) > 0: 

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

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

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

1493 return self 

1494 

1495 

1496def demo(file_path, plot): 

1497 """Demo of the audioloader functions. 

1498 

1499 Parameters 

1500 ---------- 

1501 file_path: str 

1502 File path of an audio file. 

1503 plot: bool 

1504 If True also plot the loaded data. 

1505 """ 

1506 print('') 

1507 print("try load_audio:") 

1508 full_data, rate = load_audio(file_path, 1) 

1509 if plot: 

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

1511 plt.show() 

1512 

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

1514 print('') 

1515 print("cross check:") 

1516 data1, rate1 = load_soundfile(file_path) 

1517 data2, rate2 = load_audioread(file_path) 

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

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

1520 if plot: 

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

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

1523 plt.show() 

1524 

1525 print('') 

1526 print("try AudioLoader:") 

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

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

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

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

1531 nframes = int(1.5*data.rate) 

1532 # check access: 

1533 print('check random single frame access') 

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

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

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

1537 print('check random frame slice access') 

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

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

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

1541 print('check frame slice access forward') 

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

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

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

1545 print('check frame slice access backward') 

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

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

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

1549 # forward: 

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

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

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

1553 if plot: 

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

1555 plt.show() 

1556 # and backwards: 

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

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

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

1560 if plot: 

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

1562 plt.show() 

1563 

1564 

1565def main(*args): 

1566 """Call demo with command line arguments. 

1567 

1568 Parameters 

1569 ---------- 

1570 args: list of str 

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

1572 """ 

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

1574 

1575 help = False 

1576 plot = False 

1577 file_path = None 

1578 mod = False 

1579 for arg in args: 

1580 if mod: 

1581 if not select_module(arg): 

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

1583 return 

1584 mod = False 

1585 elif arg == '-h': 

1586 help = True 

1587 break 

1588 elif arg == '-p': 

1589 plot = True 

1590 elif arg == '-m': 

1591 mod = True 

1592 else: 

1593 file_path = arg 

1594 break 

1595 

1596 if help: 

1597 print('') 

1598 print('Usage:') 

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

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

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

1602 return 

1603 

1604 if plot: 

1605 import matplotlib.pyplot as plt 

1606 

1607 demo(file_path, plot) 

1608 

1609 

1610if __name__ == "__main__": 

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