From 0ca0eb3972798a5074ac709ffa3939c345ad134d Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sun, 20 Apr 2014 12:51:11 +0200 Subject: [PATCH 1/4] Add open(), read() and write() functions This is the combination of a few commits from #18, plus a few more things. See also #14. --- pysoundfile.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/pysoundfile.py b/pysoundfile.py index e6fbb1a..908daf0 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -700,6 +700,74 @@ def write(self, data): self.seek(curr, SEEK_SET, 'w') +def open(*args, **kwargs): + """Return a new SoundFile object. + + Takes the same arguments as SoundFile.__init__(). + + """ + return SoundFile(*args, **kwargs) + + +def read(file, frames=-1, start=None, stop=None, **kwargs): + """Read a sound file and return its contents as NumPy array. + + The number of frames to read can be specified with frames, the + position to start reading can be specified with start. + By default, the whole file is read from the beginning. + Alternatively, a range can be specified with start and stop. + Both start and stop accept negative indices to specify positions + relative to the end of the file. + + The keyword arguments out, dtype, fill_value and always_2d are + forwarded to SoundFile.read(). + All further arguments are forwarded to SoundFile.__init__(). + + """ + from inspect import getargspec + + if frames >= 0 and stop is not None: + raise RuntimeError("Only one of {frames, stop} may be used") + + read_kwargs = {} + for arg in getargspec(SoundFile.read).args: + if arg in kwargs: + read_kwargs[arg] = kwargs.pop(arg) + with SoundFile(file, 'r', **kwargs) as f: + start, stop, _ = slice(start, stop).indices(f.frames) + if stop < start: + stop = start + if frames < 0: + frames = stop - start + f.seek(start, SEEK_SET) + data = f.read(frames, **read_kwargs) + return data, f.sample_rate + + +def write(data, file, sample_rate, *args, **kwargs): + """Write data from a NumPy array into a sound file. + + If file exists, it will be overwritten! + + If data is one-dimensional, a mono file is written. + For two-dimensional data, the columns are interpreted as channels. + All further arguments are forwarded to SoundFile.__init__(). + + Example usage: + + import pysoundfile as sf + sf.write(myarray, 'myfile.wav', 44100, 'PCM_24') + + """ + data = _np.asarray(data) + if data.ndim == 1: + channels = 1 + else: + channels = data.shape[1] + with SoundFile(file, 'w', sample_rate, channels, *args, **kwargs) as f: + f.write(data) + + def default_subtype(format): """Return default subtype for given format.""" return _default_subtypes.get(str(format).upper()) From 1aec4c530134399775d5471e6584169bfed6a773 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Mon, 2 Jun 2014 13:04:55 +0200 Subject: [PATCH 2/4] Repeat function arguments in module-level functions ... instead of using *args and **kwargs. --- pysoundfile.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pysoundfile.py b/pysoundfile.py index 908daf0..6d05b84 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -700,16 +700,21 @@ def write(self, data): self.seek(curr, SEEK_SET, 'w') -def open(*args, **kwargs): +def open(file, mode='r', sample_rate=None, channels=None, + subtype=None, endian=None, format=None, closefd=True): """Return a new SoundFile object. Takes the same arguments as SoundFile.__init__(). """ - return SoundFile(*args, **kwargs) + return SoundFile(file, mode, sample_rate, channels, + subtype, endian, format, closefd) -def read(file, frames=-1, start=None, stop=None, **kwargs): +def read(file, frames=-1, start=None, stop=None, + dtype='float64', always_2d=True, fill_value=None, out=None, + sample_rate=None, channels=None, + subtype=None, endian=None, format=None, closefd=True): """Read a sound file and return its contents as NumPy array. The number of frames to read can be specified with frames, the @@ -724,34 +729,31 @@ def read(file, frames=-1, start=None, stop=None, **kwargs): All further arguments are forwarded to SoundFile.__init__(). """ - from inspect import getargspec - if frames >= 0 and stop is not None: raise RuntimeError("Only one of {frames, stop} may be used") - read_kwargs = {} - for arg in getargspec(SoundFile.read).args: - if arg in kwargs: - read_kwargs[arg] = kwargs.pop(arg) - with SoundFile(file, 'r', **kwargs) as f: + with SoundFile(file, 'r', sample_rate, channels, + subtype, endian, format, closefd) as f: start, stop, _ = slice(start, stop).indices(f.frames) if stop < start: stop = start if frames < 0: frames = stop - start f.seek(start, SEEK_SET) - data = f.read(frames, **read_kwargs) + data = f.read(frames, dtype, always_2d, fill_value, out) return data, f.sample_rate -def write(data, file, sample_rate, *args, **kwargs): +def write(data, file, sample_rate, + subtype=None, endian=None, format=None, closefd=True): """Write data from a NumPy array into a sound file. If file exists, it will be overwritten! If data is one-dimensional, a mono file is written. For two-dimensional data, the columns are interpreted as channels. - All further arguments are forwarded to SoundFile.__init__(). + + All further arguments are forwarded to open(). Example usage: @@ -764,7 +766,8 @@ def write(data, file, sample_rate, *args, **kwargs): channels = 1 else: channels = data.shape[1] - with SoundFile(file, 'w', sample_rate, channels, *args, **kwargs) as f: + with open(file, 'w', sample_rate, channels, + subtype, endian, format, closefd) as f: f.write(data) From 5ccfdb540827426d2998db74c77e7ce29e51042e Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 10 Jun 2014 15:35:35 +0200 Subject: [PATCH 3/4] Move documentation from read() function to read() method Re-use constructor docstring for open() --- pysoundfile.py | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/pysoundfile.py b/pysoundfile.py index 6d05b84..718a3ca 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -303,7 +303,7 @@ class SoundFile(object): def __init__(self, file, mode='r', sample_rate=None, channels=None, subtype=None, endian=None, format=None, closefd=True): - """Open a new SoundFile. + """Open a sound file. If a file is opened with mode 'r' (the default) or 'rw', no sample_rate, channels or file format need to be given. If a @@ -626,21 +626,8 @@ def read(self, frames=-1, dtype='float64', always_2d=True, position by the same number of frames. Use frames=-1 to read until the end of the file. - A two-dimensional NumPy array is returned, where the channels - are stored along the first dimension, i.e. as columns. - A two-dimensional array is returned even if the sound file has - only one channel. Use always_2d=False to return a - one-dimensional array in this case. - - If out is specified, the data is written into the given NumPy - array. In this case, the arguments frames, dtype and always_2d - are silently ignored! - - If there is less data left in the file than requested, the rest - of the frames are filled with fill_value. If fill_value=None, a - smaller array is returned. - If out is given, only a part of it is overwritten and a view - containing all valid frames is returned. + For further keyword arguments see the module-level function + read(). """ self._check_if_closed() @@ -702,14 +689,11 @@ def write(self, data): def open(file, mode='r', sample_rate=None, channels=None, subtype=None, endian=None, format=None, closefd=True): - """Return a new SoundFile object. - - Takes the same arguments as SoundFile.__init__(). - - """ return SoundFile(file, mode, sample_rate, channels, subtype, endian, format, closefd) +open.__doc__ = SoundFile.__init__.__doc__ + def read(file, frames=-1, start=None, stop=None, dtype='float64', always_2d=True, fill_value=None, out=None, @@ -724,9 +708,24 @@ def read(file, frames=-1, start=None, stop=None, Both start and stop accept negative indices to specify positions relative to the end of the file. - The keyword arguments out, dtype, fill_value and always_2d are - forwarded to SoundFile.read(). - All further arguments are forwarded to SoundFile.__init__(). + A two-dimensional NumPy array is returned, where the channels are + stored along the first dimension, i.e. as columns. + A two-dimensional array is returned even if the sound file has only + one channel. + Use always_2d=False to return a one-dimensional array in this case. + + If out is specified, the data is written into the given NumPy array. + In this case, the arguments frames, dtype and always_2d are silently + ignored! + + If there is less data left in the file than requested, the rest of + the frames are filled with fill_value. If fill_value=None, a smaller + array is returned. + If out is given, only a part of it is overwritten and a view + containing all valid frames is returned. + + The keyword arguments sample_rate, channels, format, subtype and + endian are only needed for 'RAW' files. See open() for details. """ if frames >= 0 and stop is not None: From c1701ab837ee13bf24909905c9a5bd5ae81575a3 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sun, 15 Jun 2014 16:31:15 +0200 Subject: [PATCH 4/4] Duplicate overlapping documentation in read() --- pysoundfile.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pysoundfile.py b/pysoundfile.py index 718a3ca..2df95ca 100644 --- a/pysoundfile.py +++ b/pysoundfile.py @@ -626,8 +626,21 @@ def read(self, frames=-1, dtype='float64', always_2d=True, position by the same number of frames. Use frames=-1 to read until the end of the file. - For further keyword arguments see the module-level function - read(). + A two-dimensional NumPy array is returned, where the channels + are stored along the first dimension, i.e. as columns. + A two-dimensional array is returned even if the sound file has + only one channel. Use always_2d=False to return a + one-dimensional array in this case. + + If out is specified, the data is written into the given NumPy + array. In this case, the arguments frames, dtype and always_2d + are silently ignored! + + If there is less data left in the file than requested, the rest + of the frames are filled with fill_value. If fill_value=None, a + smaller array is returned. + If out is given, only a part of it is overwritten and a view + containing all valid frames is returned. """ self._check_if_closed()