From d3cced21e6d07565b83ac811c5c614864a1614f9 Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:05:24 +0900 Subject: [PATCH 01/19] add typehint to soundfile.py --- pyrightconfig.json | 9 ++ setup.py | 2 +- soundfile.py | 368 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 304 insertions(+), 75 deletions(-) create mode 100644 pyrightconfig.json diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..59c7273 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,9 @@ +{ + "pythonVersion": "3.9", + "exclude": [ + "**/node_modules", + "**/__pycache__", + "**/.venv", + "tests/" + ] + } \ No newline at end of file diff --git a/setup.py b/setup.py index 1e94f44..5951972 100644 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def get_tag(self): zip_safe=zip_safe, license='BSD 3-Clause License', setup_requires=["cffi>=1.0"], - install_requires=['cffi>=1.0', 'numpy'], + install_requires=['cffi>=1.0', 'numpy', 'typing-extensions'], cffi_modules=["soundfile_build.py:ffibuilder"], extras_require={'numpy': []}, # This option is no longer relevant, but the empty entry must be left in to avoid breaking old build scripts. platforms='any', diff --git a/soundfile.py b/soundfile.py index 61db56c..0dc7403 100644 --- a/soundfile.py +++ b/soundfile.py @@ -12,17 +12,36 @@ import os as _os import sys as _sys +import numpy as np +import numpy.typing as npt from os import SEEK_SET, SEEK_CUR, SEEK_END from ctypes.util import find_library as _find_library +from typing import Any, BinaryIO, Dict, Final, Generator, Literal, Optional, Tuple, Union, TypeVar, overload, cast +from typing_extensions import TypeAlias, Self from _soundfile import ffi as _ffi try: - _unicode = unicode # doesn't exist in Python 3.x + _unicode = unicode # type: ignore # doesn't exist in Python 3.x except NameError: _unicode = str - -_str_types = { +# Type aliases for specific types +StrType: TypeAlias = Literal['title', 'copyright', 'software', 'artist', 'comment', 'date', 'album', 'license', 'tracknumber', 'genre'] +Format: TypeAlias = Literal['WAV', 'AIFF', 'AU', 'RAW', 'PAF', 'SVX', 'NIST', 'VOC', 'IRCAM', 'W64', 'MAT4', 'MAT5', 'PVF', 'XI', 'HTK', 'SDS', 'AVR', 'WAVEX', 'SD2', 'FLAC', 'CAF', 'WVE', 'OGG', 'MPC2K', 'RF64', 'MP3'] +SubType: TypeAlias = Literal['PCM_S8', 'PCM_16', 'PCM_24', 'PCM_32', 'PCM_U8', 'FLOAT', 'DOUBLE', 'ULAW', 'ALAW', 'IMA_ADPCM', 'MS_ADPCM', 'GSM610', 'VOX_ADPCM', 'NMS_ADPCM_16', 'NMS_ADPCM_24', 'NMS_ADPCM_32', 'G721_32', 'G723_24', 'G723_40', 'DWVW_12', 'DWVW_16', 'DWVW_24', 'DWVW_N', 'DPCM_8', 'DPCM_16', 'VORBIS', 'OPUS', 'ALAC_16', 'ALAC_20', 'ALAC_24', 'ALAC_32', 'MPEG_LAYER_I', 'MPEG_LAYER_II', 'MPEG_LAYER_III'] +Endian: TypeAlias = Literal['FILE', 'LITTLE', 'BIG', 'CPU'] +Dtype: TypeAlias = Literal['float64', 'float32', 'int32', 'int16'] +BitrateMode: TypeAlias = Literal['CONSTANT', 'AVERAGE', 'VARIABLE'] +OpenMode: TypeAlias = Literal['r', 'r+', 'w', 'w+', 'x', 'x+'] +FileDescriptorOrPath: TypeAlias = Union[str, int, BinaryIO, _os.PathLike[Any]] +NumpyArray: TypeAlias = npt.NDArray[Any] +AudioData: TypeAlias = npt.NDArray[Union[np.float64, np.float32, np.int32, np.int16]] +T_ndarr = TypeVar("T_ndarr", bound=NumpyArray) +Frames: TypeAlias = int +_snd: Any +_ffi: Any + +_str_types: Final[Dict[StrType, int]] = { 'title': 0x01, 'copyright': 0x02, 'software': 0x03, @@ -35,7 +54,7 @@ 'genre': 0x10, } -_formats = { +_formats: Final[Dict[Format, int]] = { 'WAV': 0x010000, # Microsoft WAV format (little endian default). 'AIFF': 0x020000, # Apple/SGI AIFF format (big endian). 'AU': 0x030000, # Sun/NeXT AU format (big endian). @@ -64,7 +83,7 @@ 'MP3': 0x230000, # MPEG-1/2 audio stream } -_subtypes = { +_subtypes: Final[Dict[SubType, int]] = { 'PCM_S8': 0x0001, # Signed 8 bit data 'PCM_16': 0x0002, # Signed 16 bit data 'PCM_24': 0x0003, # Signed 24 bit data @@ -101,7 +120,7 @@ 'MPEG_LAYER_III': 0x0082, # MPEG-2 Audio Layer III. } -_endians = { +_endians: Final[Dict[Endian, int]] = { 'FILE': 0x00000000, # Default file endian-ness. 'LITTLE': 0x10000000, # Force little endian-ness. 'BIG': 0x20000000, # Force big endian-ness. @@ -109,7 +128,7 @@ } # libsndfile doesn't specify default subtypes, these are somehow arbitrary: -_default_subtypes = { +_default_subtypes: Final[Dict[Format, SubType]] = { 'WAV': 'PCM_16', 'AIFF': 'PCM_16', 'AU': 'PCM_16', @@ -138,14 +157,14 @@ 'MP3': 'MPEG_LAYER_III', } -_ffi_types = { +_ffi_types: Final[Dict[Dtype, str]] = { 'float64': 'double', 'float32': 'float', 'int32': 'int', 'int16': 'short' } -_bitrate_modes = { +_bitrate_modes: Final[Dict[BitrateMode, int]] = { 'CONSTANT': 0, 'AVERAGE': 1, 'VARIABLE': 2, @@ -216,9 +235,59 @@ __libsndfile_version__ = __libsndfile_version__[len('libsndfile-'):] -def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=False, - fill_value=None, out=None, samplerate=None, channels=None, - format=None, subtype=None, endian=None, closefd=True): + +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + *, dtype: Literal['float64'] = 'float64', always_2d: bool = False, + fill_value: Optional[float] = None, out: None = None, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[Format] = None, subtype: Optional[SubType] = None, + endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.float64], int]: + ... + +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + *, dtype: Literal['float32'], always_2d: bool = False, + fill_value: Optional[float] = None, out: None = None, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[Format] = None, subtype: Optional[SubType] = None, + endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.float32], int]: + ... + +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + *, dtype: Literal['int32'], always_2d: bool = False, + fill_value: Optional[float] = None, out: None = None, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[Format] = None, subtype: Optional[SubType] = None, + endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.int32], int]: + ... + +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + *, dtype: Literal['int16'], always_2d: bool = False, + fill_value: Optional[float] = None, out: None = None, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[Format] = None, subtype: Optional[SubType] = None, + endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.int16], int]: + ... + +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + dtype: Dtype = 'float64', always_2d: bool = False, + fill_value: Optional[float] = None, *, out: T_ndarr, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[Format] = None, subtype: Optional[SubType] = None, + endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[T_ndarr, int]: + ... + +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + dtype: Dtype = 'float64', always_2d: bool = False, + fill_value: Optional[float] = None, out: Optional[T_ndarr] = None, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[Format] = None, subtype: Optional[SubType] = None, + endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[Union[AudioData, T_ndarr], int]: + """Provide audio data from a sound file as NumPy array. By default, the whole file is read from the beginning, but the @@ -305,12 +374,22 @@ def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=False, with SoundFile(file, 'r', samplerate, channels, subtype, endian, format, closefd) as f: frames = f._prepare_read(start, stop, frames) - data = f.read(frames, dtype, always_2d, fill_value, out) + data = f.read( + frames, + dtype=dtype, + always_2d=always_2d, + fill_value=fill_value, + out=out + ) return data, f.samplerate -def write(file, data, samplerate, subtype=None, endian=None, format=None, - closefd=True, compression_level=None, bitrate_mode=None): + +def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + format: Optional[Format] = None, closefd: bool = True, + compression_level: Optional[float] = None, + bitrate_mode: Optional[BitrateMode] = None) -> None: """Write data to a sound file. .. note:: If *file* exists, it will be truncated and overwritten! @@ -366,10 +445,59 @@ def write(file, data, samplerate, subtype=None, endian=None, format=None, f.write(data) -def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None, - dtype='float64', always_2d=False, fill_value=None, out=None, - samplerate=None, channels=None, - format=None, subtype=None, endian=None, closefd=True): + +@overload +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, + overlap: int = 0, frames: int = -1, start: int = 0, + stop: Optional[int] = None, *, dtype: Literal['float64'] = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[Format] = None, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + closefd: bool = True) -> Generator[npt.NDArray[np.float64], None, None]: + ... + +@overload +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, + overlap: int = 0, frames: int = -1, start: int = 0, + stop: Optional[int] = None, *, dtype: Literal['float32'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[Format] = None, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + closefd: bool = True) -> Generator[npt.NDArray[np.float32], None, None]: + ... + +@overload +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, + overlap: int = 0, frames: int = -1, start: int = 0, + stop: Optional[int] = None, *, dtype: Literal['int32'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[Format] = None, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + closefd: bool = True) -> Generator[npt.NDArray[np.int32], None, None]: + ... + +@overload +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, + overlap: int = 0, frames: int = -1, start: int = 0, + stop: Optional[int] = None, *, dtype: Literal['int16'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[Format] = None, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + closefd: bool = True) -> Generator[npt.NDArray[np.int16], None, None]: + ... + +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, + overlap: int = 0, frames: int = -1, start: int = 0, + stop: Optional[int] = None, dtype: Dtype = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: Optional[NumpyArray] = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[Format] = None, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + closefd: bool = True) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. By default, iteration starts at the beginning and stops at the end @@ -420,8 +548,15 @@ def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None, with SoundFile(file, 'r', samplerate, channels, subtype, endian, format, closefd) as f: frames = f._prepare_read(start, stop, frames) - for block in f.blocks(blocksize, overlap, frames, - dtype, always_2d, fill_value, out): + for block in f.blocks( + blocksize=blocksize, + overlap=overlap, + frames=frames, + dtype=dtype, + always_2d=always_2d, + fill_value=fill_value, + out=out + ): yield block @@ -477,7 +612,7 @@ def __repr__(self): return info.format(self, indented_extra_info) -def info(file, verbose=False): +def info(file: FileDescriptorOrPath, verbose: bool = False) -> _SoundFileInfo: """Returns an object with information about a `SoundFile`. Parameters @@ -488,7 +623,7 @@ def info(file, verbose=False): return _SoundFileInfo(file, verbose) -def available_formats(): +def available_formats() -> Dict[Format, str]: """Return a dictionary of available major formats. Examples @@ -506,10 +641,10 @@ def available_formats(): """ return dict(_available_formats_helper(_snd.SFC_GET_FORMAT_MAJOR_COUNT, - _snd.SFC_GET_FORMAT_MAJOR)) + _snd.SFC_GET_FORMAT_MAJOR)) # type: ignore -def available_subtypes(format=None): +def available_subtypes(format: Optional[Union[str, Format]] = None) -> Dict[SubType, str]: """Return a dictionary of available subtypes. Parameters @@ -529,10 +664,11 @@ def available_subtypes(format=None): subtypes = _available_formats_helper(_snd.SFC_GET_FORMAT_SUBTYPE_COUNT, _snd.SFC_GET_FORMAT_SUBTYPE) return dict((subtype, name) for subtype, name in subtypes - if format is None or check_format(format, subtype)) + if format is None or check_format(format, subtype)) # type: ignore -def check_format(format, subtype=None, endian=None): +def check_format(format: Union[Format, str], subtype: Optional[Union[SubType, str]] = None, + endian: Optional[Endian] = None) -> bool: """Check if the combination of format/subtype/endian is valid. Examples @@ -550,7 +686,7 @@ def check_format(format, subtype=None, endian=None): return False -def default_subtype(format): +def default_subtype(format: Union[str, Format]) -> Optional[SubType]: """Return the default subtype for a given format. Examples @@ -563,7 +699,7 @@ def default_subtype(format): """ _check_format(format) - return _default_subtypes.get(format.upper()) + return _default_subtypes.get(cast(Format, format.upper())) class SoundFile(object): @@ -574,9 +710,12 @@ class SoundFile(object): """ - def __init__(self, file, mode='r', samplerate=None, channels=None, - subtype=None, endian=None, format=None, closefd=True, - compression_level=None, bitrate_mode=None): + def __init__(self, file: FileDescriptorOrPath, mode: Optional[OpenMode] = 'r', + samplerate: Optional[int] = None, channels: Optional[int] = None, + subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + format: Optional[Format] = None, closefd: bool = True, + compression_level: Optional[float] = None, + bitrate_mode: Optional[BitrateMode] = None) -> None: """Open a sound file. If a file is opened with `mode` ``'r'`` (the default) or @@ -677,11 +816,13 @@ def __init__(self, file, mode='r', samplerate=None, channels=None, """ # resolve PathLike objects (see PEP519 for details): # can be replaced with _os.fspath(file) for Python >= 3.6 - file = file.__fspath__() if hasattr(file, '__fspath__') else file + if isinstance(file, _os.PathLike): + file = _os.fspath(file) self._name = file if mode is None: mode = getattr(file, 'mode', None) mode_int = _check_mode(mode) + mode = cast(OpenMode, mode) self._mode = mode self._compression_level = compression_level self._bitrate_mode = bitrate_mode @@ -750,7 +891,7 @@ def extra_info(self): # avoid confusion if something goes wrong before assigning self._file: _file = None - def __repr__(self): + def __repr__(self) -> str: compression_setting = (", compression_level={0}".format(self.compression_level) if self.compression_level is not None else "") compression_setting += (", bitrate_mode='{0}'".format(self.bitrate_mode) @@ -760,16 +901,16 @@ def __repr__(self): "format={0.format!r}, subtype={0.subtype!r}, " "endian={0.endian!r}{1})".format(self, compression_setting)) - def __del__(self): + def __del__(self) -> None: self.close() - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *args): + def __exit__(self, *args: Any) -> None: self.close() - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any) -> None: """Write text meta-data in the sound file through properties.""" if name in _str_types: self._check_if_closed() @@ -779,7 +920,7 @@ def __setattr__(self, name, value): else: object.__setattr__(self, name, value) - def __getattr__(self, name): + def __getattr__(self, name: str) -> Any: """Read text meta-data in the sound file through properties.""" if name in _str_types: self._check_if_closed() @@ -789,26 +930,26 @@ def __getattr__(self, name): raise AttributeError( "'SoundFile' object has no attribute {0!r}".format(name)) - def __len__(self): + def __len__(self) -> int: # Note: This is deprecated and will be removed at some point, # see https://github.com/bastibe/python-soundfile/issues/199 return self._info.frames - def __bool__(self): + def __bool__(self) -> bool: # Note: This is temporary until __len__ is removed, afterwards it # can (and should) be removed without change of behavior return True - def __nonzero__(self): + def __nonzero__(self) -> bool: # Note: This is only for compatibility with Python 2 and it shall be # removed at the same time as __bool__(). return self.__bool__() - def seekable(self): + def seekable(self) -> bool: """Return True if the file supports seeking.""" return self._info.seekable == _snd.SF_TRUE - def seek(self, frames, whence=SEEK_SET): + def seek(self, frames: int, whence: int = SEEK_SET) -> int: """Set the read/write position. Parameters @@ -849,12 +990,43 @@ def seek(self, frames, whence=SEEK_SET): _error_check(self._errorcode) return position - def tell(self): + def tell(self) -> int: """Return the current read/write position.""" return self.seek(0, SEEK_CUR) - def read(self, frames=-1, dtype='float64', always_2d=False, - fill_value=None, out=None): + + @overload + def read(self, frames: int = -1, *, dtype: Literal['float64'] = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> npt.NDArray[np.float64]: + ... + @overload + def read(self, frames: int = -1, *, dtype: Literal['float32'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> npt.NDArray[np.float32]: + ... + + @overload + def read(self, frames: int = -1, *, dtype: Literal['int32'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> npt.NDArray[np.int32]: + ... + + @overload + def read(self, frames: int = -1, *, dtype: Literal['int16'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> npt.NDArray[np.int16]: + ... + + @overload + def read(self, frames: int = -1, dtype: Dtype = ..., + always_2d: bool = False, fill_value: Optional[float] = None, + *, out: T_ndarr) -> T_ndarr: + ... + + def read(self, frames: int = -1, dtype: Dtype = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: Optional[T_ndarr] = None) -> Union[AudioData, T_ndarr]: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -935,19 +1107,21 @@ def read(self, frames=-1, dtype='float64', always_2d=False, """ if out is None: frames = self._check_frames(frames, fill_value) - out = self._create_empty_array(frames, always_2d, dtype) + actual_out = self._create_empty_array(frames, always_2d, dtype) else: if frames < 0 or frames > len(out): frames = len(out) - frames = self._array_io('read', out, frames) - if len(out) > frames: + actual_out = out + frames = self._array_io('read', actual_out, frames) + if len(actual_out) > frames: if fill_value is None: - out = out[:frames] + actual_out = actual_out[:frames] else: - out[frames:] = fill_value - return out + actual_out[frames:] = fill_value + return actual_out - def buffer_read(self, frames=-1, dtype=None): + + def buffer_read(self, frames: int = -1, dtype: Optional[Dtype] = None) -> memoryview: """Read from the file and return data as buffer object. Reads the given number of *frames* in the given data format @@ -982,7 +1156,7 @@ def buffer_read(self, frames=-1, dtype=None): assert read_frames == frames return _ffi.buffer(cdata) - def buffer_read_into(self, buffer, dtype): + def buffer_read_into(self, buffer: Union[bytearray, memoryview, Any], dtype: Dtype) -> int: """Read from the file into a given buffer object. Fills the given *buffer* with frames in the given data format @@ -1015,7 +1189,7 @@ def buffer_read_into(self, buffer, dtype): frames = self._cdata_io('read', cdata, ctype, frames) return frames - def write(self, data): + def write(self, data: AudioData) -> None: """Write audio data from a NumPy array to the file. Writes a number of frames at the read/write position to the @@ -1069,7 +1243,7 @@ def write(self, data): assert written == len(data) self._update_frames(written) - def buffer_write(self, data, dtype): + def buffer_write(self, data: Any, dtype: Dtype) -> None: """Write audio data from a buffer/bytes object to the file. Writes the contents of *data* to the file at the current @@ -1096,8 +1270,47 @@ def buffer_write(self, data, dtype): assert written == frames self._update_frames(written) - def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', - always_2d=False, fill_value=None, out=None): + + @overload + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, *, dtype: Literal['float64'] = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> Generator[npt.NDArray[np.float64], None, None]: + ... + + @overload + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, *, dtype: Literal['float32'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> Generator[npt.NDArray[np.float32], None, None]: + ... + + @overload + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, *, dtype: Literal['int32'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> Generator[npt.NDArray[np.int32], None, None]: + ... + + @overload + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, *, dtype: Literal['int16'], + always_2d: bool = False, fill_value: Optional[float] = None, + out: None = None) -> Generator[npt.NDArray[np.int16], None, None]: + ... + + + @overload + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, dtype: Dtype = ..., + always_2d: bool = False, fill_value: Optional[float] = None, + *, out: T_ndarr) -> Generator[T_ndarr, None, None]: + ... + + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, dtype: Dtype = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: Optional[T_ndarr] = None) -> Generator[Union[AudioData, T_ndarr], None, None]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1156,7 +1369,7 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', if blocksize is None: raise TypeError("One of {blocksize, out} must be specified") out_size = blocksize if fill_value is not None else min(blocksize, frames) - out = self._create_empty_array(out_size, always_2d, dtype) + actual_out = self._create_empty_array(out_size, always_2d, dtype) copy_out = True else: if blocksize is not None: @@ -1164,6 +1377,7 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', "Only one of {blocksize, out} may be specified") blocksize = len(out) copy_out = False + actual_out = out overlap_memory = None while frames > 0: @@ -1171,25 +1385,31 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', output_offset = 0 else: output_offset = len(overlap_memory) - out[:output_offset] = overlap_memory + actual_out[:output_offset] = overlap_memory toread = min(blocksize - output_offset, frames) - self.read(toread, dtype, always_2d, fill_value, out[output_offset:]) + self.read( + frames=toread, + dtype=dtype, + always_2d=always_2d, + fill_value=fill_value, + out=actual_out[output_offset:] + ) if overlap: if overlap_memory is None: - overlap_memory = np.copy(out[-overlap:]) + overlap_memory = np.copy(actual_out[-overlap:]) else: - overlap_memory[:] = out[-overlap:] + overlap_memory[:] = actual_out[-overlap:] if blocksize > frames + overlap and fill_value is None: - block = out[:frames + overlap] + block = actual_out[:frames + overlap] else: - block = out + block = actual_out yield np.copy(block) if copy_out else block frames -= toread - def truncate(self, frames=None): + def truncate(self, frames: Optional[int] = None) -> None: """Truncate the file to a given number of frames. After this command, the read/write position will be at the new @@ -1213,7 +1433,7 @@ def truncate(self, frames=None): raise LibsndfileError(err, "Error truncating the file") self._info.frames = frames - def flush(self): + def flush(self) -> None: """Write unwritten data to the file system. Data written with `write()` is not immediately written to @@ -1227,7 +1447,7 @@ def flush(self): self._check_if_closed() _snd.sf_write_sync(self._file) - def close(self): + def close(self) -> None: """Close the file. Can be called multiple times.""" if not self.closed: # be sure to flush data to disk before closing the file @@ -1491,7 +1711,7 @@ def _format_int(format, subtype, endian): elif not isinstance(subtype, (_unicode, str)): raise TypeError("Invalid subtype: {0!r}".format(subtype)) try: - result |= _subtypes[subtype.upper()] + result |= _subtypes[cast(SubType, subtype.upper())] except KeyError: raise ValueError("Unknown subtype: {0!r}".format(subtype)) if endian is None: @@ -1499,7 +1719,7 @@ def _format_int(format, subtype, endian): elif not isinstance(endian, (_unicode, str)): raise TypeError("Invalid endian-ness: {0!r}".format(endian)) try: - result |= _endians[endian.upper()] + result |= _endians[cast(Endian, endian.upper())] except KeyError: raise ValueError("Unknown endian-ness: {0!r}".format(endian)) @@ -1653,13 +1873,13 @@ class LibsndfileError(SoundFileRuntimeError): code libsndfile internal error number. """ - def __init__(self, code, prefix=""): + def __init__(self, code: int, prefix: str = "") -> None: SoundFileRuntimeError.__init__(self, code, prefix) self.code = code self.prefix = prefix @property - def error_string(self): + def error_string(self) -> str: """Raw libsndfile error message.""" if self.code: err_str = _snd.sf_error_number(self.code) @@ -1670,5 +1890,5 @@ def error_string(self): # See https://github.com/erikd/libsndfile/issues/610 for details. return "(Garbled error message from libsndfile)" - def __str__(self): + def __str__(self) -> str: return self.prefix + self.error_string From 8ddd572a7f4fda0ca9dd677a504b9f2956bf5aea Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:08:01 +0900 Subject: [PATCH 02/19] set curr default value --- soundfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/soundfile.py b/soundfile.py index 0dc7403..5a9a913 100644 --- a/soundfile.py +++ b/soundfile.py @@ -1617,6 +1617,7 @@ def _cdata_io(self, action, data, ctype, frames): """Call one of libsndfile's read/write functions.""" assert ctype in _ffi_types.values() self._check_if_closed() + curr = 0 if self.seekable(): curr = self.tell() func = getattr(_snd, 'sf_' + action + 'f_' + ctype) From b6b5d48b80250ccad076631e204b9dbba9bfaa9e Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:08:31 +0900 Subject: [PATCH 03/19] add type check workflow --- .github/workflows/python-package.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ad26484..5c4461d 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -54,3 +54,23 @@ jobs: run: pip install --editable . --verbose - name: Run tests run: python -m pytest + + type-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions/setup-python@v5 + with: + python-version: "3.9" + - name: Install requirements + run: pip install numpy pytest pyright + - name: "Workaround: Generate _soundfile.py explicitly" + run: | + pip install cffi>=1.0 + python soundfile_build.py + - name: Install editable package + run: pip install --editable . --verbose + - name: Run tests + run: python -m pyright soundfile.py From 084d875817e562572929037924304320e256b085 Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:09:29 +0900 Subject: [PATCH 04/19] fix run name --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5c4461d..16d64d5 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -72,5 +72,5 @@ jobs: python soundfile_build.py - name: Install editable package run: pip install --editable . --verbose - - name: Run tests + - name: Run type check run: python -m pyright soundfile.py From b739ada532742057e739d578e787bed1f7d92690 Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:15:46 +0900 Subject: [PATCH 05/19] use Final to typing-extensions package --- soundfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index 5a9a913..6a52440 100644 --- a/soundfile.py +++ b/soundfile.py @@ -16,8 +16,8 @@ import numpy.typing as npt from os import SEEK_SET, SEEK_CUR, SEEK_END from ctypes.util import find_library as _find_library -from typing import Any, BinaryIO, Dict, Final, Generator, Literal, Optional, Tuple, Union, TypeVar, overload, cast -from typing_extensions import TypeAlias, Self +from typing import Any, BinaryIO, Dict, Generator, Literal, Optional, Tuple, Union, TypeVar, overload, cast +from typing_extensions import TypeAlias, Self, Final from _soundfile import ffi as _ffi try: From 8c4080dd90d10dc863b051a3e039f97868f5002a Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:20:46 +0900 Subject: [PATCH 06/19] fix FileDescriptorOrPath annotation on python 3.8 and 3.7 --- soundfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/soundfile.py b/soundfile.py index 6a52440..a96a908 100644 --- a/soundfile.py +++ b/soundfile.py @@ -33,7 +33,10 @@ Dtype: TypeAlias = Literal['float64', 'float32', 'int32', 'int16'] BitrateMode: TypeAlias = Literal['CONSTANT', 'AVERAGE', 'VARIABLE'] OpenMode: TypeAlias = Literal['r', 'r+', 'w', 'w+', 'x', 'x+'] -FileDescriptorOrPath: TypeAlias = Union[str, int, BinaryIO, _os.PathLike[Any]] +if _sys.version_info >= (3, 9): + FileDescriptorOrPath: TypeAlias = Union[str, int, BinaryIO, _os.PathLike[Any]] +else: + FileDescriptorOrPath: TypeAlias = Union[str, int, BinaryIO, _os.PathLike] NumpyArray: TypeAlias = npt.NDArray[Any] AudioData: TypeAlias = npt.NDArray[Union[np.float64, np.float32, np.int32, np.int16]] T_ndarr = TypeVar("T_ndarr", bound=NumpyArray) From 43874c348f303325f61d819495146a4bb7f0e6ae Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:24:14 +0900 Subject: [PATCH 07/19] use Literal from typing-extensions for python 3.7 --- soundfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index a96a908..29f42fa 100644 --- a/soundfile.py +++ b/soundfile.py @@ -16,8 +16,8 @@ import numpy.typing as npt from os import SEEK_SET, SEEK_CUR, SEEK_END from ctypes.util import find_library as _find_library -from typing import Any, BinaryIO, Dict, Generator, Literal, Optional, Tuple, Union, TypeVar, overload, cast -from typing_extensions import TypeAlias, Self, Final +from typing import Any, BinaryIO, Dict, Generator, Optional, Tuple, Union, TypeVar, overload, cast +from typing_extensions import TypeAlias, Self, Final, Literal from _soundfile import ffi as _ffi try: From f3f2d033f545d9900c27a71a2f175e2623d6d11d Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:36:36 +0900 Subject: [PATCH 08/19] fix file end of pyrightconfig.json --- pyrightconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index 59c7273..a78bcb0 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -6,4 +6,4 @@ "**/.venv", "tests/" ] - } \ No newline at end of file +} From 9e32fce6c6ee0aa52eb95b5dc53a41ba6a891b6f Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:42:30 +0900 Subject: [PATCH 09/19] Add Type Checking section to CONTRIBUTING.rst --- CONTRIBUTING.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3722022..c4b111f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -35,6 +35,29 @@ This uses pytest_; .. _known problem: http://www.mega-nerd.com/libsndfile/api.html#open_fd +Type Checking +^^^^^^^^^^^^^ + +Type hints have been added to the codebase to support static type checking. +You can use pyright to check the types: + +.. code-block:: bash + + pip install pyright + pyright soundfile.py + +Or you can use the VS Code extension for inline type checking. + +When contributing, please maintain type hints for all public functions, methods, and classes. +Make sure to use appropriate types from the typing and typing-extensions modules. + +The following conventions are used: +- Use Literal types for enumerated string values +- Use TypeAlias for complex type definitions +- Use overloads to provide precise return type information +- Use Optional for parameters that can be None +- Use Union for values that can be different types + Coverage ^^^^^^^^ From 43e17dca6222bd41a49e4d12ddea805cf5d749da Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:46:00 +0900 Subject: [PATCH 10/19] Update CONTRIBUTING.rst --- CONTRIBUTING.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c4b111f..e0780e9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -52,6 +52,7 @@ When contributing, please maintain type hints for all public functions, methods, Make sure to use appropriate types from the typing and typing-extensions modules. The following conventions are used: + - Use Literal types for enumerated string values - Use TypeAlias for complex type definitions - Use overloads to provide precise return type information From 6589908ca47c203258d10c29262d2c6fe84950e6 Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:18:44 +0900 Subject: [PATCH 11/19] update CONTRIBUTING.rst --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e0780e9..2ebf61f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -14,7 +14,7 @@ regressions), if you add a feature, you should add tests for it as well. Set up local environment with the following commands:: - pip install numpy pytest "cffi>=1.0" + pip install numpy pytest "cffi>=1.0" typing-extensions python soundfile_build.py To run the tests, use:: From a8a77c1e8155dc029f53aad1d69a7a501a60a671 Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:48:19 +0900 Subject: [PATCH 12/19] minimize type hints --- soundfile.py | 256 +++++++++------------------------------------------ 1 file changed, 45 insertions(+), 211 deletions(-) diff --git a/soundfile.py b/soundfile.py index 29f42fa..6c20e34 100644 --- a/soundfile.py +++ b/soundfile.py @@ -12,12 +12,11 @@ import os as _os import sys as _sys -import numpy as np -import numpy.typing as npt +import numpy.typing from os import SEEK_SET, SEEK_CUR, SEEK_END from ctypes.util import find_library as _find_library -from typing import Any, BinaryIO, Dict, Generator, Optional, Tuple, Union, TypeVar, overload, cast -from typing_extensions import TypeAlias, Self, Final, Literal +from typing import Any, BinaryIO, Dict, Generator, Optional, Tuple, Union +from typing_extensions import TypeAlias, Self, Final from _soundfile import ffi as _ffi try: @@ -26,25 +25,15 @@ _unicode = str # Type aliases for specific types -StrType: TypeAlias = Literal['title', 'copyright', 'software', 'artist', 'comment', 'date', 'album', 'license', 'tracknumber', 'genre'] -Format: TypeAlias = Literal['WAV', 'AIFF', 'AU', 'RAW', 'PAF', 'SVX', 'NIST', 'VOC', 'IRCAM', 'W64', 'MAT4', 'MAT5', 'PVF', 'XI', 'HTK', 'SDS', 'AVR', 'WAVEX', 'SD2', 'FLAC', 'CAF', 'WVE', 'OGG', 'MPC2K', 'RF64', 'MP3'] -SubType: TypeAlias = Literal['PCM_S8', 'PCM_16', 'PCM_24', 'PCM_32', 'PCM_U8', 'FLOAT', 'DOUBLE', 'ULAW', 'ALAW', 'IMA_ADPCM', 'MS_ADPCM', 'GSM610', 'VOX_ADPCM', 'NMS_ADPCM_16', 'NMS_ADPCM_24', 'NMS_ADPCM_32', 'G721_32', 'G723_24', 'G723_40', 'DWVW_12', 'DWVW_16', 'DWVW_24', 'DWVW_N', 'DPCM_8', 'DPCM_16', 'VORBIS', 'OPUS', 'ALAC_16', 'ALAC_20', 'ALAC_24', 'ALAC_32', 'MPEG_LAYER_I', 'MPEG_LAYER_II', 'MPEG_LAYER_III'] -Endian: TypeAlias = Literal['FILE', 'LITTLE', 'BIG', 'CPU'] -Dtype: TypeAlias = Literal['float64', 'float32', 'int32', 'int16'] -BitrateMode: TypeAlias = Literal['CONSTANT', 'AVERAGE', 'VARIABLE'] -OpenMode: TypeAlias = Literal['r', 'r+', 'w', 'w+', 'x', 'x+'] if _sys.version_info >= (3, 9): FileDescriptorOrPath: TypeAlias = Union[str, int, BinaryIO, _os.PathLike[Any]] else: FileDescriptorOrPath: TypeAlias = Union[str, int, BinaryIO, _os.PathLike] -NumpyArray: TypeAlias = npt.NDArray[Any] -AudioData: TypeAlias = npt.NDArray[Union[np.float64, np.float32, np.int32, np.int16]] -T_ndarr = TypeVar("T_ndarr", bound=NumpyArray) -Frames: TypeAlias = int +AudioData: TypeAlias = numpy.typing.NDArray[Any] _snd: Any _ffi: Any -_str_types: Final[Dict[StrType, int]] = { +_str_types: Final[Dict[str, int]] = { 'title': 0x01, 'copyright': 0x02, 'software': 0x03, @@ -57,7 +46,7 @@ 'genre': 0x10, } -_formats: Final[Dict[Format, int]] = { +_formats: Final[Dict[str, int]] = { 'WAV': 0x010000, # Microsoft WAV format (little endian default). 'AIFF': 0x020000, # Apple/SGI AIFF format (big endian). 'AU': 0x030000, # Sun/NeXT AU format (big endian). @@ -86,7 +75,7 @@ 'MP3': 0x230000, # MPEG-1/2 audio stream } -_subtypes: Final[Dict[SubType, int]] = { +_subtypes: Final[Dict[str, int]] = { 'PCM_S8': 0x0001, # Signed 8 bit data 'PCM_16': 0x0002, # Signed 16 bit data 'PCM_24': 0x0003, # Signed 24 bit data @@ -123,7 +112,7 @@ 'MPEG_LAYER_III': 0x0082, # MPEG-2 Audio Layer III. } -_endians: Final[Dict[Endian, int]] = { +_endians: Final[Dict[str, int]] = { 'FILE': 0x00000000, # Default file endian-ness. 'LITTLE': 0x10000000, # Force little endian-ness. 'BIG': 0x20000000, # Force big endian-ness. @@ -131,7 +120,7 @@ } # libsndfile doesn't specify default subtypes, these are somehow arbitrary: -_default_subtypes: Final[Dict[Format, SubType]] = { +_default_subtypes: Final[Dict[str, str]] = { 'WAV': 'PCM_16', 'AIFF': 'PCM_16', 'AU': 'PCM_16', @@ -160,14 +149,14 @@ 'MP3': 'MPEG_LAYER_III', } -_ffi_types: Final[Dict[Dtype, str]] = { +_ffi_types: Final[Dict[str, str]] = { 'float64': 'double', 'float32': 'float', 'int32': 'int', 'int16': 'short' } -_bitrate_modes: Final[Dict[BitrateMode, int]] = { +_bitrate_modes: Final[Dict[str, int]] = { 'CONSTANT': 0, 'AVERAGE': 1, 'VARIABLE': 2, @@ -239,57 +228,12 @@ -@overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - *, dtype: Literal['float64'] = 'float64', always_2d: bool = False, - fill_value: Optional[float] = None, out: None = None, + dtype: str = 'float64', always_2d: bool = False, + fill_value: Optional[float] = None, out: Optional[AudioData] = None, samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[Format] = None, subtype: Optional[SubType] = None, - endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.float64], int]: - ... - -@overload -def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - *, dtype: Literal['float32'], always_2d: bool = False, - fill_value: Optional[float] = None, out: None = None, - samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[Format] = None, subtype: Optional[SubType] = None, - endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.float32], int]: - ... - -@overload -def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - *, dtype: Literal['int32'], always_2d: bool = False, - fill_value: Optional[float] = None, out: None = None, - samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[Format] = None, subtype: Optional[SubType] = None, - endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.int32], int]: - ... - -@overload -def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - *, dtype: Literal['int16'], always_2d: bool = False, - fill_value: Optional[float] = None, out: None = None, - samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[Format] = None, subtype: Optional[SubType] = None, - endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[npt.NDArray[np.int16], int]: - ... - -@overload -def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - dtype: Dtype = 'float64', always_2d: bool = False, - fill_value: Optional[float] = None, *, out: T_ndarr, - samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[Format] = None, subtype: Optional[SubType] = None, - endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[T_ndarr, int]: - ... - -def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - dtype: Dtype = 'float64', always_2d: bool = False, - fill_value: Optional[float] = None, out: Optional[T_ndarr] = None, - samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[Format] = None, subtype: Optional[SubType] = None, - endian: Optional[Endian] = None, closefd: bool = True) -> Tuple[Union[AudioData, T_ndarr], int]: + format: Optional[str] = None, subtype: Optional[str] = None, + endian: Optional[str] = None, closefd: bool = True) -> Tuple[AudioData, int]: """Provide audio data from a sound file as NumPy array. @@ -389,10 +333,10 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Opt def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, - format: Optional[Format] = None, closefd: bool = True, + subtype: Optional[str] = None, endian: Optional[str] = None, + format: Optional[str] = None, closefd: bool = True, compression_level: Optional[float] = None, - bitrate_mode: Optional[BitrateMode] = None) -> None: + bitrate_mode: Optional[str] = None) -> None: """Write data to a sound file. .. note:: If *file* exists, it will be truncated and overwritten! @@ -448,58 +392,13 @@ def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, f.write(data) - -@overload -def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, - overlap: int = 0, frames: int = -1, start: int = 0, - stop: Optional[int] = None, *, dtype: Literal['float64'] = 'float64', - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None, samplerate: Optional[int] = None, - channels: Optional[int] = None, format: Optional[Format] = None, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, - closefd: bool = True) -> Generator[npt.NDArray[np.float64], None, None]: - ... - -@overload -def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, - overlap: int = 0, frames: int = -1, start: int = 0, - stop: Optional[int] = None, *, dtype: Literal['float32'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None, samplerate: Optional[int] = None, - channels: Optional[int] = None, format: Optional[Format] = None, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, - closefd: bool = True) -> Generator[npt.NDArray[np.float32], None, None]: - ... - -@overload -def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, - overlap: int = 0, frames: int = -1, start: int = 0, - stop: Optional[int] = None, *, dtype: Literal['int32'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None, samplerate: Optional[int] = None, - channels: Optional[int] = None, format: Optional[Format] = None, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, - closefd: bool = True) -> Generator[npt.NDArray[np.int32], None, None]: - ... - -@overload -def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, - overlap: int = 0, frames: int = -1, start: int = 0, - stop: Optional[int] = None, *, dtype: Literal['int16'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None, samplerate: Optional[int] = None, - channels: Optional[int] = None, format: Optional[Format] = None, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, - closefd: bool = True) -> Generator[npt.NDArray[np.int16], None, None]: - ... - def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, overlap: int = 0, frames: int = -1, start: int = 0, - stop: Optional[int] = None, dtype: Dtype = 'float64', + stop: Optional[int] = None, dtype: str = 'float64', always_2d: bool = False, fill_value: Optional[float] = None, - out: Optional[NumpyArray] = None, samplerate: Optional[int] = None, - channels: Optional[int] = None, format: Optional[Format] = None, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, + out: Optional[AudioData] = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[str] = None, + subtype: Optional[str] = None, endian: Optional[str] = None, closefd: bool = True) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. @@ -626,7 +525,7 @@ def info(file: FileDescriptorOrPath, verbose: bool = False) -> _SoundFileInfo: return _SoundFileInfo(file, verbose) -def available_formats() -> Dict[Format, str]: +def available_formats() -> Dict[str, str]: """Return a dictionary of available major formats. Examples @@ -644,10 +543,10 @@ def available_formats() -> Dict[Format, str]: """ return dict(_available_formats_helper(_snd.SFC_GET_FORMAT_MAJOR_COUNT, - _snd.SFC_GET_FORMAT_MAJOR)) # type: ignore + _snd.SFC_GET_FORMAT_MAJOR)) -def available_subtypes(format: Optional[Union[str, Format]] = None) -> Dict[SubType, str]: +def available_subtypes(format: Optional[str] = None) -> Dict[str, str]: """Return a dictionary of available subtypes. Parameters @@ -667,11 +566,11 @@ def available_subtypes(format: Optional[Union[str, Format]] = None) -> Dict[SubT subtypes = _available_formats_helper(_snd.SFC_GET_FORMAT_SUBTYPE_COUNT, _snd.SFC_GET_FORMAT_SUBTYPE) return dict((subtype, name) for subtype, name in subtypes - if format is None or check_format(format, subtype)) # type: ignore + if format is None or check_format(format, subtype)) -def check_format(format: Union[Format, str], subtype: Optional[Union[SubType, str]] = None, - endian: Optional[Endian] = None) -> bool: +def check_format(format: str, subtype: Optional[str] = None, + endian: Optional[str] = None) -> bool: """Check if the combination of format/subtype/endian is valid. Examples @@ -689,7 +588,7 @@ def check_format(format: Union[Format, str], subtype: Optional[Union[SubType, st return False -def default_subtype(format: Union[str, Format]) -> Optional[SubType]: +def default_subtype(format: str) -> Optional[str]: """Return the default subtype for a given format. Examples @@ -702,7 +601,7 @@ def default_subtype(format: Union[str, Format]) -> Optional[SubType]: """ _check_format(format) - return _default_subtypes.get(cast(Format, format.upper())) + return _default_subtypes.get(format.upper()) class SoundFile(object): @@ -713,12 +612,12 @@ class SoundFile(object): """ - def __init__(self, file: FileDescriptorOrPath, mode: Optional[OpenMode] = 'r', + def __init__(self, file: FileDescriptorOrPath, mode: Optional[str] = 'r', samplerate: Optional[int] = None, channels: Optional[int] = None, - subtype: Optional[SubType] = None, endian: Optional[Endian] = None, - format: Optional[Format] = None, closefd: bool = True, + subtype: Optional[str] = None, endian: Optional[str] = None, + format: Optional[str] = None, closefd: bool = True, compression_level: Optional[float] = None, - bitrate_mode: Optional[BitrateMode] = None) -> None: + bitrate_mode: Optional[str] = None) -> None: """Open a sound file. If a file is opened with `mode` ``'r'`` (the default) or @@ -824,8 +723,9 @@ def __init__(self, file: FileDescriptorOrPath, mode: Optional[OpenMode] = 'r', self._name = file if mode is None: mode = getattr(file, 'mode', None) + if mode is None: + raise ValueError("Can not detect mode from file") # Raises ValueError explicitly for type checking. mode_int = _check_mode(mode) - mode = cast(OpenMode, mode) self._mode = mode self._compression_level = compression_level self._bitrate_mode = bitrate_mode @@ -998,38 +898,9 @@ def tell(self) -> int: return self.seek(0, SEEK_CUR) - @overload - def read(self, frames: int = -1, *, dtype: Literal['float64'] = 'float64', - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> npt.NDArray[np.float64]: - ... - @overload - def read(self, frames: int = -1, *, dtype: Literal['float32'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> npt.NDArray[np.float32]: - ... - - @overload - def read(self, frames: int = -1, *, dtype: Literal['int32'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> npt.NDArray[np.int32]: - ... - - @overload - def read(self, frames: int = -1, *, dtype: Literal['int16'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> npt.NDArray[np.int16]: - ... - - @overload - def read(self, frames: int = -1, dtype: Dtype = ..., - always_2d: bool = False, fill_value: Optional[float] = None, - *, out: T_ndarr) -> T_ndarr: - ... - - def read(self, frames: int = -1, dtype: Dtype = 'float64', + def read(self, frames: int = -1, dtype: str = 'float64', always_2d: bool = False, fill_value: Optional[float] = None, - out: Optional[T_ndarr] = None) -> Union[AudioData, T_ndarr]: + out: Optional[AudioData] = None) -> AudioData: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -1124,7 +995,7 @@ def read(self, frames: int = -1, dtype: Dtype = 'float64', return actual_out - def buffer_read(self, frames: int = -1, dtype: Optional[Dtype] = None) -> memoryview: + def buffer_read(self, frames: int = -1, dtype: Optional[str] = None) -> memoryview: """Read from the file and return data as buffer object. Reads the given number of *frames* in the given data format @@ -1159,7 +1030,7 @@ def buffer_read(self, frames: int = -1, dtype: Optional[Dtype] = None) -> memory assert read_frames == frames return _ffi.buffer(cdata) - def buffer_read_into(self, buffer: Union[bytearray, memoryview, Any], dtype: Dtype) -> int: + def buffer_read_into(self, buffer: Union[bytearray, memoryview, Any], dtype: str) -> int: """Read from the file into a given buffer object. Fills the given *buffer* with frames in the given data format @@ -1246,7 +1117,7 @@ def write(self, data: AudioData) -> None: assert written == len(data) self._update_frames(written) - def buffer_write(self, data: Any, dtype: Dtype) -> None: + def buffer_write(self, data: Any, dtype: str) -> None: """Write audio data from a buffer/bytes object to the file. Writes the contents of *data* to the file at the current @@ -1273,47 +1144,10 @@ def buffer_write(self, data: Any, dtype: Dtype) -> None: assert written == frames self._update_frames(written) - - @overload - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, *, dtype: Literal['float64'] = 'float64', - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> Generator[npt.NDArray[np.float64], None, None]: - ... - - @overload - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, *, dtype: Literal['float32'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> Generator[npt.NDArray[np.float32], None, None]: - ... - - @overload - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, *, dtype: Literal['int32'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> Generator[npt.NDArray[np.int32], None, None]: - ... - - @overload - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, *, dtype: Literal['int16'], - always_2d: bool = False, fill_value: Optional[float] = None, - out: None = None) -> Generator[npt.NDArray[np.int16], None, None]: - ... - - - @overload - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, dtype: Dtype = ..., - always_2d: bool = False, fill_value: Optional[float] = None, - *, out: T_ndarr) -> Generator[T_ndarr, None, None]: - ... - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, dtype: Dtype = 'float64', + frames: int = -1, dtype: str = 'float64', always_2d: bool = False, fill_value: Optional[float] = None, - out: Optional[T_ndarr] = None) -> Generator[Union[AudioData, T_ndarr], None, None]: + out: Optional[AudioData] = None) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1715,7 +1549,7 @@ def _format_int(format, subtype, endian): elif not isinstance(subtype, (_unicode, str)): raise TypeError("Invalid subtype: {0!r}".format(subtype)) try: - result |= _subtypes[cast(SubType, subtype.upper())] + result |= _subtypes[subtype.upper()] except KeyError: raise ValueError("Unknown subtype: {0!r}".format(subtype)) if endian is None: @@ -1723,7 +1557,7 @@ def _format_int(format, subtype, endian): elif not isinstance(endian, (_unicode, str)): raise TypeError("Invalid endian-ness: {0!r}".format(endian)) try: - result |= _endians[cast(Endian, endian.upper())] + result |= _endians[endian.upper()] except KeyError: raise ValueError("Unknown endian-ness: {0!r}".format(endian)) From 8b5de1310489eb723d177945a651e0fa9ea18419 Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:53:15 +0900 Subject: [PATCH 13/19] fix read and blocks function format --- soundfile.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/soundfile.py b/soundfile.py index 6c20e34..d7f882f 100644 --- a/soundfile.py +++ b/soundfile.py @@ -321,13 +321,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Opt with SoundFile(file, 'r', samplerate, channels, subtype, endian, format, closefd) as f: frames = f._prepare_read(start, stop, frames) - data = f.read( - frames, - dtype=dtype, - always_2d=always_2d, - fill_value=fill_value, - out=out - ) + data = f.read(frames, dtype, always_2d, fill_value, out) return data, f.samplerate @@ -450,15 +444,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, with SoundFile(file, 'r', samplerate, channels, subtype, endian, format, closefd) as f: frames = f._prepare_read(start, stop, frames) - for block in f.blocks( - blocksize=blocksize, - overlap=overlap, - frames=frames, - dtype=dtype, - always_2d=always_2d, - fill_value=fill_value, - out=out - ): + for block in f.blocks(blocksize, overlap, frames, dtype, always_2d, fill_value, out): yield block From da0e5097ce3ed89b7cf5d20977d30bcfd7d13fe9 Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:56:17 +0900 Subject: [PATCH 14/19] Fix code format --- soundfile.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/soundfile.py b/soundfile.py index d7f882f..a6470fe 100644 --- a/soundfile.py +++ b/soundfile.py @@ -1211,13 +1211,7 @@ def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, actual_out[:output_offset] = overlap_memory toread = min(blocksize - output_offset, frames) - self.read( - frames=toread, - dtype=dtype, - always_2d=always_2d, - fill_value=fill_value, - out=actual_out[output_offset:] - ) + self.read(toread, dtype, always_2d, fill_value, actual_out[output_offset:]) if overlap: if overlap_memory is None: From 64ff6dcbb6893e7e41562a700d4a7df0b8ce552d Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:06:16 +0900 Subject: [PATCH 15/19] Fix open mode error --- soundfile.py | 2 +- tests/test_soundfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index a6470fe..4cb27ad 100644 --- a/soundfile.py +++ b/soundfile.py @@ -710,7 +710,7 @@ def __init__(self, file: FileDescriptorOrPath, mode: Optional[str] = 'r', if mode is None: mode = getattr(file, 'mode', None) if mode is None: - raise ValueError("Can not detect mode from file") # Raises ValueError explicitly for type checking. + raise TypeError("Can not get `mode` from file. provided `mode` is None.") # Raises ValueError explicitly for type checking. mode_int = _check_mode(mode) self._mode = mode self._compression_level = compression_level diff --git a/tests/test_soundfile.py b/tests/test_soundfile.py index 65fde92..bba9c67 100644 --- a/tests/test_soundfile.py +++ b/tests/test_soundfile.py @@ -598,7 +598,7 @@ def test_open_w_and_wplus_with_too_few_arguments(): def test_open_with_mode_is_none(): with pytest.raises(TypeError) as excinfo: sf.SoundFile(filename_stereo, mode=None) - assert "Invalid mode: None" in str(excinfo.value) + assert "Can not get `mode` from file. provided `mode` is None." in str(excinfo.value) with open(filename_stereo, 'rb') as fobj: with sf.SoundFile(fobj, mode=None) as f: assert f.mode == 'rb' From 4b68fbf1f9e6f84c8a1d3d23129fe2f53cd8bb3d Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:09:56 +0900 Subject: [PATCH 16/19] Fix redundant code flow --- soundfile.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/soundfile.py b/soundfile.py index 4cb27ad..fe7f7b2 100644 --- a/soundfile.py +++ b/soundfile.py @@ -967,18 +967,17 @@ def read(self, frames: int = -1, dtype: str = 'float64', """ if out is None: frames = self._check_frames(frames, fill_value) - actual_out = self._create_empty_array(frames, always_2d, dtype) + out = self._create_empty_array(frames, always_2d, dtype) else: if frames < 0 or frames > len(out): frames = len(out) - actual_out = out - frames = self._array_io('read', actual_out, frames) - if len(actual_out) > frames: + frames = self._array_io('read', out, frames) + if len(out) > frames: if fill_value is None: - actual_out = actual_out[:frames] + out = out[:frames] else: - actual_out[frames:] = fill_value - return actual_out + out[frames:] = fill_value + return out def buffer_read(self, frames: int = -1, dtype: Optional[str] = None) -> memoryview: @@ -1192,7 +1191,7 @@ def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, if blocksize is None: raise TypeError("One of {blocksize, out} must be specified") out_size = blocksize if fill_value is not None else min(blocksize, frames) - actual_out = self._create_empty_array(out_size, always_2d, dtype) + out = self._create_empty_array(out_size, always_2d, dtype) copy_out = True else: if blocksize is not None: @@ -1200,7 +1199,6 @@ def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, "Only one of {blocksize, out} may be specified") blocksize = len(out) copy_out = False - actual_out = out overlap_memory = None while frames > 0: @@ -1208,21 +1206,21 @@ def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, output_offset = 0 else: output_offset = len(overlap_memory) - actual_out[:output_offset] = overlap_memory + out[:output_offset] = overlap_memory toread = min(blocksize - output_offset, frames) - self.read(toread, dtype, always_2d, fill_value, actual_out[output_offset:]) + self.read(toread, dtype, always_2d, fill_value, out[output_offset:]) if overlap: if overlap_memory is None: - overlap_memory = np.copy(actual_out[-overlap:]) + overlap_memory = np.copy(out[-overlap:]) else: - overlap_memory[:] = actual_out[-overlap:] + overlap_memory[:] = out[-overlap:] if blocksize > frames + overlap and fill_value is None: - block = actual_out[:frames + overlap] + block = out[:frames + overlap] else: - block = actual_out + block = out yield np.copy(block) if copy_out else block frames -= toread From 0f4379c16898f428f689f8c142365f747f90602d Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:12:55 +0900 Subject: [PATCH 17/19] remove comment --- soundfile.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index fe7f7b2..f17b3cc 100644 --- a/soundfile.py +++ b/soundfile.py @@ -702,8 +702,6 @@ def __init__(self, file: FileDescriptorOrPath, mode: Optional[str] = 'r', >>> assert myfile.closed """ - # resolve PathLike objects (see PEP519 for details): - # can be replaced with _os.fspath(file) for Python >= 3.6 if isinstance(file, _os.PathLike): file = _os.fspath(file) self._name = file From 29c215fb43d8b7640f8634f68586ef07dbb79294 Mon Sep 17 00:00:00 2001 From: Geson-anko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:17:05 +0900 Subject: [PATCH 18/19] remove spaces of equal from functions --- soundfile.py | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/soundfile.py b/soundfile.py index f17b3cc..12e36d2 100644 --- a/soundfile.py +++ b/soundfile.py @@ -228,12 +228,12 @@ -def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, - dtype: str = 'float64', always_2d: bool = False, - fill_value: Optional[float] = None, out: Optional[AudioData] = None, - samplerate: Optional[int] = None, channels: Optional[int] = None, - format: Optional[str] = None, subtype: Optional[str] = None, - endian: Optional[str] = None, closefd: bool = True) -> Tuple[AudioData, int]: +def read(file: FileDescriptorOrPath, frames: int=-1, start: int=0, stop: Optional[int]=None, + dtype: str='float64', always_2d: bool=False, + fill_value: Optional[float]=None, out: Optional[AudioData]=None, + samplerate: Optional[int]=None, channels: Optional[int]=None, + format: Optional[str]=None, subtype: Optional[str]=None, + endian: Optional[str]=None, closefd: bool=True) -> Tuple[AudioData, int]: """Provide audio data from a sound file as NumPy array. @@ -327,10 +327,10 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Opt def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, - subtype: Optional[str] = None, endian: Optional[str] = None, - format: Optional[str] = None, closefd: bool = True, - compression_level: Optional[float] = None, - bitrate_mode: Optional[str] = None) -> None: + subtype: Optional[str]=None, endian: Optional[str]=None, + format: Optional[str]=None, closefd: bool=True, + compression_level: Optional[float]=None, + bitrate_mode: Optional[str]=None) -> None: """Write data to a sound file. .. note:: If *file* exists, it will be truncated and overwritten! @@ -386,14 +386,14 @@ def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, f.write(data) -def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, - overlap: int = 0, frames: int = -1, start: int = 0, - stop: Optional[int] = None, dtype: str = 'float64', - always_2d: bool = False, fill_value: Optional[float] = None, - out: Optional[AudioData] = None, samplerate: Optional[int] = None, - channels: Optional[int] = None, format: Optional[str] = None, - subtype: Optional[str] = None, endian: Optional[str] = None, - closefd: bool = True) -> Generator[AudioData, None, None]: +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int]=None, + overlap: int=0, frames: int=-1, start: int=0, + stop: Optional[int]=None, dtype: str='float64', + always_2d: bool=False, fill_value: Optional[float]=None, + out: Optional[AudioData]=None, samplerate: Optional[int]=None, + channels: Optional[int]=None, format: Optional[str]=None, + subtype: Optional[str]=None, endian: Optional[str]=None, + closefd: bool=True) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. By default, iteration starts at the beginning and stops at the end @@ -500,7 +500,7 @@ def __repr__(self): return info.format(self, indented_extra_info) -def info(file: FileDescriptorOrPath, verbose: bool = False) -> _SoundFileInfo: +def info(file: FileDescriptorOrPath, verbose: bool=False) -> _SoundFileInfo: """Returns an object with information about a `SoundFile`. Parameters @@ -532,7 +532,7 @@ def available_formats() -> Dict[str, str]: _snd.SFC_GET_FORMAT_MAJOR)) -def available_subtypes(format: Optional[str] = None) -> Dict[str, str]: +def available_subtypes(format: Optional[str]=None) -> Dict[str, str]: """Return a dictionary of available subtypes. Parameters @@ -555,8 +555,8 @@ def available_subtypes(format: Optional[str] = None) -> Dict[str, str]: if format is None or check_format(format, subtype)) -def check_format(format: str, subtype: Optional[str] = None, - endian: Optional[str] = None) -> bool: +def check_format(format: str, subtype: Optional[str]=None, + endian: Optional[str]=None) -> bool: """Check if the combination of format/subtype/endian is valid. Examples @@ -598,12 +598,12 @@ class SoundFile(object): """ - def __init__(self, file: FileDescriptorOrPath, mode: Optional[str] = 'r', - samplerate: Optional[int] = None, channels: Optional[int] = None, - subtype: Optional[str] = None, endian: Optional[str] = None, - format: Optional[str] = None, closefd: bool = True, - compression_level: Optional[float] = None, - bitrate_mode: Optional[str] = None) -> None: + def __init__(self, file: FileDescriptorOrPath, mode: Optional[str]='r', + samplerate: Optional[int]=None, channels: Optional[int]=None, + subtype: Optional[str]=None, endian: Optional[str]=None, + format: Optional[str]=None, closefd: bool=True, + compression_level: Optional[float]=None, + bitrate_mode: Optional[str]=None) -> None: """Open a sound file. If a file is opened with `mode` ``'r'`` (the default) or @@ -836,7 +836,7 @@ def seekable(self) -> bool: """Return True if the file supports seeking.""" return self._info.seekable == _snd.SF_TRUE - def seek(self, frames: int, whence: int = SEEK_SET) -> int: + def seek(self, frames: int, whence: int=SEEK_SET) -> int: """Set the read/write position. Parameters @@ -882,9 +882,9 @@ def tell(self) -> int: return self.seek(0, SEEK_CUR) - def read(self, frames: int = -1, dtype: str = 'float64', - always_2d: bool = False, fill_value: Optional[float] = None, - out: Optional[AudioData] = None) -> AudioData: + def read(self, frames: int=-1, dtype: str='float64', + always_2d: bool=False, fill_value: Optional[float]=None, + out: Optional[AudioData]=None) -> AudioData: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -978,7 +978,7 @@ def read(self, frames: int = -1, dtype: str = 'float64', return out - def buffer_read(self, frames: int = -1, dtype: Optional[str] = None) -> memoryview: + def buffer_read(self, frames: int=-1, dtype: Optional[str]=None) -> memoryview: """Read from the file and return data as buffer object. Reads the given number of *frames* in the given data format @@ -1127,10 +1127,10 @@ def buffer_write(self, data: Any, dtype: str) -> None: assert written == frames self._update_frames(written) - def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, - frames: int = -1, dtype: str = 'float64', - always_2d: bool = False, fill_value: Optional[float] = None, - out: Optional[AudioData] = None) -> Generator[AudioData, None, None]: + def blocks(self, blocksize: Optional[int]=None, overlap: int=0, + frames: int=-1, dtype: str='float64', + always_2d: bool=False, fill_value: Optional[float]=None, + out: Optional[AudioData]=None) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1222,7 +1222,7 @@ def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, yield np.copy(block) if copy_out else block frames -= toread - def truncate(self, frames: Optional[int] = None) -> None: + def truncate(self, frames: Optional[int]=None) -> None: """Truncate the file to a given number of frames. After this command, the read/write position will be at the new @@ -1687,7 +1687,7 @@ class LibsndfileError(SoundFileRuntimeError): code libsndfile internal error number. """ - def __init__(self, code: int, prefix: str = "") -> None: + def __init__(self, code: int, prefix: str="") -> None: SoundFileRuntimeError.__init__(self, code, prefix) self.code = code self.prefix = prefix From 68a57caff203a09e211d7fd234c28a45b4e56973 Mon Sep 17 00:00:00 2001 From: GesonAnko <59220704+Geson-anko@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:14:15 +0900 Subject: [PATCH 19/19] Revert "remove spaces of equal from functions" This reverts commit 29c215fb43d8b7640f8634f68586ef07dbb79294. --- soundfile.py | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/soundfile.py b/soundfile.py index 12e36d2..f17b3cc 100644 --- a/soundfile.py +++ b/soundfile.py @@ -228,12 +228,12 @@ -def read(file: FileDescriptorOrPath, frames: int=-1, start: int=0, stop: Optional[int]=None, - dtype: str='float64', always_2d: bool=False, - fill_value: Optional[float]=None, out: Optional[AudioData]=None, - samplerate: Optional[int]=None, channels: Optional[int]=None, - format: Optional[str]=None, subtype: Optional[str]=None, - endian: Optional[str]=None, closefd: bool=True) -> Tuple[AudioData, int]: +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: Optional[int] = None, + dtype: str = 'float64', always_2d: bool = False, + fill_value: Optional[float] = None, out: Optional[AudioData] = None, + samplerate: Optional[int] = None, channels: Optional[int] = None, + format: Optional[str] = None, subtype: Optional[str] = None, + endian: Optional[str] = None, closefd: bool = True) -> Tuple[AudioData, int]: """Provide audio data from a sound file as NumPy array. @@ -327,10 +327,10 @@ def read(file: FileDescriptorOrPath, frames: int=-1, start: int=0, stop: Optiona def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, - subtype: Optional[str]=None, endian: Optional[str]=None, - format: Optional[str]=None, closefd: bool=True, - compression_level: Optional[float]=None, - bitrate_mode: Optional[str]=None) -> None: + subtype: Optional[str] = None, endian: Optional[str] = None, + format: Optional[str] = None, closefd: bool = True, + compression_level: Optional[float] = None, + bitrate_mode: Optional[str] = None) -> None: """Write data to a sound file. .. note:: If *file* exists, it will be truncated and overwritten! @@ -386,14 +386,14 @@ def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, f.write(data) -def blocks(file: FileDescriptorOrPath, blocksize: Optional[int]=None, - overlap: int=0, frames: int=-1, start: int=0, - stop: Optional[int]=None, dtype: str='float64', - always_2d: bool=False, fill_value: Optional[float]=None, - out: Optional[AudioData]=None, samplerate: Optional[int]=None, - channels: Optional[int]=None, format: Optional[str]=None, - subtype: Optional[str]=None, endian: Optional[str]=None, - closefd: bool=True) -> Generator[AudioData, None, None]: +def blocks(file: FileDescriptorOrPath, blocksize: Optional[int] = None, + overlap: int = 0, frames: int = -1, start: int = 0, + stop: Optional[int] = None, dtype: str = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: Optional[AudioData] = None, samplerate: Optional[int] = None, + channels: Optional[int] = None, format: Optional[str] = None, + subtype: Optional[str] = None, endian: Optional[str] = None, + closefd: bool = True) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. By default, iteration starts at the beginning and stops at the end @@ -500,7 +500,7 @@ def __repr__(self): return info.format(self, indented_extra_info) -def info(file: FileDescriptorOrPath, verbose: bool=False) -> _SoundFileInfo: +def info(file: FileDescriptorOrPath, verbose: bool = False) -> _SoundFileInfo: """Returns an object with information about a `SoundFile`. Parameters @@ -532,7 +532,7 @@ def available_formats() -> Dict[str, str]: _snd.SFC_GET_FORMAT_MAJOR)) -def available_subtypes(format: Optional[str]=None) -> Dict[str, str]: +def available_subtypes(format: Optional[str] = None) -> Dict[str, str]: """Return a dictionary of available subtypes. Parameters @@ -555,8 +555,8 @@ def available_subtypes(format: Optional[str]=None) -> Dict[str, str]: if format is None or check_format(format, subtype)) -def check_format(format: str, subtype: Optional[str]=None, - endian: Optional[str]=None) -> bool: +def check_format(format: str, subtype: Optional[str] = None, + endian: Optional[str] = None) -> bool: """Check if the combination of format/subtype/endian is valid. Examples @@ -598,12 +598,12 @@ class SoundFile(object): """ - def __init__(self, file: FileDescriptorOrPath, mode: Optional[str]='r', - samplerate: Optional[int]=None, channels: Optional[int]=None, - subtype: Optional[str]=None, endian: Optional[str]=None, - format: Optional[str]=None, closefd: bool=True, - compression_level: Optional[float]=None, - bitrate_mode: Optional[str]=None) -> None: + def __init__(self, file: FileDescriptorOrPath, mode: Optional[str] = 'r', + samplerate: Optional[int] = None, channels: Optional[int] = None, + subtype: Optional[str] = None, endian: Optional[str] = None, + format: Optional[str] = None, closefd: bool = True, + compression_level: Optional[float] = None, + bitrate_mode: Optional[str] = None) -> None: """Open a sound file. If a file is opened with `mode` ``'r'`` (the default) or @@ -836,7 +836,7 @@ def seekable(self) -> bool: """Return True if the file supports seeking.""" return self._info.seekable == _snd.SF_TRUE - def seek(self, frames: int, whence: int=SEEK_SET) -> int: + def seek(self, frames: int, whence: int = SEEK_SET) -> int: """Set the read/write position. Parameters @@ -882,9 +882,9 @@ def tell(self) -> int: return self.seek(0, SEEK_CUR) - def read(self, frames: int=-1, dtype: str='float64', - always_2d: bool=False, fill_value: Optional[float]=None, - out: Optional[AudioData]=None) -> AudioData: + def read(self, frames: int = -1, dtype: str = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: Optional[AudioData] = None) -> AudioData: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -978,7 +978,7 @@ def read(self, frames: int=-1, dtype: str='float64', return out - def buffer_read(self, frames: int=-1, dtype: Optional[str]=None) -> memoryview: + def buffer_read(self, frames: int = -1, dtype: Optional[str] = None) -> memoryview: """Read from the file and return data as buffer object. Reads the given number of *frames* in the given data format @@ -1127,10 +1127,10 @@ def buffer_write(self, data: Any, dtype: str) -> None: assert written == frames self._update_frames(written) - def blocks(self, blocksize: Optional[int]=None, overlap: int=0, - frames: int=-1, dtype: str='float64', - always_2d: bool=False, fill_value: Optional[float]=None, - out: Optional[AudioData]=None) -> Generator[AudioData, None, None]: + def blocks(self, blocksize: Optional[int] = None, overlap: int = 0, + frames: int = -1, dtype: str = 'float64', + always_2d: bool = False, fill_value: Optional[float] = None, + out: Optional[AudioData] = None) -> Generator[AudioData, None, None]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1222,7 +1222,7 @@ def blocks(self, blocksize: Optional[int]=None, overlap: int=0, yield np.copy(block) if copy_out else block frames -= toread - def truncate(self, frames: Optional[int]=None) -> None: + def truncate(self, frames: Optional[int] = None) -> None: """Truncate the file to a given number of frames. After this command, the read/write position will be at the new @@ -1687,7 +1687,7 @@ class LibsndfileError(SoundFileRuntimeError): code libsndfile internal error number. """ - def __init__(self, code: int, prefix: str="") -> None: + def __init__(self, code: int, prefix: str = "") -> None: SoundFileRuntimeError.__init__(self, code, prefix) self.code = code self.prefix = prefix