hunterHearsPy 1.3.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hunterhearspy-1.3.0/src/hunterHearsPy.egg-info → hunterhearspy-1.4.0}/PKG-INFO +2 -8
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/README.md +1 -7
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/pyproject.toml +1 -1
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/__init__.py +3 -1
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_arrays.py +2 -1
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_io.py +43 -47
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/dataBaskets.py +1 -1
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0/src/hunterHearsPy.egg-info}/PKG-INFO +2 -8
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_io.py +5 -5
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/LICENSE +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/setup.cfg +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_fft.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_resample.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/amplitude.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/autoRevert.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/clippingArrays.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/py.typed +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/theSSOT.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/theTypes.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/windowingFunctions.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/windowingFunctionsTensor.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/SOURCES.txt +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/dependency_links.txt +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/requires.txt +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/top_level.txt +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_arrays.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_fft.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_amplitude.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_autoRevert.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_clippingArrays.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_spectrogram.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_waveform.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_resample.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_windowingFunctions.py +0 -0
- {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_windowingFunctionsTensor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterHearsPy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Audio processing.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
Maintainer-email: Hunter Hogan <HunterHogan@pm.me>
|
|
@@ -131,13 +131,7 @@ I don't know a good way to implement user-configurable universal settings. There
|
|
|
131
131
|
- time is more generic than samples and often preferred.
|
|
132
132
|
- array is generic.
|
|
133
133
|
- when talking about a NumPy `ndarray`, write `ndarray` not array.
|
|
134
|
-
- `sampleRate`
|
|
135
|
-
- Regularly review these semiotics until the system is clear.
|
|
136
|
-
- Most of the time, `sampleRate` is descriptive: this is the sample rate. See `writeWAV`.
|
|
137
|
-
- In `readAudioFile`, the parameter is prescriptive: make this the sample rate. Even worse, the
|
|
138
|
-
attribute of the current sample rate is `readSoundFile.samplerate`, which is descriptive, of course.
|
|
139
|
-
- However, `def resampleWaveform(waveform, sampleRateDesired: float, sampleRateSource: float):` has
|
|
140
|
-
excellent semiotics because it does not use `sampleRate`.
|
|
134
|
+
- Use `sampleRate` unless I have a compelling reason not to.
|
|
141
135
|
|
|
142
136
|
### Preferred packages
|
|
143
137
|
|
|
@@ -86,13 +86,7 @@ I don't know a good way to implement user-configurable universal settings. There
|
|
|
86
86
|
- time is more generic than samples and often preferred.
|
|
87
87
|
- array is generic.
|
|
88
88
|
- when talking about a NumPy `ndarray`, write `ndarray` not array.
|
|
89
|
-
- `sampleRate`
|
|
90
|
-
- Regularly review these semiotics until the system is clear.
|
|
91
|
-
- Most of the time, `sampleRate` is descriptive: this is the sample rate. See `writeWAV`.
|
|
92
|
-
- In `readAudioFile`, the parameter is prescriptive: make this the sample rate. Even worse, the
|
|
93
|
-
attribute of the current sample rate is `readSoundFile.samplerate`, which is descriptive, of course.
|
|
94
|
-
- However, `def resampleWaveform(waveform, sampleRateDesired: float, sampleRateSource: float):` has
|
|
95
|
-
excellent semiotics because it does not use `sampleRate`.
|
|
89
|
+
- Use `sampleRate` unless I have a compelling reason not to.
|
|
96
90
|
|
|
97
91
|
### Preferred packages
|
|
98
92
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# ruff: noqa: D104
|
|
2
|
+
# TODO design and implement a namespace.
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from hunterHearsPy import theTypes as theTypes
|
|
@@ -35,7 +36,8 @@ from hunterHearsPy.autoRevert import moveToAxisOfOperation as moveToAxisOfOperat
|
|
|
35
36
|
from hunterHearsPy._fft import stft as stft, waveformSpectrogramWaveform as waveformSpectrogramWaveform
|
|
36
37
|
|
|
37
38
|
# isort: split
|
|
38
|
-
from hunterHearsPy._io import
|
|
39
|
+
from hunterHearsPy._io import (
|
|
40
|
+
getSampleRate as getSampleRate, readAudioFile as readAudioFile, spectrogramToWAV as spectrogramToWAV, writeWAV as writeWAV)
|
|
39
41
|
|
|
40
42
|
# isort: split
|
|
41
43
|
from hunterHearsPy.clippingArrays import applyHardLimit as applyHardLimit, applyHardLimitComplexValued as applyHardLimitComplexValued
|
|
@@ -54,6 +54,7 @@ def getWaveformMetadata(
|
|
|
54
54
|
sys.stderr.write(message + '\n')
|
|
55
55
|
|
|
56
56
|
for index, pathFilename in enumerate(tqdm(listPathFilenames, desc='Preparing combined array', leave=False)):
|
|
57
|
+
# NOTE Use readAudioFile with the prescribed sampleRate to get the exact length.
|
|
57
58
|
channels, lengthWaveform = readAudioFile(pathFilename, sampleRate).shape
|
|
58
59
|
dictionaryWaveformMetadata[index] = WaveformMetadata(
|
|
59
60
|
channels=channels, lengthWaveform=lengthWaveform, pathFilename=pathFilename, samplesStart=0, samplesStop=0
|
|
@@ -71,7 +72,7 @@ def getWaveformMetadata(
|
|
|
71
72
|
|
|
72
73
|
for metadata in dictionaryWaveformMetadata.values():
|
|
73
74
|
samplesPadding: int = axis['time'].size - metadata['lengthWaveform']
|
|
74
|
-
#
|
|
75
|
+
# DOCUMENT that if `samplesPadding` is odd, the extra pad-sample is added to samplesStop.
|
|
75
76
|
metadata['samplesStart'] = int(samplesPadding * multiplicandSamplesStart)
|
|
76
77
|
metadata['samplesStop'] = metadata['samplesStart'] + metadata['lengthWaveform']
|
|
77
78
|
|
|
@@ -18,18 +18,21 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from soundfile import AudioData_2d, dtype_str as Options_dtype_str
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
-
def
|
|
21
|
+
def getSampleRate(pathFilename: FileDescriptorOrPath) -> int:
|
|
22
|
+
return soundfile.info(pathFilename).samplerate
|
|
23
|
+
|
|
24
|
+
def readAudioFile(pathFilename: FileDescriptorOrPath, sampleRate: float | None = None, dtype_str: Options_dtype_str | None = None) -> Waveform:
|
|
22
25
|
"""Read an audio file and return waveform data as a NumPy array.
|
|
23
26
|
|
|
24
27
|
You can use this function to load any audio file that `soundfile` [1] supports. The returned
|
|
25
|
-
`Waveform` [2] is always shaped `(channels, samples)`. When `
|
|
28
|
+
`Waveform` [2] is always shaped `(channels, samples)`. When `sampleRate` differs from the file's
|
|
26
29
|
native sample rate, `readAudioFile` resamples using `resampleWaveform`.
|
|
27
30
|
|
|
28
31
|
Parameters
|
|
29
32
|
----------
|
|
30
33
|
pathFilename : FileDescriptorOrPath
|
|
31
34
|
Path to the audio file or a binary stream.
|
|
32
|
-
|
|
35
|
+
sampleRate : float | None = 44100
|
|
33
36
|
Target sample rate of the returned `Waveform` [2] in Hz. Defaults to `setting.sampleRate`,
|
|
34
37
|
which is probably 44100, when `None`.
|
|
35
38
|
dtype_str : Literal["float64", "float32", "int32", "int16"] | None = 'float32'
|
|
@@ -40,7 +43,7 @@ def readAudioFile(pathFilename: FileDescriptorOrPath, sampleRateDesired: float |
|
|
|
40
43
|
waveform : Waveform
|
|
41
44
|
Audio data shaped `(channels, samples)` as `setting.dtypeWaveform`.
|
|
42
45
|
"""
|
|
43
|
-
sampleRateDesired =
|
|
46
|
+
sampleRateDesired: float = sampleRate or setting.sampleRate
|
|
44
47
|
with soundfile.SoundFile(pathFilename) as readSoundFile:
|
|
45
48
|
sampleRateSource: int = readSoundFile.samplerate
|
|
46
49
|
audioData: AudioData_2d = readSoundFile.read(dtype=dtype_str or setting.dtype_str, always_2d=True) # ty:ignore[invalid-assignment] https://github.com/astral-sh/ty/issues/2799
|
|
@@ -49,6 +52,42 @@ def readAudioFile(pathFilename: FileDescriptorOrPath, sampleRateDesired: float |
|
|
|
49
52
|
|
|
50
53
|
return resampleWaveform(waveform, sampleRateDesired, sampleRateSource, axis['time'].number) # ty:ignore[no-matching-overload] https://github.com/astral-sh/ty/issues/2799
|
|
51
54
|
|
|
55
|
+
def saveOnError(arrayTarget: ndarray[tuple[Any, ...], dtype[Any]], *, identifierTarget: str = 'arrayTarget') -> PurePath:
|
|
56
|
+
pathFilename: Path = Path(tempfile.mkdtemp(prefix='hunterHearsPy'), f"{identifierTarget}_{uuid.uuid4().hex}.npy").resolve()
|
|
57
|
+
numpy.save(pathFilename, arrayTarget)
|
|
58
|
+
|
|
59
|
+
return PurePath(pathFilename)
|
|
60
|
+
|
|
61
|
+
def spectrogramToWAV(spectrogram: Spectrogram, pathFilename: FileDescriptorOrPath, lengthWaveform: int, **parametersSTFT: Unpack[Parameters_stft]) -> None:
|
|
62
|
+
"""Write a complex spectrogram to a WAV file by computing the inverse STFT.
|
|
63
|
+
|
|
64
|
+
You can use this function to reconstruct a waveform from a `Spectrogram` [1] and save
|
|
65
|
+
it directly to a WAV file. `spectrogramToWAV` calls `stft` with `inverse=True` to
|
|
66
|
+
obtain the reconstructed `Waveform` [2], then passes it to `writeWAV`.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
spectrogram : Spectrogram
|
|
71
|
+
Complex spectrogram to convert back to a waveform.
|
|
72
|
+
pathFilename : FileDescriptorOrPath
|
|
73
|
+
Destination path for the WAV file, or a binary stream.
|
|
74
|
+
lengthWaveform : int
|
|
75
|
+
Number of samples in the output waveform. The inverse STFT cannot recover the
|
|
76
|
+
original length from the spectrogram alone, so `lengthWaveform` is required.
|
|
77
|
+
sampleRate : float | None = None
|
|
78
|
+
Sample rate for the output WAV file in Hz. Defaults to `44100` when `None`.
|
|
79
|
+
**parametersSTFT : Any
|
|
80
|
+
Keyword parameters forwarded to `stft`, such as `lengthWindowingFunction` and
|
|
81
|
+
`lengthHop`.
|
|
82
|
+
|
|
83
|
+
File Overwrite and Format
|
|
84
|
+
-------------------------
|
|
85
|
+
See `writeWAV` for file overwrite behavior and output format details.
|
|
86
|
+
"""
|
|
87
|
+
waveform: Waveform = stft(spectrogram, lengthWaveform=lengthWaveform, **parametersSTFT)
|
|
88
|
+
sampleRate: float = parametersSTFT.get('sampleRate', setting.sampleRate)
|
|
89
|
+
writeWAV(pathFilename, waveform, sampleRate)
|
|
90
|
+
|
|
52
91
|
def writeWAV(pathFilename: FileDescriptorOrPath, waveform: Waveform, sampleRate: float | None = None, subtype: str | None = None) -> FileDescriptorOrPath:
|
|
53
92
|
"""Write a waveform array to a WAV file.
|
|
54
93
|
|
|
@@ -95,46 +134,3 @@ def writeWAV(pathFilename: FileDescriptorOrPath, waveform: Waveform, sampleRate:
|
|
|
95
134
|
|
|
96
135
|
soundfile.write(file=pathFilename, data=audioData, samplerate=sampleRate, subtype=subtype, format='WAV')
|
|
97
136
|
return pathFilename
|
|
98
|
-
|
|
99
|
-
def spectrogramToWAV(spectrogram: Spectrogram, pathFilename: FileDescriptorOrPath, lengthWaveform: int, **parametersSTFT: Unpack[Parameters_stft]) -> None:
|
|
100
|
-
"""Write a complex spectrogram to a WAV file by computing the inverse STFT.
|
|
101
|
-
|
|
102
|
-
You can use this function to reconstruct a waveform from a `Spectrogram` [1] and save
|
|
103
|
-
it directly to a WAV file. `spectrogramToWAV` calls `stft` with `inverse=True` to
|
|
104
|
-
obtain the reconstructed `Waveform` [2], then passes it to `writeWAV`.
|
|
105
|
-
|
|
106
|
-
Parameters
|
|
107
|
-
----------
|
|
108
|
-
spectrogram : Spectrogram
|
|
109
|
-
Complex spectrogram to convert back to a waveform.
|
|
110
|
-
pathFilename : FileDescriptorOrPath
|
|
111
|
-
Destination path for the WAV file, or a binary stream.
|
|
112
|
-
lengthWaveform : int
|
|
113
|
-
Number of samples in the output waveform. The inverse STFT cannot recover the
|
|
114
|
-
original length from the spectrogram alone, so `lengthWaveform` is required.
|
|
115
|
-
sampleRate : float | None = None
|
|
116
|
-
Sample rate for the output WAV file in Hz. Defaults to `44100` when `None`.
|
|
117
|
-
**parametersSTFT : Any
|
|
118
|
-
Keyword parameters forwarded to `stft`, such as `lengthWindowingFunction` and
|
|
119
|
-
`lengthHop`.
|
|
120
|
-
|
|
121
|
-
File Overwrite and Format
|
|
122
|
-
-------------------------
|
|
123
|
-
See `writeWAV` for file overwrite behavior and output format details.
|
|
124
|
-
|
|
125
|
-
References
|
|
126
|
-
----------
|
|
127
|
-
[1] `Spectrogram`
|
|
128
|
-
|
|
129
|
-
[2] `Waveform`
|
|
130
|
-
|
|
131
|
-
"""
|
|
132
|
-
waveform: Waveform = stft(spectrogram, lengthWaveform=lengthWaveform, **parametersSTFT)
|
|
133
|
-
sampleRate: float = parametersSTFT.get('sampleRate', setting.sampleRate)
|
|
134
|
-
writeWAV(pathFilename, waveform, sampleRate)
|
|
135
|
-
|
|
136
|
-
def saveOnError(arrayTarget: ndarray[tuple[Any, ...], dtype[Any]], *, identifierTarget: str = 'arrayTarget') -> PurePath:
|
|
137
|
-
pathFilename: Path = Path(tempfile.mkdtemp(prefix='hunterHearsPy'), f"{identifierTarget}_{uuid.uuid4().hex}.npy").resolve()
|
|
138
|
-
numpy.save(pathFilename, arrayTarget)
|
|
139
|
-
|
|
140
|
-
return PurePath(pathFilename)
|
|
@@ -58,7 +58,7 @@ class WaveformMetadata(TypedDict):
|
|
|
58
58
|
channels: int
|
|
59
59
|
lengthWaveform: int
|
|
60
60
|
pathFilename: FileDescriptorOrPath
|
|
61
|
-
#
|
|
61
|
+
# DOCUMENT If the following values were assigned directly to a `slice` object, the slice object would
|
|
62
62
|
# work as desired. https://docs.python.org/3/library/functions.html#slice Therefore, maintain this
|
|
63
63
|
# functionality, and keep the semiotics aligned: `slice(start, stop)`.
|
|
64
64
|
samplesStart: int
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterHearsPy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Audio processing.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
Maintainer-email: Hunter Hogan <HunterHogan@pm.me>
|
|
@@ -131,13 +131,7 @@ I don't know a good way to implement user-configurable universal settings. There
|
|
|
131
131
|
- time is more generic than samples and often preferred.
|
|
132
132
|
- array is generic.
|
|
133
133
|
- when talking about a NumPy `ndarray`, write `ndarray` not array.
|
|
134
|
-
- `sampleRate`
|
|
135
|
-
- Regularly review these semiotics until the system is clear.
|
|
136
|
-
- Most of the time, `sampleRate` is descriptive: this is the sample rate. See `writeWAV`.
|
|
137
|
-
- In `readAudioFile`, the parameter is prescriptive: make this the sample rate. Even worse, the
|
|
138
|
-
attribute of the current sample rate is `readSoundFile.samplerate`, which is descriptive, of course.
|
|
139
|
-
- However, `def resampleWaveform(waveform, sampleRateDesired: float, sampleRateSource: float):` has
|
|
140
|
-
excellent semiotics because it does not use `sampleRate`.
|
|
134
|
+
- Use `sampleRate` unless I have a compelling reason not to.
|
|
141
135
|
|
|
142
136
|
### Preferred packages
|
|
143
137
|
|
|
@@ -10,8 +10,8 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from soundfile import dtype_str as Options_dtype_str
|
|
12
12
|
|
|
13
|
-
# TODO add `None` to `
|
|
14
|
-
@pytest.mark.parametrize('
|
|
13
|
+
# TODO add `None` to `sampleRate`.
|
|
14
|
+
@pytest.mark.parametrize('sampleRate', (44100, 48000))
|
|
15
15
|
@pytest.mark.parametrize('pathFilename'
|
|
16
16
|
, indirect=['pathFilename']
|
|
17
17
|
, argvalues=(
|
|
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|
|
24
24
|
, ('Music_ch2_Hz44100_float32_sec20_RMS-20.wav')
|
|
25
25
|
)
|
|
26
26
|
)
|
|
27
|
-
def test_readAudioFile(pathFilename: Path,
|
|
28
|
-
actual: Waveform = readAudioFile(pathFilename,
|
|
27
|
+
def test_readAudioFile(pathFilename: Path, sampleRate: float, dtype_str: Options_dtype_str | None, expected: Waveform) -> None:
|
|
28
|
+
actual: Waveform = readAudioFile(pathFilename, sampleRate, dtype_str)
|
|
29
29
|
|
|
30
|
-
assert_array_equal(actual, expected, 'readAudioFile', pathFilename.name,
|
|
30
|
+
assert_array_equal(actual, expected, 'readAudioFile', pathFilename.name, sampleRate=sampleRate, dtype_str=dtype_str)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|