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
« 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.
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.
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.
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.
17For a demo run the module as:
18```
19python -m src.audioio.audioloader audiofile.wav
20```
21"""
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
36def load_wave(filepath):
37 """Load wav file using the wave module from pythons standard libray.
39 Documentation
40 -------------
41 https://docs.python.org/3.8/library/wave.html
43 Parameters
44 ----------
45 filepath: str
46 The full path and name of the file to load.
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.
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
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)
81def load_ewave(filepath):
82 """Load wav file using ewave module.
84 Documentation
85 -------------
86 https://github.com/melizalab/py-ewave
88 Parameters
89 ----------
90 filepath: str
91 The full path and name of the file to load.
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.
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
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)
121def load_wavfile(filepath):
122 """Load wav file using scipy.io.wavfile.
124 Documentation
125 -------------
126 http://docs.scipy.org/doc/scipy/reference/io.html
127 Does not support blocked read.
129 Parameters
130 ----------
131 filepath: str
132 The full path and name of the file to load.
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.
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
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)
165def load_soundfile(filepath):
166 """Load audio file using SoundFile (based on libsndfile).
168 Documentation
169 -------------
170 http://pysoundfile.readthedocs.org
171 http://www.mega-nerd.com/libsndfile
173 Parameters
174 ----------
175 filepath: str
176 The full path and name of the file to load.
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.
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
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)
203def load_wavefile(filepath):
204 """Load audio file using wavefile (based on libsndfile).
206 Documentation
207 -------------
208 https://github.com/vokimon/python-wavefile
210 Parameters
211 ----------
212 filepath: str
213 The full path and name of the file to load.
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.
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
232 rate, data = wavefile.load(filepath)
233 return data.astype(np.float64, copy=False).T, float(rate)
236def load_audioread(filepath):
237 """Load audio file using audioread.
239 Documentation
240 -------------
241 https://github.com/beetbox/audioread
243 Parameters
244 ----------
245 filepath: str
246 The full path and name of the file to load.
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.
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
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)
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.
294Each element of the list is a tuple with the module's name and its
295load() function.
297"""
300def load_audio(filepath, verbose=0):
301 """Call this function to load all channels of audio data from a file.
303 This function tries different python modules to load the audio file.
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.
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.
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.
332 Examples
333 --------
334 ```
335 import matplotlib.pyplot as plt
336 from audioio import load_audio
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)!')
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
381def metadata(filepath, store_empty=False):
382 """Read metadata of an audio file.
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.
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.
403 Examples
404 --------
405 ```
406 from audioio import metadata, print_metadata
407 md = metadata('data.wav')
408 print_metadata(md)
409 ```
411 """
412 try:
413 return metadata_riff(filepath, store_empty)
414 except ValueError: # not a RIFF file
415 return {}
418def markers(filepath):
419 """ Read markers of an audio file.
421 See `audioio.audiomarkers` module for available functions
422 to work with markers.
424 Parameters
425 ----------
426 filepath: str or file handle
427 The audio file.
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).
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)
452class AudioLoader(BufferedArray):
453 """Buffered reading of audio data for random access of the data in the file.
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.
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.
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.
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 ```
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 ```
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 ```
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 ```
517 Traditional open and close:
518 ```
519 data = aio.AudioLoader(filepath, 60.0)
520 x = data[:,:] # read the whole file
521 data.close()
522 ```
524 this is the same as:
525 ```
526 data = aio.AudioLoader()
527 data.open(filepath, 60.0)
528 ...
529 ```
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`.
538 In the constructor or some kind of opening function, you need to
539 set some member variables, as described for `BufferedArray`.
541 For loading metadata and markers, implement the functions
542 ```
543 self._load_metadata(filepath, **kwargs)
544 self._load_markers(filepath)
545 ```
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.
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.
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.
605 """
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)
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 """
642 def _close(self):
643 pass
645 def __del__(self):
646 self.close()
648 def format_dict(self):
649 """ Technical infos about how the data are stored in the file.
651 Returns
652 -------
653 fmt: dict
654 Dictionary with filepath, format, encoding, samplingrate,
655 channels, frames, and duration of the audio file as strings.
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
669 def metadata(self):
670 """Metadata of the audio file.
672 Parameters
673 ----------
674 store_empty: bool
675 If `False` do not add meta data with empty values.
677 Returns
678 -------
679 meta_data: nested dict
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.
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
700 def markers(self):
701 """Read markers of the audio file.
703 See `audioio.audiomarkers` module for available functions
704 to work with markers.
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
723 def set_unwrap(self, thresh, clips=False, down_scale=True, unit=''):
724 """Set parameters for unwrapping clipped data.
726 See unwrap() function from the audioio package.
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)
767 def _load_buffer_unwrap(self, r_offset, r_size, pbuffer):
768 """Load new data and unwrap it.
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
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.
795 Note: we assume that setpos() and tell() use integer numbers!
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.
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
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
860 def _load_buffer_wave(self, r_offset, r_size, buffer):
861 """Load new data from file using the wave module.
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
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.
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.
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
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
937 def _load_buffer_ewave(self, r_offset, r_size, buffer):
938 """Load new data from file using the ewave module.
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
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.
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.
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
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
1017 def _load_buffer_soundfile(self, r_offset, r_size, buffer):
1018 """Load new data from file using the SoundFile module.
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)
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.
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.
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
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
1098 def _load_buffer_wavefile(self, r_offset, r_size, buffer):
1099 """Load new data from file using the wavefile module.
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
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.
1121 Note, that audioread can only read forward, therefore random and
1122 backward access is really slow.
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.
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
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
1177 def _load_buffer_audioread(self, r_offset, r_size, buffer):
1178 """Load new data from file using the audioread module.
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.
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
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.
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.
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.
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
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 = []
1386 def _load_buffer_multiple(self, r_offset, r_size, buffer):
1387 """Load new data from the underlying files.
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
1417 def open(self, filepath, buffersize=10.0, backsize=0.0, verbose=0):
1418 """Open audio file for reading.
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.
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.
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
1496def demo(file_path, plot):
1497 """Demo of the audioloader functions.
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()
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()
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()
1565def main(*args):
1566 """Call demo with command line arguments.
1568 Parameters
1569 ----------
1570 args: list of str
1571 Command line arguments as provided by sys.argv[1:]
1572 """
1573 print("Checking audioloader module ...")
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
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
1604 if plot:
1605 import matplotlib.pyplot as plt
1607 demo(file_path, plot)
1610if __name__ == "__main__":
1611 main(*sys.argv[1:])