From 60a0b36559d851f170973c504fbea5e4ab458a56 Mon Sep 17 00:00:00 2001 From: Hunter Hogan Date: Wed, 29 Apr 2026 20:45:26 -0500 Subject: [PATCH 1/6] overload read and blocks functions and methods --- soundfile.py | 207 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 177 insertions(+), 30 deletions(-) diff --git a/soundfile.py b/soundfile.py index 6db0b4b6..ad752849 100644 --- a/soundfile.py +++ b/soundfile.py @@ -16,17 +16,35 @@ from collections.abc import Generator from ctypes.util import find_library as _find_library from os import SEEK_CUR, SEEK_END, SEEK_SET -from typing import Any, BinaryIO, Final, Literal, TypeAlias +from typing import Any, BinaryIO, Final, Literal, TypeAlias, overload, TypedDict import numpy -from typing_extensions import Self +from numpy import ndarray, int16, int32, float32, float64 +from numpy.typing import NDArray +from typing_extensions import Self, Unpack, TypeVar from _soundfile import ffi as _ffi FileDescriptorOrPath: TypeAlias = str | int | BinaryIO | _os.PathLike[Any] -AudioData: TypeAlias = numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.float32 | numpy.float64 | numpy.int32 | numpy.int16]] -AudioData_2d: TypeAlias = numpy.ndarray[tuple[int, int], numpy.dtype[numpy.float32 | numpy.float64 | numpy.int32 | numpy.int16]] -dtype_str: TypeAlias = Literal['float64', 'float32', 'int32', 'int16'] +AudioData: TypeAlias = NDArray[float32 | float64 | int16 | int32] +AudioData_2d: TypeAlias = ndarray[tuple[int, int], numpy.dtype[float32 | float64 | int16 | int32]] +_2d_float32: TypeAlias = ndarray[tuple[int, int], numpy.dtype[float32]] +_2d_float64: TypeAlias = ndarray[tuple[int, int], numpy.dtype[float64]] +_2d_int16: TypeAlias = ndarray[tuple[int, int], numpy.dtype[int16]] +_2d_int32: TypeAlias = ndarray[tuple[int, int], numpy.dtype[int32]] + +T_ndarray = TypeVar('T_ndarray', bound=numpy.ndarray) + +dtype_str: TypeAlias = Literal['float32', 'float64', 'int16', 'int32'] + +class kwargs_read(TypedDict, total=False): + samplerate: int | None + channels: int | None + format: str | None + subtype: str | None + endian: str | None + closefd: bool + _snd: Any _ffi: Any @@ -222,10 +240,47 @@ __libsndfile_version__ = __libsndfile_version__[len('libsndfile-'):] +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, + *, out: T_ndarray, **kwargs: Unpack[kwargs_read]) -> tuple[T_ndarray, int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[_2d_float32, int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[NDArray[float32], int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[_2d_float64, int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, + samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, + endian: str | None = None, closefd: bool = True) -> tuple[NDArray[float64], int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[_2d_int16, int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[NDArray[int16], int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[_2d_int32, int]:... +@overload +def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> tuple[NDArray[int32], int]:... def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', - always_2d: bool = False, fill_value: float | None = None, out: AudioData | AudioData_2d | None = None, + always_2d: bool = False, fill_value: float | None = None, out: T_ndarray | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> tuple[AudioData | AudioData_2d, int]: + endian: str | None = None, closefd: bool = True) -> tuple[AudioData | AudioData_2d | T_ndarray, int]: """Provide audio data from a sound file as NumPy array. @@ -313,11 +368,10 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int 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=frames, dtype=dtype, always_2d=always_2d, fill_value=fill_value, out=out) return data, f.samplerate - def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, subtype: str | None = None, endian: str | None = None, format: str | None = None, closefd: bool = True, @@ -377,14 +431,53 @@ def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, compression_level, bitrate_mode) as f: f.write(data) + +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', + always_2d: bool = False, fill_value: float | None = None, + *, out: T_ndarray, **kwargs: Unpack[kwargs_read]) -> Generator[T_ndarray]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[_2d_float32]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[NDArray[float32]]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[_2d_float64]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, + samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, + endian: str | None = None, closefd: bool = True) -> Generator[NDArray[float64]]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[_2d_int16]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[NDArray[int16]]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[_2d_int32]:... +@overload +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, + **kwargs: Unpack[kwargs_read]) -> Generator[NDArray[int32]]:... def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: AudioData | AudioData_2d | None = None, samplerate: int | None = None, + out: T_ndarray | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, endian: str | None = None, - closefd: bool = True) -> Generator[AudioData, None, None] | Generator[AudioData_2d, None, None]: + closefd: bool = True) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[T_ndarray]: """Return a generator for block-wise reading. By default, iteration starts at the beginning and stops at the end @@ -435,7 +528,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, with SoundFile(file, 'r', samplerate, channels, subtype, endian, format, closefd) as f: frames = f._prepare_read(start, stop, frames) - yield from f.blocks(blocksize, overlap, frames, dtype, always_2d, fill_value, out) + yield from f.blocks(blocksize=blocksize, overlap=overlap, frames=frames, dtype=dtype, always_2d=always_2d, fill_value=fill_value, out=out) class _SoundFileInfo: @@ -871,10 +964,36 @@ def tell(self) -> int: """Return the current read/write position.""" return self.seek(0, SEEK_CUR) - + @overload + def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, + *, out: T_ndarray) -> T_ndarray: ... + @overload + def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: Literal[True], + fill_value: float | None = None, out: None = None) -> _2d_float32:... + @overload + def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: bool = False, + fill_value: float | None = None, out: None = None) -> NDArray[float32]:... + @overload + def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], + fill_value: float | None = None, out: None = None) -> _2d_float64:... + @overload + def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', always_2d: bool = False, + fill_value: float | None = None, out: None = None) -> NDArray[float64]:... + @overload + def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], + fill_value: float | None = None, out: None = None) -> _2d_int16:... + @overload + def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: bool = False, + fill_value: float | None = None, out: None = None) -> NDArray[int16]:... + @overload + def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], + fill_value: float | None = None, out: None = None) -> _2d_int32:... + @overload + def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: bool = False, + fill_value: float | None = None, out: None = None) -> NDArray[int32]:... def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: AudioData | AudioData_2d | None = None) -> AudioData | AudioData_2d: + out: T_ndarray | None = None) -> AudioData | AudioData_2d | T_ndarray: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -955,18 +1074,18 @@ def read(self, frames: int = -1, dtype: dtype_str = 'float64', """ if out is None: frames = self._check_frames(frames, fill_value) - out = self._create_empty_array(frames, always_2d, dtype) + sound = self._create_empty_array(frames, always_2d, dtype) else: + sound = out if frames < 0 or frames > len(out): frames = len(out) - frames = self._array_io('read', out, frames) - if len(out) > frames: + frames = self._array_io('read', sound, frames) + if len(sound) > frames: if fill_value is None: - out = out[:frames] + sound = sound[:frames] else: - out[frames:] = fill_value - return out - + sound[frames:] = fill_value + return sound def buffer_read(self, frames: int = -1, dtype: dtype_str | None = None) -> memoryview: """Read from the file and return data as buffer object. @@ -1117,10 +1236,37 @@ def buffer_write(self, data: bytes, dtype: dtype_str) -> None: assert written == frames self._update_frames(written) + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', + always_2d: bool = False, fill_value: float | None = None, *, out: T_ndarray) -> Generator[T_ndarray]: ... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float32]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float32]]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', + *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float64]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float64]]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int16]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int16]]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int32]:... + @overload + def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int32]]:... def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: AudioData | AudioData_2d | None = None) -> Generator[AudioData, None, None] | Generator[AudioData_2d, None, None]: + out: T_ndarray | None = None) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[T_ndarray]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1179,13 +1325,14 @@ def blocks(self, blocksize: int | None = 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) - out = self._create_empty_array(out_size, always_2d, dtype) + sound = self._create_empty_array(out_size, always_2d, dtype) copy_out = True else: if blocksize is not None: raise TypeError( "Only one of {blocksize, out} may be specified") - blocksize = len(out) + sound = out + blocksize = len(sound) copy_out = False overlap_memory = None @@ -1194,21 +1341,21 @@ def blocks(self, blocksize: int | None = None, overlap: int = 0, output_offset = 0 else: output_offset = len(overlap_memory) - out[:output_offset] = overlap_memory + sound[: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=sound[output_offset:]) if overlap: if overlap_memory is None: - overlap_memory = np.copy(out[-overlap:]) + overlap_memory = np.copy(sound[-overlap:]) else: - overlap_memory[:] = out[-overlap:] + overlap_memory[:] = sound[-overlap:] if blocksize > frames + overlap and fill_value is None: - block = out[:frames + overlap] + block = sound[:frames + overlap] else: - block = out + block = sound yield np.copy(block) if copy_out else block frames -= toread From 02d4f5dab7829c9ca7aa32dad3be2deba6417f99 Mon Sep 17 00:00:00 2001 From: Hunter Hogan Date: Mon, 20 Jul 2026 21:00:21 -0500 Subject: [PATCH 2/6] name changes and formatting changes. 1. `import numpy as np` to match other imports. 2. imports sorted. 3. `_ndarrayT` is more similar to other names and more conventional (I think). 4. `_kwargs_read_blocks`: tells type checkers it is private. more obviously belongs in `blocks` functions. 5. move `_kwargs_read_blocks` class below the single-line expressions. 6. On `overload`, `:...` to match convention. 7. `blocks`: reformatted 3 long lines to 4 less-long lines. --- soundfile.py | 155 +++++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 73 deletions(-) diff --git a/soundfile.py b/soundfile.py index ad752849..11d2c11c 100644 --- a/soundfile.py +++ b/soundfile.py @@ -16,28 +16,33 @@ from collections.abc import Generator from ctypes.util import find_library as _find_library from os import SEEK_CUR, SEEK_END, SEEK_SET -from typing import Any, BinaryIO, Final, Literal, TypeAlias, overload, TypedDict +from typing import (Any, BinaryIO, Final, Literal, TypeAlias, TypedDict, + overload) -import numpy -from numpy import ndarray, int16, int32, float32, float64 +import numpy as np +from numpy import float32, float64, int16, int32, ndarray from numpy.typing import NDArray -from typing_extensions import Self, Unpack, TypeVar +from typing_extensions import Self, TypeVar, Unpack from _soundfile import ffi as _ffi FileDescriptorOrPath: TypeAlias = str | int | BinaryIO | _os.PathLike[Any] + +dtype_str: TypeAlias = Literal['float32', 'float64', 'int16', 'int32'] + AudioData: TypeAlias = NDArray[float32 | float64 | int16 | int32] -AudioData_2d: TypeAlias = ndarray[tuple[int, int], numpy.dtype[float32 | float64 | int16 | int32]] -_2d_float32: TypeAlias = ndarray[tuple[int, int], numpy.dtype[float32]] -_2d_float64: TypeAlias = ndarray[tuple[int, int], numpy.dtype[float64]] -_2d_int16: TypeAlias = ndarray[tuple[int, int], numpy.dtype[int16]] -_2d_int32: TypeAlias = ndarray[tuple[int, int], numpy.dtype[int32]] +AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[float32 | float64 | int16 | int32]] +_2d_float32: TypeAlias = ndarray[tuple[int, int], np.dtype[float32]] +_2d_float64: TypeAlias = ndarray[tuple[int, int], np.dtype[float64]] +_2d_int16: TypeAlias = ndarray[tuple[int, int], np.dtype[int16]] +_2d_int32: TypeAlias = ndarray[tuple[int, int], np.dtype[int32]] -T_ndarray = TypeVar('T_ndarray', bound=numpy.ndarray) +_ndarrayT = TypeVar('_ndarrayT', bound=np.ndarray) -dtype_str: TypeAlias = Literal['float32', 'float64', 'int16', 'int32'] +_snd: Any +_ffi: Any -class kwargs_read(TypedDict, total=False): +class _kwargs_read_blocks(TypedDict, total=False): samplerate: int | None channels: int | None format: str | None @@ -45,9 +50,6 @@ class kwargs_read(TypedDict, total=False): endian: str | None closefd: bool -_snd: Any -_ffi: Any - _str_types: Final[dict[str, int]] = { 'title': 0x01, 'copyright': 0x02, @@ -243,45 +245,44 @@ class kwargs_read(TypedDict, total=False): @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - *, out: T_ndarray, **kwargs: Unpack[kwargs_read]) -> tuple[T_ndarray, int]:... + *, out: _ndarrayT, **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_ndarrayT, int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[_2d_float32, int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_float32, int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[NDArray[float32], int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[NDArray[float32], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[_2d_float64, int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_float64, int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> tuple[NDArray[float64], int]:... + endian: str | None = None, closefd: bool = True) -> tuple[NDArray[float64], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[_2d_int16, int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_int16, int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[NDArray[int16], int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[NDArray[int16], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[_2d_int32, int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_int32, int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> tuple[NDArray[int32], int]:... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[NDArray[int32], int]: ... def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', - always_2d: bool = False, fill_value: float | None = None, out: T_ndarray | None = None, + always_2d: bool = False, fill_value: float | None = None, out: _ndarrayT | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> tuple[AudioData | AudioData_2d | T_ndarray, int]: - + endian: str | None = None, closefd: bool = True) -> tuple[AudioData | AudioData_2d | _ndarrayT, int]: """Provide audio data from a sound file as NumPy array. By default, the whole file is read from the beginning, but the @@ -436,48 +437,56 @@ def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - *, out: T_ndarray, **kwargs: Unpack[kwargs_read]) -> Generator[T_ndarray]:... + *, out: _ndarrayT, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_ndarrayT]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[_2d_float32]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_float32]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[NDArray[float32]]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[NDArray[float32]]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[_2d_float64]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_float64]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> Generator[NDArray[float64]]:... + endian: str | None = None, closefd: bool = True) -> Generator[NDArray[float64]]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[_2d_int16]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_int16]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[NDArray[int16]]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[NDArray[int16]]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[_2d_int32]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_int32]: ... @overload -def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, - *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[kwargs_read]) -> Generator[NDArray[int32]]:... +def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, + frames: int = -1, start: int = 0, stop: int | None = None, + *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[NDArray[int32]]: ... def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: T_ndarray | None = None, samplerate: int | None = None, + out: _ndarrayT | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, endian: str | None = None, - closefd: bool = True) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[T_ndarray]: + closefd: bool = True) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[_ndarrayT]: """Return a generator for block-wise reading. By default, iteration starts at the beginning and stops at the end @@ -966,34 +975,34 @@ def tell(self) -> int: @overload def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - *, out: T_ndarray) -> T_ndarray: ... + *, out: _ndarrayT) -> _ndarrayT: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_float32:... + fill_value: float | None = None, out: None = None) -> _2d_float32: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[float32]:... + fill_value: float | None = None, out: None = None) -> NDArray[float32]: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_float64:... + fill_value: float | None = None, out: None = None) -> _2d_float64: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[float64]:... + fill_value: float | None = None, out: None = None) -> NDArray[float64]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_int16:... + fill_value: float | None = None, out: None = None) -> _2d_int16: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[int16]:... + fill_value: float | None = None, out: None = None) -> NDArray[int16]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_int32:... + fill_value: float | None = None, out: None = None) -> _2d_int32: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[int32]:... + fill_value: float | None = None, out: None = None) -> NDArray[int32]: ... def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: T_ndarray | None = None) -> AudioData | AudioData_2d | T_ndarray: + out: _ndarrayT | None = None) -> AudioData | AudioData_2d | _ndarrayT: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -1238,35 +1247,35 @@ def buffer_write(self, data: bytes, dtype: dtype_str) -> None: @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', - always_2d: bool = False, fill_value: float | None = None, *, out: T_ndarray) -> Generator[T_ndarray]: ... + always_2d: bool = False, fill_value: float | None = None, *, out: _ndarrayT) -> Generator[_ndarrayT]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float32]:... + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float32]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float32]]:... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', - *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float64]:... + *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float64]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float64]]:... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float64]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int16]:... + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int16]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int16]]:... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int16]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int32]:... + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int32]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int32]]:... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int32]]: ... def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: T_ndarray | None = None) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[T_ndarray]: + out: _ndarrayT | None = None) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[_ndarrayT]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given From 2e66386215e8a3934507369f833d911c73fd36f9 Mon Sep 17 00:00:00 2001 From: Hunter Hogan Date: Mon, 20 Jul 2026 21:21:37 -0500 Subject: [PATCH 3/6] `ndarray[tuple[Any, ...],...` Based on guidance from Joren: 1. `_NDArray` is a bespoke implementation of NDArray with tuple[Any, ...]. 2. `_dtypeT` narrows the dtypes type checkers will allow. Named `_dtypeT` to strongly associate it with soundfile.read.dtype parameter name. --- soundfile.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/soundfile.py b/soundfile.py index 11d2c11c..10a903a4 100644 --- a/soundfile.py +++ b/soundfile.py @@ -30,6 +30,10 @@ dtype_str: TypeAlias = Literal['float32', 'float64', 'int16', 'int32'] +_dtypeT = TypeVar('_dtypeT', float32, float64, int16, int32, default=float64) + +_NDArray: TypeAlias = ndarray[tuple[Any, ...], np.dtype[_dtypeT]] + AudioData: TypeAlias = NDArray[float32 | float64 | int16 | int32] AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[float32 | float64 | int16 | int32]] _2d_float32: TypeAlias = ndarray[tuple[int, int], np.dtype[float32]] @@ -253,7 +257,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[NDArray[float32], int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_NDArray[float32], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -262,7 +266,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> tuple[NDArray[float64], int]: ... + endian: str | None = None, closefd: bool = True) -> tuple[_NDArray[float64], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -270,7 +274,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[NDArray[int16], int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_NDArray[int16], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -278,7 +282,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[NDArray[int32], int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_NDArray[int32], int]: ... def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, out: _ndarrayT | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, @@ -447,7 +451,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[NDArray[float32]]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_NDArray[float32]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -458,7 +462,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> Generator[NDArray[float64]]: ... + endian: str | None = None, closefd: bool = True) -> Generator[_NDArray[float64]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -468,7 +472,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[NDArray[int16]]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_NDArray[int16]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -478,7 +482,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[NDArray[int32]]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_NDArray[int32]]: ... def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', @@ -981,25 +985,25 @@ def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: Litera fill_value: float | None = None, out: None = None) -> _2d_float32: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[float32]: ... + fill_value: float | None = None, out: None = None) -> _NDArray[float32]: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> _2d_float64: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[float64]: ... + fill_value: float | None = None, out: None = None) -> _NDArray[float64]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> _2d_int16: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[int16]: ... + fill_value: float | None = None, out: None = None) -> _NDArray[int16]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> _2d_int32: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> NDArray[int32]: ... + fill_value: float | None = None, out: None = None) -> _NDArray[int32]: ... def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, out: _ndarrayT | None = None) -> AudioData | AudioData_2d | _ndarrayT: @@ -1253,25 +1257,25 @@ def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float32]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float32]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[float32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float64]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[float64]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[float64]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int16]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int16]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[int16]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int32]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[NDArray[int32]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[int32]]: ... def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, From 207b34dbc59bf2594b46c90c4bcafb2c155a07a9 Mon Sep 17 00:00:00 2001 From: Hunter Hogan Date: Mon, 20 Jul 2026 21:42:33 -0500 Subject: [PATCH 4/6] better code makes making better code easier. 1. Change the annotation of `AudioData` to the new `_NDArray` type. 2. That means `_dtypeT` needs a catchall, so create `_dtype`. 3. `_dtype` makes it easy to simplify the annotation `AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[float32 | float64 | int16 | int32]]` to `AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[_dtype]]`. 4. Wait a minute, if we use the TypeVar `dtypeT` in `AudioData_2d, we don't need the other four `_2d_*` TypeAlias. Change to `dtypeT`. 5. Delete the four TypeAlias and just use AudioData_2d with the correct numpy.dtype. --- soundfile.py | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/soundfile.py b/soundfile.py index 10a903a4..79373647 100644 --- a/soundfile.py +++ b/soundfile.py @@ -21,7 +21,6 @@ import numpy as np from numpy import float32, float64, int16, int32, ndarray -from numpy.typing import NDArray from typing_extensions import Self, TypeVar, Unpack from _soundfile import ffi as _ffi @@ -29,17 +28,13 @@ FileDescriptorOrPath: TypeAlias = str | int | BinaryIO | _os.PathLike[Any] dtype_str: TypeAlias = Literal['float32', 'float64', 'int16', 'int32'] - -_dtypeT = TypeVar('_dtypeT', float32, float64, int16, int32, default=float64) +_dtype: TypeAlias = float32 | float64 | int16 | int32 +_dtypeT = TypeVar('_dtypeT', float32, float64, int16, int32, _dtype, default=float64) _NDArray: TypeAlias = ndarray[tuple[Any, ...], np.dtype[_dtypeT]] -AudioData: TypeAlias = NDArray[float32 | float64 | int16 | int32] -AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[float32 | float64 | int16 | int32]] -_2d_float32: TypeAlias = ndarray[tuple[int, int], np.dtype[float32]] -_2d_float64: TypeAlias = ndarray[tuple[int, int], np.dtype[float64]] -_2d_int16: TypeAlias = ndarray[tuple[int, int], np.dtype[int16]] -_2d_int32: TypeAlias = ndarray[tuple[int, int], np.dtype[int32]] +AudioData: TypeAlias = _NDArray[_dtype] +AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[_dtypeT]] _ndarrayT = TypeVar('_ndarrayT', bound=np.ndarray) @@ -253,7 +248,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_float32, int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData_2d[float32], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, @@ -261,7 +256,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_float64, int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData_2d[float64], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, @@ -270,7 +265,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_int16, int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData_2d[int16], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, @@ -278,7 +273,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_2d_int32, int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData_2d[int32], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, @@ -446,7 +441,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_float32]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData_2d[float32]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -456,7 +451,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_float64]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData_2d[float64]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -467,7 +462,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_int16]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData_2d[int16]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -477,7 +472,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_2d_int32]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData_2d[int32]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -982,25 +977,25 @@ def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = *, out: _ndarrayT) -> _ndarrayT: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_float32: ... + fill_value: float | None = None, out: None = None) -> AudioData_2d[float32]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None) -> _NDArray[float32]: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_float64: ... + fill_value: float | None = None, out: None = None) -> AudioData_2d[float64]: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None) -> _NDArray[float64]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_int16: ... + fill_value: float | None = None, out: None = None) -> AudioData_2d[int16]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None) -> _NDArray[int16]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], - fill_value: float | None = None, out: None = None) -> _2d_int32: ... + fill_value: float | None = None, out: None = None) -> AudioData_2d[int32]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None) -> _NDArray[int32]: ... @@ -1254,25 +1249,25 @@ def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = - always_2d: bool = False, fill_value: float | None = None, *, out: _ndarrayT) -> Generator[_ndarrayT]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float32]: ... + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[float32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[float32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', - *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_float64]: ... + *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[float64]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[float64]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int16]: ... + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[int16]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[int16]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], - always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[_2d_int32]: ... + always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[int32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[int32]]: ... From af079cb2a9fa0845bd5bdc219ce82d7f4ceadd8a Mon Sep 17 00:00:00 2001 From: Hunter Hogan Date: Mon, 20 Jul 2026 22:35:51 -0500 Subject: [PATCH 5/6] better semiotics makes making better code easier. 1. Remove _NDArray and just use `AudioData` with a numpy.dtype if known or the `_dtype` catchall. 2. Replace `_ndarrayT` with `AudioData[_dtypeT]`. 3. Simplify the return annotation. 4. `_create_empty_array`: add a little typing to help the type checkers. Add (tuple) to help me. --- soundfile.py | 64 ++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/soundfile.py b/soundfile.py index 79373647..5d5b3019 100644 --- a/soundfile.py +++ b/soundfile.py @@ -31,13 +31,9 @@ _dtype: TypeAlias = float32 | float64 | int16 | int32 _dtypeT = TypeVar('_dtypeT', float32, float64, int16, int32, _dtype, default=float64) -_NDArray: TypeAlias = ndarray[tuple[Any, ...], np.dtype[_dtypeT]] - -AudioData: TypeAlias = _NDArray[_dtype] +AudioData: TypeAlias = ndarray[tuple[Any, ...], np.dtype[_dtypeT]] AudioData_2d: TypeAlias = ndarray[tuple[int, int], np.dtype[_dtypeT]] -_ndarrayT = TypeVar('_ndarrayT', bound=np.ndarray) - _snd: Any _ffi: Any @@ -244,7 +240,7 @@ class _kwargs_read_blocks(TypedDict, total=False): @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - *, out: _ndarrayT, **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_ndarrayT, int]: ... + *, out: AudioData[_dtypeT], **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData[_dtypeT], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -252,7 +248,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_NDArray[float32], int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData[float32], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -261,7 +257,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> tuple[_NDArray[float64], int]: ... + endian: str | None = None, closefd: bool = True) -> tuple[AudioData[float64], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -269,7 +265,7 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_NDArray[int16], int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData[int16], int]: ... @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None, @@ -277,11 +273,11 @@ def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int @overload def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, out: None = None, - **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[_NDArray[int32], int]: ... + **kwargs: Unpack[_kwargs_read_blocks]) -> tuple[AudioData[int32], int]: ... def read(file: FileDescriptorOrPath, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', - always_2d: bool = False, fill_value: float | None = None, out: _ndarrayT | None = None, + always_2d: bool = False, fill_value: float | None = None, out: AudioData[Any] | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> tuple[AudioData | AudioData_2d | _ndarrayT, int]: + endian: str | None = None, closefd: bool = True) -> tuple[AudioData[Any], int]: """Provide audio data from a sound file as NumPy array. By default, the whole file is read from the beginning, but the @@ -436,7 +432,7 @@ def write(file: FileDescriptorOrPath, data: AudioData, samplerate: int, def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - *, out: _ndarrayT, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_ndarrayT]: ... + *, out: AudioData[_dtypeT], **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData[_dtypeT]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -446,7 +442,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['float32'], always_2d: bool = False, fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_NDArray[float32]]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData[float32]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -457,7 +453,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in frames: int = -1, start: int = 0, stop: int | None = None, dtype: Literal['float64'] = 'float64', always_2d: bool = False, fill_value: float | None = None, out: None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, - endian: str | None = None, closefd: bool = True) -> Generator[_NDArray[float64]]: ... + endian: str | None = None, closefd: bool = True) -> Generator[AudioData[float64]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -467,7 +463,7 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int16'], always_2d: bool = False, fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_NDArray[int16]]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData[int16]]: ... @overload def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, @@ -477,15 +473,15 @@ def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: in def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, *, dtype: Literal['int32'], always_2d: bool = False, fill_value: float | None = None, - out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[_NDArray[int32]]: ... + out: None = None, **kwargs: Unpack[_kwargs_read_blocks]) -> Generator[AudioData[int32]]: ... def blocks(file: FileDescriptorOrPath, blocksize: int | None = None, overlap: int = 0, frames: int = -1, start: int = 0, stop: int | None = None, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: _ndarrayT | None = None, samplerate: int | None = None, + out: AudioData[Any] | None = None, samplerate: int | None = None, channels: int | None = None, format: str | None = None, subtype: str | None = None, endian: str | None = None, - closefd: bool = True) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[_ndarrayT]: + closefd: bool = True) -> Generator[AudioData[Any]]: """Return a generator for block-wise reading. By default, iteration starts at the beginning and stops at the end @@ -974,34 +970,34 @@ def tell(self) -> int: @overload def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - *, out: _ndarrayT) -> _ndarrayT: ... + *, out: AudioData[_dtypeT]) -> AudioData[_dtypeT]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> AudioData_2d[float32]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['float32'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> _NDArray[float32]: ... + fill_value: float | None = None, out: None = None) -> AudioData[float32]: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> AudioData_2d[float64]: ... @overload def read(self, frames: int = -1, dtype: Literal['float64'] = 'float64', always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> _NDArray[float64]: ... + fill_value: float | None = None, out: None = None) -> AudioData[float64]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> AudioData_2d[int16]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int16'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> _NDArray[int16]: ... + fill_value: float | None = None, out: None = None) -> AudioData[int16]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> AudioData_2d[int32]: ... @overload def read(self, frames: int = -1, *, dtype: Literal['int32'], always_2d: bool = False, - fill_value: float | None = None, out: None = None) -> _NDArray[int32]: ... + fill_value: float | None = None, out: None = None) -> AudioData[int32]: ... def read(self, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: _ndarrayT | None = None) -> AudioData | AudioData_2d | _ndarrayT: + out: AudioData[Any] | None = None) -> AudioData[Any]: """Read from the file and return data as NumPy array. Reads the given number of frames in the given data format @@ -1246,35 +1242,35 @@ def buffer_write(self, data: bytes, dtype: dtype_str) -> None: @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', - always_2d: bool = False, fill_value: float | None = None, *, out: _ndarrayT) -> Generator[_ndarrayT]: ... + always_2d: bool = False, fill_value: float | None = None, *, out: AudioData[_dtypeT]) -> Generator[AudioData[_dtypeT]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[float32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['float32'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[float32]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[AudioData[float32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', *, always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[float64]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: Literal['float64'] = 'float64', - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[float64]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[AudioData[float64]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[int16]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int16'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[int16]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[AudioData[int16]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], always_2d: Literal[True], fill_value: float | None = None, out: None = None) -> Generator[AudioData_2d[int32]]: ... @overload def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, *, dtype: Literal['int32'], - always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[_NDArray[int32]]: ... + always_2d: bool = False, fill_value: float | None = None, out: None = None) -> Generator[AudioData[int32]]: ... def blocks(self, blocksize: int | None = None, overlap: int = 0, frames: int = -1, dtype: dtype_str = 'float64', always_2d: bool = False, fill_value: float | None = None, - out: _ndarrayT | None = None) -> Generator[AudioData] | Generator[AudioData_2d] | Generator[_ndarrayT]: + out: AudioData[Any] | None = None) -> Generator[AudioData[Any]]: """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1548,13 +1544,13 @@ def _check_buffer(self, data, ctype): raise ValueError("Data size must be a multiple of frame size") return data, frames - def _create_empty_array(self, frames, always_2d, dtype): + def _create_empty_array(self, frames: int, always_2d: bool, dtype: dtype_str): """Create an empty array with appropriate shape.""" import numpy as np if always_2d or self.channels > 1: - shape = frames, self.channels + shape = (frames, self.channels) else: - shape = frames, + shape = (frames,) return np.empty(shape, dtype, order='C') def _check_dtype(self, dtype): From 1901db6db041de0420e7b1a57786a44cd1795911 Mon Sep 17 00:00:00 2001 From: Hunter Hogan Date: Mon, 20 Jul 2026 22:47:31 -0500 Subject: [PATCH 6/6] Use new annotations to prevent problems. Annotate `_ffi_types` with `dtype_str` to help ensure the str values stay aligned. --- soundfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/soundfile.py b/soundfile.py index 5d5b3019..172cf219 100644 --- a/soundfile.py +++ b/soundfile.py @@ -161,7 +161,7 @@ class _kwargs_read_blocks(TypedDict, total=False): 'MP3': 'MPEG_LAYER_III', } -_ffi_types: Final[dict[str, str]] = { +_ffi_types: Final[dict[dtype_str, str]] = { 'float64': 'double', 'float32': 'float', 'int32': 'int', @@ -1553,14 +1553,14 @@ def _create_empty_array(self, frames: int, always_2d: bool, dtype: dtype_str): shape = (frames,) return np.empty(shape, dtype, order='C') - def _check_dtype(self, dtype): + def _check_dtype(self, dtype) -> str: """Check if dtype string is valid and return ctype string.""" try: return _ffi_types[dtype] except KeyError: raise ValueError(f"dtype must be one of {sorted(_ffi_types.keys())!r} and not {dtype!r}") - def _array_io(self, action, array, frames): + def _array_io(self, action, array: AudioData[Any], frames: int): """Check array and call low-level IO function.""" if array.ndim not in (1,2): raise ValueError(f"Invalid shape: {array.shape!r} ({'0 dimensions not supported' if array.ndim < 1 else 'too many dimensions'})")