hunterHearsPy 1.4.0__tar.gz → 1.4.1__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.4.0/src/hunterHearsPy.egg-info → hunterhearspy-1.4.1}/PKG-INFO +16 -7
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/README.md +6 -1
- hunterhearspy-1.4.1/astTransformations/__init__.py +1 -0
- hunterhearspy-1.4.1/astTransformations/windowingFunctions.py +109 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/__init__.py +2 -2
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/_arrays.py +0 -1
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/_fft.py +1 -1
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/_io.py +4 -5
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/amplitude.py +4 -4
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/dataBaskets.py +1 -1
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/theSSOT.py +1 -1
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/theTypes.py +1 -1
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/windowingFunctionsTensor.py +5 -5
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1/hunterHearsPy.egg-info}/PKG-INFO +16 -7
- hunterhearspy-1.4.1/hunterHearsPy.egg-info/SOURCES.txt +40 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy.egg-info/requires.txt +8 -2
- hunterhearspy-1.4.1/hunterHearsPy.egg-info/top_level.txt +5 -0
- hunterhearspy-1.4.1/pyproject.toml +64 -0
- hunterhearspy-1.4.1/tests/__init__.py +7 -0
- hunterhearspy-1.4.1/tests/_theSSOT.py +8 -0
- hunterhearspy-1.4.1/tests/conftest.py +105 -0
- hunterhearspy-1.4.1/tests/conftestAnnex.py +38 -0
- hunterhearspy-1.4.1/tests/oldSampleData.py +42 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_old_clippingArrays.py +2 -2
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_old_spectrogram.py +1 -1
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_old_waveform.py +1 -1
- hunterhearspy-1.4.0/pyproject.toml +0 -95
- hunterhearspy-1.4.0/src/hunterHearsPy.egg-info/SOURCES.txt +0 -33
- hunterhearspy-1.4.0/src/hunterHearsPy.egg-info/top_level.txt +0 -1
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/LICENSE +0 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/_resample.py +0 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/autoRevert.py +0 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/clippingArrays.py +0 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/py.typed +0 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy/windowingFunctions.py +0 -0
- {hunterhearspy-1.4.0/src → hunterhearspy-1.4.1}/hunterHearsPy.egg-info/dependency_links.txt +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/setup.cfg +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_arrays.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_fft.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_io.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_old_amplitude.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_old_autoRevert.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_resample.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_windowingFunctions.py +0 -0
- {hunterhearspy-1.4.0 → hunterhearspy-1.4.1}/tests/test_windowingFunctionsTensor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterHearsPy
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Audio processing.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
Maintainer-email: Hunter Hogan <HunterHogan@pm.me>
|
|
@@ -17,23 +17,27 @@ Classifier: Intended Audience :: Developers
|
|
|
17
17
|
Classifier: Intended Audience :: Science/Research
|
|
18
18
|
Classifier: Natural Language :: English
|
|
19
19
|
Classifier: Operating System :: OS Independent
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
24
|
Classifier: Typing :: Typed
|
|
26
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.12
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
28
27
|
License-File: LICENSE
|
|
29
|
-
Requires-Dist: Z0Z_tools>=
|
|
30
|
-
Requires-Dist: hunterMakesPy>=0.
|
|
28
|
+
Requires-Dist: Z0Z_tools>=4.0.0
|
|
29
|
+
Requires-Dist: hunterMakesPy>=0.10.0
|
|
31
30
|
Requires-Dist: numpy
|
|
32
31
|
Requires-Dist: resampy
|
|
33
32
|
Requires-Dist: scipy
|
|
34
33
|
Requires-Dist: soundfile>=0.14.0
|
|
35
34
|
Requires-Dist: tqdm
|
|
36
35
|
Requires-Dist: typing-extensions>=4.0.0
|
|
36
|
+
Provides-Extra: development
|
|
37
|
+
Requires-Dist: astToolkit; extra == "development"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "development"
|
|
39
|
+
Requires-Dist: scipy-stubs; extra == "development"
|
|
40
|
+
Requires-Dist: types-resampy; extra == "development"
|
|
37
41
|
Provides-Extra: testing
|
|
38
42
|
Requires-Dist: pytest; extra == "testing"
|
|
39
43
|
Requires-Dist: pytest-mock; extra == "testing"
|
|
@@ -184,6 +188,11 @@ pip install hunterHearsPy
|
|
|
184
188
|
## My recovery
|
|
185
189
|
|
|
186
190
|
[](https://HunterThinks.com/support)
|
|
187
|
-
[](https://www.youtube.com/@HunterHogan)
|
|
188
191
|
|
|
189
192
|
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
193
|
+
|
|
194
|
+
By law, authors retain their copyrights unless they grant exceptions. The [Attribution-NonCommercial 4.0 International Deed](https://github.com/hunterhogan/hunterHearsPy#License-1-ov-file) outlines how you may use this work.
|
|
195
|
+
|
|
196
|
+
The restriction on commercial use (without a license) limits advantage-taking, unjust enrichment, and exploitation. You would be morally wrong if you were to enrich yourself by using this work while I am denied healthcare, housing, security of property, and the safety of my person.
|
|
197
|
+
|
|
198
|
+
To obtain and retain the right to use this work, you incur the cost of attributing the work to me. Your nominal cost might accrue as a benefit that mitigates my suffering or saves my life.
|
|
@@ -139,6 +139,11 @@ pip install hunterHearsPy
|
|
|
139
139
|
## My recovery
|
|
140
140
|
|
|
141
141
|
[](https://HunterThinks.com/support)
|
|
142
|
-
[](https://www.youtube.com/@HunterHogan)
|
|
143
142
|
|
|
144
143
|
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
144
|
+
|
|
145
|
+
By law, authors retain their copyrights unless they grant exceptions. The [Attribution-NonCommercial 4.0 International Deed](https://github.com/hunterhogan/hunterHearsPy#License-1-ov-file) outlines how you may use this work.
|
|
146
|
+
|
|
147
|
+
The restriction on commercial use (without a license) limits advantage-taking, unjust enrichment, and exploitation. You would be morally wrong if you were to enrich yourself by using this work while I am denied healthcare, housing, security of property, and the safety of my person.
|
|
148
|
+
|
|
149
|
+
To obtain and retain the right to use this work, you incur the cost of attributing the work to me. Your nominal cost might accrue as a benefit that mitigates my suffering or saves my life.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ruff:ignore[undocumented-public-package]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Generate PyTorch tensor windowing functions from NumPy windowing functions.
|
|
2
|
+
|
|
3
|
+
This module programmatically creates PyTorch tensor versions of windowing functions by transforming
|
|
4
|
+
existing functions from the `windowingFunctions` module. It generates functions that return PyTorch
|
|
5
|
+
tensors instead of NumPy arrays.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from astToolkit import Be, Make, NodeTourist, Then
|
|
10
|
+
from astToolkit.changeDef import makeDictionaryFunctionDef
|
|
11
|
+
from astToolkit.filesystem import parseLogicalPath2astModule, write_astModule
|
|
12
|
+
from hunterHearsPy.theSSOT import settingsPackage
|
|
13
|
+
from hunterMakesPy import raiseIfNone
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
import ast
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
moduleDestination = 'windowingFunctionsTensor'
|
|
21
|
+
moduleSource = 'windowingFunctions'
|
|
22
|
+
|
|
23
|
+
pathFilenameDestination: Path = settingsPackage.pathPackage / (moduleDestination + settingsPackage.fileExtension)
|
|
24
|
+
pathFilenameSource: Path = pathFilenameDestination.with_stem(moduleSource)
|
|
25
|
+
|
|
26
|
+
def make_windowingFunctionsTensor() -> None:
|
|
27
|
+
"""Generate PyTorch tensor windowing functions from NumPy windowing functions."""
|
|
28
|
+
listImports: list[ast.stmt] = [Make.ImportFrom('__future__', [Make.alias('annotations')])
|
|
29
|
+
, Make.ImportFrom('typing', [Make.alias('TYPE_CHECKING')])]
|
|
30
|
+
listBody: list[ast.stmt] = []
|
|
31
|
+
|
|
32
|
+
listBody.append(Make.If(test=Make.Name('TYPE_CHECKING') # ruff: ignore[repeated-append]
|
|
33
|
+
, body=[Make.ImportFrom('torch.types', [Make.alias('Device')])
|
|
34
|
+
, Make.ImportFrom('typing', [Make.alias('Any')])
|
|
35
|
+
, Make.ImportFrom(settingsPackage.identifierPackage + '.theTypes', [Make.alias('callableReturnsNDArray')])
|
|
36
|
+
]))
|
|
37
|
+
listBody.append(Make.FunctionDef(
|
|
38
|
+
'_convertToTensor'
|
|
39
|
+
, Make.arguments(vararg=Make.arg('arguments', annotation=Make.Name('Any'))
|
|
40
|
+
, kwonlyargs=[Make.arg('callableTarget', annotation=Make.Name('callableReturnsNDArray'))
|
|
41
|
+
, Make.arg('device', annotation=Make.BitOr.join([Make.Name('Device'), Make.Constant(None)]))
|
|
42
|
+
, Make.arg('dtype', annotation=Make.BitOr.join([Make.Attribute(Make.Name('torch'), 'dtype'), Make.Constant(None)]))]
|
|
43
|
+
, kw_defaults=[None, Make.Constant(None), Make.Constant(None)]
|
|
44
|
+
, kwarg=Make.arg('keywordArguments', annotation=Make.Name('Any'))
|
|
45
|
+
)
|
|
46
|
+
, body=[Make.Assign([Make.Name('arrayTarget', ast.Store())]
|
|
47
|
+
, value=Make.Call(Make.Name('callableTarget')
|
|
48
|
+
, listParameters=[Make.Starred(value=Make.Name('arguments'))]
|
|
49
|
+
, list_keyword=[Make.keyword(None, value=Make.Name('keywordArguments'))])
|
|
50
|
+
)
|
|
51
|
+
, Make.If(test=Make.Compare(left=Make.Name('device'), ops=[Make.Is()], comparators=[Make.Constant(None)])
|
|
52
|
+
, body=[Make.Assign([Make.Name('device', ast.Store())], value=Make.Call(Make.Attribute(Make.Name('torch'), 'device'), list_keyword=[Make.keyword('device', value=Make.Constant('cpu'))]))]
|
|
53
|
+
)
|
|
54
|
+
, Make.Return(Make.Call(Make.Attribute(Make.Name('torch'), 'tensor'), list_keyword=[
|
|
55
|
+
Make.keyword('data', value=Make.Name('arrayTarget'))
|
|
56
|
+
, Make.keyword('dtype', value=Make.Or.join([Make.Name('dtype'), Make.Attribute(Make.Name('torch'), 'float32')]))
|
|
57
|
+
, Make.keyword('device', value=Make.Name('device'))
|
|
58
|
+
]))
|
|
59
|
+
]
|
|
60
|
+
, returns=Make.Attribute(Make.Name('torch'), 'Tensor')
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
dictionaryFunctionDef: dict[str, ast.FunctionDef] = makeDictionaryFunctionDef(parseLogicalPath2astModule('.'.join([settingsPackage.identifierPackage, moduleSource])))
|
|
64
|
+
|
|
65
|
+
ImaIndent: str = ' ' * 4
|
|
66
|
+
ImaReturnsSection: str = f"\n{ImaIndent}Returns"
|
|
67
|
+
docstringDevice: str = f"{ImaIndent}device : Device = torch.device(device='cpu')\n{ImaIndent}{ImaIndent}PyTorch device for `Tensor`.\n"
|
|
68
|
+
docstringDtype: str = f"{ImaIndent}dtype : torch.dtype = torch.float32\n{ImaIndent}{ImaIndent}PyTorch data type for `Tensor`.\n"
|
|
69
|
+
|
|
70
|
+
for callableIdentifier, astFunctionDef in dictionaryFunctionDef.items():
|
|
71
|
+
if callableIdentifier.startswith('_'):
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
docstring: ast.Expr = Make.Expr(Make.Constant(
|
|
75
|
+
raiseIfNone(ast.get_docstring(astFunctionDef, clean=False), errorMessage="Where's the windowing function docstring?")
|
|
76
|
+
.replace('\t', ImaIndent).replace(ImaReturnsSection, docstringDevice + docstringDtype + ImaReturnsSection)))
|
|
77
|
+
|
|
78
|
+
listImports.append(Make.ImportFrom(settingsPackage.identifierPackage, [Make.alias(callableIdentifier)]))
|
|
79
|
+
|
|
80
|
+
argumentSpecification: ast.arguments = raiseIfNone(NodeTourist(Be.arguments, Then.extractIt).captureLastMatch(astFunctionDef))
|
|
81
|
+
args: list[ast.expr] = [Make.Name(ast_arg.arg) for ast_arg in [*argumentSpecification.args, *argumentSpecification.kwonlyargs]]
|
|
82
|
+
|
|
83
|
+
list_keyword: list[ast.keyword] = [Make.keyword('callableTarget', Make.Name(callableIdentifier))
|
|
84
|
+
, Make.keyword('device', Make.Name('device'))
|
|
85
|
+
, Make.keyword('dtype', Make.Name('dtype'))]
|
|
86
|
+
if argumentSpecification.kwarg:
|
|
87
|
+
list_keyword.append(Make.keyword(None, value=Make.Name(argumentSpecification.kwarg.arg)))
|
|
88
|
+
|
|
89
|
+
argumentSpecification.args.append(Make.arg('device', annotation=Make.BitOr.join([Make.Name('Device'), Make.Constant(None)])))
|
|
90
|
+
argumentSpecification.defaults.append(Make.Constant(None))
|
|
91
|
+
argumentSpecification.args.append(Make.arg('dtype', annotation=Make.BitOr.join([Make.Attribute(Make.Name('torch'), 'dtype'), Make.Constant(None)])))
|
|
92
|
+
argumentSpecification.defaults.append(Make.Constant(None))
|
|
93
|
+
|
|
94
|
+
listBody.append(Make.FunctionDef(callableIdentifier + 'Tensor'
|
|
95
|
+
, argumentSpecification
|
|
96
|
+
, body=[docstring
|
|
97
|
+
, Make.Return(Make.Call(Make.Name('_convertToTensor'), listParameters=args, list_keyword=list_keyword))]
|
|
98
|
+
, returns=Make.Attribute(Make.Name('torch'), 'Tensor')
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
listImports.append(Make.Import('torch'))
|
|
102
|
+
|
|
103
|
+
docstringModule = Make.Expr(Make.Constant("""Create PyTorch tensor windowing functions."""))
|
|
104
|
+
astModule = Make.Module(body=[docstringModule, *listImports, *listBody])
|
|
105
|
+
|
|
106
|
+
write_astModule(astModule, pathFilenameDestination, identifierPackage=settingsPackage.identifierPackage)
|
|
107
|
+
|
|
108
|
+
if __name__ == '__main__':
|
|
109
|
+
make_windowingFunctionsTensor()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ruff:
|
|
1
|
+
# ruff:file-ignore[undocumented-public-package]
|
|
2
2
|
# TODO design and implement a namespace.
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ from hunterHearsPy.windowingFunctions import cosineWings as cosineWings, equalPo
|
|
|
10
10
|
# isort: split
|
|
11
11
|
from contextlib import suppress
|
|
12
12
|
|
|
13
|
-
with suppress(ModuleNotFoundError): #
|
|
13
|
+
with suppress(ModuleNotFoundError): # ruff:ignore[non-empty-init-module]
|
|
14
14
|
from hunterHearsPy.windowingFunctionsTensor import (
|
|
15
15
|
cosineWingsTensor as cosineWingsTensor, equalPowerTensor as equalPowerTensor, halfsineTensor as halfsineTensor,
|
|
16
16
|
tukeyTensor as tukeyTensor)
|
|
@@ -71,7 +71,7 @@ def stft(
|
|
|
71
71
|
if arrayTarget.ndim == 2:
|
|
72
72
|
arrayFloating: WaveformFloating = amplitudeIntegerToFloating(arrayTarget)
|
|
73
73
|
elif numpy.issubdtype(arrayTarget.dtype, complexfloating) and (lengthWaveform < 1):
|
|
74
|
-
from hunterHearsPy._io import saveOnError #
|
|
74
|
+
from hunterHearsPy._io import saveOnError # ruff:ignore[import-outside-top-level]
|
|
75
75
|
|
|
76
76
|
pathFilename: PurePath = saveOnError(arrayTarget)
|
|
77
77
|
message: str = (
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from hunterHearsPy import amplitudeToSoundfile, getAxis, resampleWaveform, setting, stft
|
|
4
|
-
from hunterMakesPy.
|
|
4
|
+
from hunterMakesPy.kitFilesystem import makeDirectorySafely
|
|
5
5
|
from pathlib import Path, PurePath
|
|
6
6
|
from soundfile import AudioData
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
|
-
from typing_extensions import Unpack
|
|
9
8
|
import numpy
|
|
10
9
|
import soundfile
|
|
11
10
|
import tempfile
|
|
@@ -16,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
16
15
|
from hunterHearsPy.theTypes import FileDescriptorOrPath, Parameters_stft, Spectrogram, Waveform
|
|
17
16
|
from numpy import dtype, ndarray
|
|
18
17
|
from soundfile import AudioData_2d, dtype_str as Options_dtype_str
|
|
19
|
-
from typing import Any
|
|
18
|
+
from typing import Any, Unpack
|
|
20
19
|
|
|
21
20
|
def getSampleRate(pathFilename: FileDescriptorOrPath) -> int:
|
|
22
21
|
return soundfile.info(pathFilename).samplerate
|
|
@@ -46,11 +45,11 @@ def readAudioFile(pathFilename: FileDescriptorOrPath, sampleRate: float | None =
|
|
|
46
45
|
sampleRateDesired: float = sampleRate or setting.sampleRate
|
|
47
46
|
with soundfile.SoundFile(pathFilename) as readSoundFile:
|
|
48
47
|
sampleRateSource: int = readSoundFile.samplerate
|
|
49
|
-
audioData: AudioData_2d = readSoundFile.read(dtype=dtype_str or setting.dtype_str, always_2d=True)
|
|
48
|
+
audioData: AudioData_2d = readSoundFile.read(dtype=dtype_str or setting.dtype_str, always_2d=True)
|
|
50
49
|
axis: dict[str, AxisMetadata] = getAxis()
|
|
51
50
|
waveform: Waveform = audioData.transpose((axis['time'].number, axis['channel'].number))
|
|
52
51
|
|
|
53
|
-
return resampleWaveform(waveform, sampleRateDesired, sampleRateSource, axis['time'].number)
|
|
52
|
+
return resampleWaveform(waveform, sampleRateDesired, sampleRateSource, axis['time'].number)
|
|
54
53
|
|
|
55
54
|
def saveOnError(arrayTarget: ndarray[tuple[Any, ...], dtype[Any]], *, identifierTarget: str = 'arrayTarget') -> PurePath:
|
|
56
55
|
pathFilename: Path = Path(tempfile.mkdtemp(prefix='hunterHearsPy'), f"{identifierTarget}_{uuid.uuid4().hex}.npy").resolve()
|
|
@@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
|
|
38
38
|
bits: Final[int] = 8
|
|
39
39
|
|
|
40
40
|
def amplitudeIntegerToFloating(arrayTarget: ndarray[形Shape, dtype[integer[Any]]]) -> ndarray[形Shape, dtype[floating[Any]]]:
|
|
41
|
-
"""The original purpose of this function was to convert integer PCM waveforms passed to `stft`."""
|
|
41
|
+
"""The original purpose of this function was to convert integer PCM waveforms passed to `stft`."""
|
|
42
42
|
amplitudeMaximum: int = 2 ** ((arrayTarget.dtype.itemsize * bits) - 1) # This works for signed (2s complement) and unsigned because the MSD is the sign, right?
|
|
43
43
|
# TODO Why is this fixed at float32? Can/ought I to derive it or get it from `setting` or something?
|
|
44
44
|
Z0Z_floatWidth = float32
|
|
@@ -96,12 +96,12 @@ def amplitudeToSoundfile(arrayTarget: ndarray[形Shape, dtype[Any]]) -> AudioDat
|
|
|
96
96
|
|
|
97
97
|
elif numpy.issubdtype(arrayTarget.dtype, numpy.floating):
|
|
98
98
|
dtypeSoundfileFloating: frozenset[dtype[floating[Any]]] = frozenset(filter(lambda _dtype: numpy.issubdtype(_dtype, floating), dtypeSoundfile))
|
|
99
|
-
dtypeNewFloating: dtype[floating[Any]] = min(min(arrayTarget.dtype, *dtypeSoundfileFloating), max(dtypeSoundfileFloating)) #
|
|
99
|
+
dtypeNewFloating: dtype[floating[Any]] = min(min(arrayTarget.dtype, *dtypeSoundfileFloating), max(dtypeSoundfileFloating)) # ruff:ignore[nested-min-max]
|
|
100
100
|
arraySoundfile = numpy.astype(arrayTarget, dtypeNewFloating, copy=False)
|
|
101
101
|
|
|
102
102
|
elif numpy.issubdtype(arrayTarget.dtype, numpy.integer):
|
|
103
103
|
dtypesInteger: frozenset[dtype[integer[Any]]] = frozenset(filter(lambda _dtype: numpy.issubdtype(_dtype, integer), dtypeSoundfile))
|
|
104
|
-
dtypeNewInteger: dtype[integer[Any]] = min(min(arrayTarget.dtype, *dtypesInteger), max(dtypesInteger)) #
|
|
104
|
+
dtypeNewInteger: dtype[integer[Any]] = min(min(arrayTarget.dtype, *dtypesInteger), max(dtypesInteger)) # ruff:ignore[nested-min-max]
|
|
105
105
|
|
|
106
106
|
amplitudeMaximumTarget: int = 2 ** ((arrayTarget.dtype.itemsize * bits) - 1)
|
|
107
107
|
amplitudeMaximumNew: int = 2 ** ((dtypeNewInteger.itemsize * bits) - 1)
|
|
@@ -118,7 +118,7 @@ def amplitudeToSoundfile(arrayTarget: ndarray[形Shape, dtype[Any]]) -> AudioDat
|
|
|
118
118
|
|
|
119
119
|
arraySoundfile = arraySoundfile.astype(dtypeNewInteger, copy=True) # pyright: ignore[reportAssignmentType]
|
|
120
120
|
else:
|
|
121
|
-
from hunterHearsPy._io import saveOnError #
|
|
121
|
+
from hunterHearsPy._io import saveOnError # ruff:ignore[import-outside-top-level]
|
|
122
122
|
|
|
123
123
|
pathFilename: PurePath = saveOnError(arrayTarget)
|
|
124
124
|
message: str = (
|
|
@@ -10,13 +10,13 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from torch.types import Device
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
def _convertToTensor(*arguments: Any, callableTarget: callableReturnsNDArray, device: Device | None=None, dtype: torch.dtype | None=None, **keywordArguments: Any) -> torch.Tensor:
|
|
13
|
+
def _convertToTensor(*arguments: Any, callableTarget: callableReturnsNDArray, device: Device | None = None, dtype: torch.dtype | None = None, **keywordArguments: Any) -> torch.Tensor:
|
|
14
14
|
arrayTarget = callableTarget(*arguments, **keywordArguments)
|
|
15
15
|
if device is None:
|
|
16
16
|
device = torch.device(device='cpu')
|
|
17
17
|
return torch.tensor(data=arrayTarget, dtype=dtype or torch.float32, device=device)
|
|
18
18
|
|
|
19
|
-
def cosineWingsTensor(lengthSupport: int, ratioTaper: float=0.1, device: Device | None=None, dtype: torch.dtype | None=None) -> torch.Tensor:
|
|
19
|
+
def cosineWingsTensor(lengthSupport: int, ratioTaper: float = 0.1, device: Device | None = None, dtype: torch.dtype | None = None) -> torch.Tensor:
|
|
20
20
|
"""Generate a cosine-tapered windowingFunction with a flat center and tapered ends.
|
|
21
21
|
|
|
22
22
|
You can use this function to produce a 1-D windowingFunction of length `lengthSupport` whose ends
|
|
@@ -43,7 +43,7 @@ def cosineWingsTensor(lengthSupport: int, ratioTaper: float=0.1, device: Device
|
|
|
43
43
|
"""
|
|
44
44
|
return _convertToTensor(lengthSupport, ratioTaper, callableTarget=cosineWings, device=device, dtype=dtype)
|
|
45
45
|
|
|
46
|
-
def equalPowerTensor(lengthSupport: int, ratioTaper: float=0.1, device: Device | None=None, dtype: torch.dtype | None=None) -> torch.Tensor:
|
|
46
|
+
def equalPowerTensor(lengthSupport: int, ratioTaper: float = 0.1, device: Device | None = None, dtype: torch.dtype | None = None) -> torch.Tensor:
|
|
47
47
|
"""Generate an equal-power windowingFunction suitable for crossfades.
|
|
48
48
|
|
|
49
49
|
You can use this function to build a 1-D windowingFunction whose ends follow a square-root ramp.
|
|
@@ -70,7 +70,7 @@ def equalPowerTensor(lengthSupport: int, ratioTaper: float=0.1, device: Device |
|
|
|
70
70
|
"""
|
|
71
71
|
return _convertToTensor(lengthSupport, ratioTaper, callableTarget=equalPower, device=device, dtype=dtype)
|
|
72
72
|
|
|
73
|
-
def halfsineTensor(lengthSupport: int, device: Device | None=None, dtype: torch.dtype | None=None) -> torch.Tensor:
|
|
73
|
+
def halfsineTensor(lengthSupport: int, device: Device | None = None, dtype: torch.dtype | None = None) -> torch.Tensor:
|
|
74
74
|
"""Generate a half-sine windowingFunction of the requested length.
|
|
75
75
|
|
|
76
76
|
This function returns a 1-D half-sine windowingFunction of length `lengthSupport`. The value at
|
|
@@ -99,7 +99,7 @@ def halfsineTensor(lengthSupport: int, device: Device | None=None, dtype: torch.
|
|
|
99
99
|
"""
|
|
100
100
|
return _convertToTensor(lengthSupport, callableTarget=halfsine, device=device, dtype=dtype)
|
|
101
101
|
|
|
102
|
-
def tukeyTensor(lengthSupport: int, ratioTaper: float=0.1, device: Device | None=None, dtype: torch.dtype | None=None, **keywordArguments: float) -> torch.Tensor:
|
|
102
|
+
def tukeyTensor(lengthSupport: int, ratioTaper: float = 0.1, device: Device | None = None, dtype: torch.dtype | None = None, **keywordArguments: float) -> torch.Tensor:
|
|
103
103
|
"""Generate a Tukey windowingFunction.
|
|
104
104
|
|
|
105
105
|
You can use this function to obtain a tapered windowingFunction where the central region is
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterHearsPy
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: Audio processing.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
Maintainer-email: Hunter Hogan <HunterHogan@pm.me>
|
|
@@ -17,23 +17,27 @@ Classifier: Intended Audience :: Developers
|
|
|
17
17
|
Classifier: Intended Audience :: Science/Research
|
|
18
18
|
Classifier: Natural Language :: English
|
|
19
19
|
Classifier: Operating System :: OS Independent
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
24
|
Classifier: Typing :: Typed
|
|
26
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.12
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
28
27
|
License-File: LICENSE
|
|
29
|
-
Requires-Dist: Z0Z_tools>=
|
|
30
|
-
Requires-Dist: hunterMakesPy>=0.
|
|
28
|
+
Requires-Dist: Z0Z_tools>=4.0.0
|
|
29
|
+
Requires-Dist: hunterMakesPy>=0.10.0
|
|
31
30
|
Requires-Dist: numpy
|
|
32
31
|
Requires-Dist: resampy
|
|
33
32
|
Requires-Dist: scipy
|
|
34
33
|
Requires-Dist: soundfile>=0.14.0
|
|
35
34
|
Requires-Dist: tqdm
|
|
36
35
|
Requires-Dist: typing-extensions>=4.0.0
|
|
36
|
+
Provides-Extra: development
|
|
37
|
+
Requires-Dist: astToolkit; extra == "development"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "development"
|
|
39
|
+
Requires-Dist: scipy-stubs; extra == "development"
|
|
40
|
+
Requires-Dist: types-resampy; extra == "development"
|
|
37
41
|
Provides-Extra: testing
|
|
38
42
|
Requires-Dist: pytest; extra == "testing"
|
|
39
43
|
Requires-Dist: pytest-mock; extra == "testing"
|
|
@@ -184,6 +188,11 @@ pip install hunterHearsPy
|
|
|
184
188
|
## My recovery
|
|
185
189
|
|
|
186
190
|
[](https://HunterThinks.com/support)
|
|
187
|
-
[](https://www.youtube.com/@HunterHogan)
|
|
188
191
|
|
|
189
192
|
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
193
|
+
|
|
194
|
+
By law, authors retain their copyrights unless they grant exceptions. The [Attribution-NonCommercial 4.0 International Deed](https://github.com/hunterhogan/hunterHearsPy#License-1-ov-file) outlines how you may use this work.
|
|
195
|
+
|
|
196
|
+
The restriction on commercial use (without a license) limits advantage-taking, unjust enrichment, and exploitation. You would be morally wrong if you were to enrich yourself by using this work while I am denied healthcare, housing, security of property, and the safety of my person.
|
|
197
|
+
|
|
198
|
+
To obtain and retain the right to use this work, you incur the cost of attributing the work to me. Your nominal cost might accrue as a benefit that mitigates my suffering or saves my life.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
astTransformations/__init__.py
|
|
5
|
+
astTransformations/windowingFunctions.py
|
|
6
|
+
hunterHearsPy/__init__.py
|
|
7
|
+
hunterHearsPy/_arrays.py
|
|
8
|
+
hunterHearsPy/_fft.py
|
|
9
|
+
hunterHearsPy/_io.py
|
|
10
|
+
hunterHearsPy/_resample.py
|
|
11
|
+
hunterHearsPy/amplitude.py
|
|
12
|
+
hunterHearsPy/autoRevert.py
|
|
13
|
+
hunterHearsPy/clippingArrays.py
|
|
14
|
+
hunterHearsPy/dataBaskets.py
|
|
15
|
+
hunterHearsPy/py.typed
|
|
16
|
+
hunterHearsPy/theSSOT.py
|
|
17
|
+
hunterHearsPy/theTypes.py
|
|
18
|
+
hunterHearsPy/windowingFunctions.py
|
|
19
|
+
hunterHearsPy/windowingFunctionsTensor.py
|
|
20
|
+
hunterHearsPy.egg-info/PKG-INFO
|
|
21
|
+
hunterHearsPy.egg-info/SOURCES.txt
|
|
22
|
+
hunterHearsPy.egg-info/dependency_links.txt
|
|
23
|
+
hunterHearsPy.egg-info/requires.txt
|
|
24
|
+
hunterHearsPy.egg-info/top_level.txt
|
|
25
|
+
tests/__init__.py
|
|
26
|
+
tests/_theSSOT.py
|
|
27
|
+
tests/conftest.py
|
|
28
|
+
tests/conftestAnnex.py
|
|
29
|
+
tests/oldSampleData.py
|
|
30
|
+
tests/test_arrays.py
|
|
31
|
+
tests/test_fft.py
|
|
32
|
+
tests/test_io.py
|
|
33
|
+
tests/test_old_amplitude.py
|
|
34
|
+
tests/test_old_autoRevert.py
|
|
35
|
+
tests/test_old_clippingArrays.py
|
|
36
|
+
tests/test_old_spectrogram.py
|
|
37
|
+
tests/test_old_waveform.py
|
|
38
|
+
tests/test_resample.py
|
|
39
|
+
tests/test_windowingFunctions.py
|
|
40
|
+
tests/test_windowingFunctionsTensor.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
Z0Z_tools>=
|
|
2
|
-
hunterMakesPy>=0.
|
|
1
|
+
Z0Z_tools>=4.0.0
|
|
2
|
+
hunterMakesPy>=0.10.0
|
|
3
3
|
numpy
|
|
4
4
|
resampy
|
|
5
5
|
scipy
|
|
@@ -7,6 +7,12 @@ soundfile>=0.14.0
|
|
|
7
7
|
tqdm
|
|
8
8
|
typing-extensions>=4.0.0
|
|
9
9
|
|
|
10
|
+
[development]
|
|
11
|
+
astToolkit
|
|
12
|
+
pytest-cov
|
|
13
|
+
scipy-stubs
|
|
14
|
+
types-resampy
|
|
15
|
+
|
|
10
16
|
[testing]
|
|
11
17
|
pytest
|
|
12
18
|
pytest-mock
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hunterHearsPy"
|
|
3
|
+
version = "1.4.1"
|
|
4
|
+
description = "Audio processing."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = "CC-BY-NC-4.0"
|
|
8
|
+
license-files = ["LICEN[CS]E*"]
|
|
9
|
+
authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
|
|
10
|
+
maintainers = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
|
|
11
|
+
keywords = []
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Programming Language :: Python :: 3.15",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"Z0Z_tools>=4.0.0",
|
|
27
|
+
"hunterMakesPy>=0.10.0",
|
|
28
|
+
"numpy",
|
|
29
|
+
"resampy",
|
|
30
|
+
"scipy",
|
|
31
|
+
"soundfile>=0.14.0",
|
|
32
|
+
"tqdm",
|
|
33
|
+
"typing-extensions>=4.0.0",
|
|
34
|
+
]
|
|
35
|
+
optional-dependencies = { development = ["astToolkit", "pytest-cov", "scipy-stubs", "types-resampy"], testing = ["pytest", "pytest-mock", "pytest-xdist", "torch"], torch = ["torch"] }
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Context7 = "https://context7.com/hunterhogan/hunterhearspy"
|
|
39
|
+
Donate = "https://www.patreon.com/integrated"
|
|
40
|
+
Download = "https://pypi.org/project/hunterHearsPy"
|
|
41
|
+
Homepage = "https://github.com/hunterhogan/hunterHearsPy"
|
|
42
|
+
Issues = "https://github.com/hunterhogan/hunterHearsPy/issues"
|
|
43
|
+
Repository = "https://github.com/hunterhogan/hunterHearsPy.git"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["setuptools"]
|
|
47
|
+
build-backend = "setuptools.build_meta"
|
|
48
|
+
|
|
49
|
+
[tool.coverage]
|
|
50
|
+
report = { exclude_lines = ["if TYPE_CHECKING:", "if __name__ == \"__main__\":"], ignore_errors = false }
|
|
51
|
+
run = { branch = true, concurrency = ["multiprocessing"], data_file = "tests/coverage/.coverage", omit = ["tests/*"], parallel = true, source = ["."] }
|
|
52
|
+
|
|
53
|
+
[tool.pytest]
|
|
54
|
+
addopts = ["--color=auto", "--tb=line", "-n", "4"]
|
|
55
|
+
log_auto_indent = "On"
|
|
56
|
+
testpaths = ["tests"]
|
|
57
|
+
|
|
58
|
+
[tool.setuptools]
|
|
59
|
+
package-data = { "*" = ["py.typed"]}
|
|
60
|
+
packages = { find = {} }
|
|
61
|
+
|
|
62
|
+
[tool.updateCitation]
|
|
63
|
+
filenameCitationDOTcff = "CITATION.cff"
|
|
64
|
+
pathFilenameCitationSSOT = "citations/CITATION.cff"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from tests._theSSOT import (
|
|
4
|
+
dtypeTokens as dtypeTokens, pathDataSamples as pathDataSamples, pathDataSamplesExpected as pathDataSamplesExpected)
|
|
5
|
+
from tests.conftestAnnex import (
|
|
6
|
+
assert_allclose as assert_allclose, assert_approx as assert_approx, assert_array_equal as assert_array_equal,
|
|
7
|
+
assertEqualTo as assertEqualTo, messageTestFailure as messageTestFailure, messageTestFailure_ndarray as messageTestFailure_ndarray)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
pathDataSamples = Path('tests/dataSamples')
|
|
6
|
+
pathDataSamplesExpected: Path = pathDataSamples / 'expected'
|
|
7
|
+
|
|
8
|
+
dtypeTokens: frozenset[str] = frozenset(('int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float16', 'float32', 'float64', 'complex64', 'complex128'))
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from hashlib import blake2b
|
|
4
|
+
from humpy_cytoolz.dicttoolz import keyfilter, merge
|
|
5
|
+
from hunterHearsPy import loadWaveforms, readAudioFile
|
|
6
|
+
from hunterMakesPy.dataStructures import stringItUp
|
|
7
|
+
from more_itertools import one
|
|
8
|
+
from soundfile import dtype_str as Options_dtype_str
|
|
9
|
+
from tests import pathDataSamples, pathDataSamplesExpected
|
|
10
|
+
from typing import cast, TYPE_CHECKING
|
|
11
|
+
import inspect
|
|
12
|
+
import numpy
|
|
13
|
+
import operator
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
from hunterHearsPy.theTypes import ArrayWaveforms, Waveform, 形ndarray
|
|
19
|
+
from inspect import Parameter
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from pytest import FixtureRequest
|
|
22
|
+
from torch.types import Device
|
|
23
|
+
from types import MappingProxyType
|
|
24
|
+
|
|
25
|
+
# ================== Test-function parameters ======================================================
|
|
26
|
+
|
|
27
|
+
@pytest.fixture()
|
|
28
|
+
def approx_abs(request: FixtureRequest) -> float:
|
|
29
|
+
"""The `abs` (***abs***olute tolerance) parameter value for `pytest.approx`."""
|
|
30
|
+
return 1e-12
|
|
31
|
+
|
|
32
|
+
@pytest.fixture()
|
|
33
|
+
def approx_rel(request: FixtureRequest) -> float:
|
|
34
|
+
"""The `rel` (***rel***ative tolerance) parameter value for `pytest.approx`."""
|
|
35
|
+
return 1e-6
|
|
36
|
+
|
|
37
|
+
@pytest.fixture()
|
|
38
|
+
def atol(request: FixtureRequest) -> float:
|
|
39
|
+
"""The `atol` (***a***bsolute ***tol***erance) parameter value for `numpy.allclose`."""
|
|
40
|
+
return 1e-08
|
|
41
|
+
|
|
42
|
+
@pytest.fixture()
|
|
43
|
+
def expected(request: FixtureRequest) -> 形ndarray:
|
|
44
|
+
"""Test-function and its parameters encoded in a `__` delimited filename.
|
|
45
|
+
|
|
46
|
+
Each parameter created with `parametrize` (as opposed to, for example, `fixture`) is encoded as `{parameter}~{value}`.
|
|
47
|
+
"""
|
|
48
|
+
request_nodeParameters: MappingProxyType[str, Parameter] = cast('MappingProxyType[str, Parameter]', request.node.callspec.params) # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
|
|
49
|
+
filenameStem: str = '__'.join((
|
|
50
|
+
str(request.function.__name__).removeprefix('test_')
|
|
51
|
+
, *(f'{keyAndValue[0]}~{"".join(stringItUp(keyAndValue[1]) or ["None"])}'
|
|
52
|
+
for keyAndValue in keyfilter(request_nodeParameters.keys().__contains__, merge(inspect.signature(request.function).parameters, request_nodeParameters)).items()
|
|
53
|
+
)))
|
|
54
|
+
if 251 < len(filenameStem):
|
|
55
|
+
filenameStem = f'{str(request.function.__name__).removeprefix("test_")}__blake2b~{blake2b(filenameStem.encode(), digest_size=16).hexdigest()}'
|
|
56
|
+
pathFilename: Path = pathDataSamplesExpected / f'{filenameStem}.npz'
|
|
57
|
+
with numpy.load(pathFilename, allow_pickle=False) as archiveExpected:
|
|
58
|
+
return archiveExpected['expected']
|
|
59
|
+
|
|
60
|
+
@pytest.fixture()
|
|
61
|
+
def rtol(request: FixtureRequest) -> float:
|
|
62
|
+
"""The `rtol` (***r***elative ***tol***erance) parameter value for `numpy.allclose`."""
|
|
63
|
+
return 1e-05
|
|
64
|
+
|
|
65
|
+
# ================== Parameter values to test against the package's `Callable` =====================
|
|
66
|
+
|
|
67
|
+
@pytest.fixture()
|
|
68
|
+
def arrayTarget(request: FixtureRequest) -> Waveform | ArrayWaveforms:
|
|
69
|
+
return cast('Waveform | ArrayWaveforms', request.getfixturevalue(getattr(request, 'param', 'waveform')))
|
|
70
|
+
|
|
71
|
+
@pytest.fixture()
|
|
72
|
+
def arrayWaveforms(listPathFilenames: tuple[Path, ...], CPUlimit: bool | float | int | None) -> ArrayWaveforms:
|
|
73
|
+
return loadWaveforms(listPathFilenames, CPUlimit=CPUlimit).array
|
|
74
|
+
|
|
75
|
+
@pytest.fixture()
|
|
76
|
+
def CPUlimit(request: FixtureRequest) -> int:
|
|
77
|
+
return 1
|
|
78
|
+
|
|
79
|
+
@pytest.fixture(params=tuple(map(pytest.param, (None, 'cpu'))))
|
|
80
|
+
def device(request: FixtureRequest) -> Device | None:
|
|
81
|
+
return request.param
|
|
82
|
+
|
|
83
|
+
@pytest.fixture()
|
|
84
|
+
def dtype_str(pathFilename: Path) -> Options_dtype_str | None:
|
|
85
|
+
return one(set(Options_dtype_str.__args__).intersection(pathFilename.stem.split('_')), too_short=None)
|
|
86
|
+
|
|
87
|
+
# TODO Resolving the actual location for every `pathFilename` and `listPathFilenames` parameter MUST
|
|
88
|
+
# be centralized in one function.
|
|
89
|
+
@pytest.fixture()
|
|
90
|
+
def listPathFilenames(request: FixtureRequest) -> tuple[Path, ...]:
|
|
91
|
+
listFilenames: Sequence[str] = cast('Sequence[str]', request.node.callspec.params['listPathFilenames']) # pyright: ignore[reportUnknownMemberType]
|
|
92
|
+
return tuple(map(pathDataSamples.joinpath, listFilenames))
|
|
93
|
+
|
|
94
|
+
@pytest.fixture()
|
|
95
|
+
def pathFilename(request: FixtureRequest) -> Path:
|
|
96
|
+
filename: str = request.param
|
|
97
|
+
return pathDataSamples / filename
|
|
98
|
+
|
|
99
|
+
@pytest.fixture()
|
|
100
|
+
def sampleRateSource(pathFilename: Path) -> float:
|
|
101
|
+
return float(one(filter(str.isdecimal, map(operator.itemgetter(slice(2, None)), filter(lambda string: string.startswith('Hz'), pathFilename.stem.split('_'))))))
|
|
102
|
+
|
|
103
|
+
@pytest.fixture()
|
|
104
|
+
def waveform(pathFilename: Path, sampleRateSource: float, dtype_str: Options_dtype_str | None) -> Waveform:
|
|
105
|
+
return readAudioFile(pathFilename, sampleRateSource, dtype_str)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from itertools import starmap
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
import numpy
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from hunterHearsPy.theTypes import 个, 形ndarray
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
#================== Assert scalar and built-in containers ========================================================================
|
|
13
|
+
|
|
14
|
+
def assertEqualTo(actual: 个, expected: 个, function: str, *arguments: Any, **keywordArguments: Any) -> None:
|
|
15
|
+
"""Assert that two objects are equal, and if not, raise an AssertionError with a detailed message."""
|
|
16
|
+
assert actual == expected, messageTestFailure(actual, expected, function, *arguments, **keywordArguments)
|
|
17
|
+
|
|
18
|
+
def assert_approx(actual: 个, expected: 个, pytest_rel: float, pytest_abs: float, function: str, *arguments: Any, **keywordArguments: Any) -> None:
|
|
19
|
+
assert actual == pytest.approx(expected, pytest_rel, pytest_abs, nan_ok=True), messageTestFailure(actual, expected, function, *arguments, **keywordArguments)
|
|
20
|
+
|
|
21
|
+
def messageTestFailure(actual: Any, expected: Any, function: str, *arguments: Any, **keywordArguments: Any) -> str:
|
|
22
|
+
"""Format assertion message for any test comparison."""
|
|
23
|
+
parameters: list[str] = [*map(repr, arguments), *starmap('{}={!r}'.format, keywordArguments.items())]
|
|
24
|
+
return f'{function}({", ".join(parameters)}) = {actual!r}, but {expected = }.'
|
|
25
|
+
|
|
26
|
+
#================== Assert NumPy arrays ========================================================================
|
|
27
|
+
|
|
28
|
+
def assert_array_equal(actual: 形ndarray, expected: 形ndarray, function: str, *arguments: Any, **keywordArguments: Any) -> None:
|
|
29
|
+
"""Assert that two arrays are equal, and if not, raise an AssertionError with a detailed message."""
|
|
30
|
+
assert numpy.array_equal(actual, expected), messageTestFailure_ndarray(actual, expected, function, *arguments, **keywordArguments)
|
|
31
|
+
|
|
32
|
+
def assert_allclose(actual: 形ndarray, expected: 形ndarray, rtol: float, atol: float, function: str, *arguments: Any, **keywordArguments: Any) -> None:
|
|
33
|
+
assert numpy.allclose(actual, expected, rtol, atol), messageTestFailure_ndarray(actual, expected, function, *arguments, **keywordArguments)
|
|
34
|
+
|
|
35
|
+
def messageTestFailure_ndarray(actual: 形ndarray, expected: 形ndarray, function: str, *arguments: Any, **keywordArguments: Any) -> str:
|
|
36
|
+
"""Printing arrays is absurd."""
|
|
37
|
+
parameters: list[str] = [*map(repr, arguments), *starmap('{}={!r}'.format, keywordArguments.items())]
|
|
38
|
+
return f'{function}({", ".join(parameters)}) = {actual.shape=},\t{actual.dtype=}, but {expected.shape=}, {expected.dtype=}.'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import cache
|
|
4
|
+
from hunterHearsPy import readAudioFile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import ClassVar, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from hunterHearsPy.theTypes import Waveform
|
|
10
|
+
|
|
11
|
+
class WaveformAndMetadata:
|
|
12
|
+
_cacheWaveforms: ClassVar[dict[Path, Waveform]] = {}
|
|
13
|
+
|
|
14
|
+
def __init__(self, pathFilename: Path, LUFS: float, sampleRate: float, channelsTotal: int, ID: str) -> None:
|
|
15
|
+
self.pathFilename: Path = pathFilename
|
|
16
|
+
self.LUFS: float = LUFS
|
|
17
|
+
self.sampleRate: float = sampleRate
|
|
18
|
+
self.channelsTotal: int = channelsTotal
|
|
19
|
+
self.ID: str = ID
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def waveform(self) -> Waveform:
|
|
23
|
+
if self.pathFilename not in self._cacheWaveforms:
|
|
24
|
+
self._cacheWaveforms[self.pathFilename] = readAudioFile(self.pathFilename, self.sampleRate)
|
|
25
|
+
return self._cacheWaveforms[self.pathFilename]
|
|
26
|
+
|
|
27
|
+
pathDataSamples_labeled = Path('tests/dataSamples/labeled')
|
|
28
|
+
|
|
29
|
+
def ingestSampleData() -> list[WaveformAndMetadata]:
|
|
30
|
+
"""Parse LUFS*.wav filenames and create WaveformData objects without loading waveforms."""
|
|
31
|
+
listWaveformData: list[WaveformAndMetadata] = []
|
|
32
|
+
for pathFilename in pathDataSamples_labeled.glob('LUFS*.wav'):
|
|
33
|
+
LUFSAsStr, sampleRateAsStr, channelsTotalAsStr, ID = pathFilename.stem.split('_', maxsplit=3)
|
|
34
|
+
LUFS = -float(LUFSAsStr[len('LUFS') :])
|
|
35
|
+
sampleRate = float(sampleRateAsStr)
|
|
36
|
+
channelsTotal = int(channelsTotalAsStr[len('ch') :])
|
|
37
|
+
listWaveformData.append(WaveformAndMetadata(pathFilename, LUFS, sampleRate, channelsTotal, ID))
|
|
38
|
+
return listWaveformData
|
|
39
|
+
|
|
40
|
+
@cache
|
|
41
|
+
def sampleData() -> list[WaveformAndMetadata]:
|
|
42
|
+
return ingestSampleData()
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# ty:ignore[unresolved-attribute]
|
|
2
|
-
# ruff:
|
|
2
|
+
# ruff:file-ignore[docstring-missing-exception]
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from hunterHearsPy import applyHardLimit, applyHardLimitComplexValued
|
|
6
6
|
from numpy import float64
|
|
7
|
-
from numpy._core._exceptions import _UFuncNoLoopError #
|
|
7
|
+
from numpy._core._exceptions import _UFuncNoLoopError # ruff:ignore[import-private-name]
|
|
8
8
|
from tests import messageTestFailure
|
|
9
9
|
from typing import Any, Final, TYPE_CHECKING
|
|
10
10
|
import numpy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pyright: reportUnknownArgumentType=false
|
|
2
2
|
# pyright: reportUnknownLambdaType=false
|
|
3
|
-
# ruff:
|
|
3
|
+
# ruff:file-ignore[docstring-missing-exception]
|
|
4
4
|
# ty:ignore[unresolved-attribute]
|
|
5
5
|
"""test_waveform or test_spectrogram? if a spectrogram is involved at any point, then test_spectrogram."""
|
|
6
6
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# pyright: reportUnknownArgumentType=false
|
|
5
5
|
# pyright: reportUnknownLambdaType=false
|
|
6
6
|
# pyright: reportUnknownVariableType=false
|
|
7
|
-
# ruff:
|
|
7
|
+
# ruff:file-ignore[print, try-except-in-loop]
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from hunterHearsPy import getWaveformMetadata, writeWAV
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "hunterHearsPy"
|
|
3
|
-
version = "1.4.0"
|
|
4
|
-
description = "Audio processing."
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.10"
|
|
7
|
-
license = "CC-BY-NC-4.0"
|
|
8
|
-
license-files = ["LICEN[CS]E*"]
|
|
9
|
-
authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
|
|
10
|
-
maintainers = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
|
|
11
|
-
keywords = []
|
|
12
|
-
classifiers = [
|
|
13
|
-
"Development Status :: 4 - Beta",
|
|
14
|
-
"Environment :: Console",
|
|
15
|
-
"Intended Audience :: Developers",
|
|
16
|
-
"Intended Audience :: Science/Research",
|
|
17
|
-
"Natural Language :: English",
|
|
18
|
-
"Operating System :: OS Independent",
|
|
19
|
-
"Programming Language :: Python :: 3.10",
|
|
20
|
-
"Programming Language :: Python :: 3.11",
|
|
21
|
-
"Programming Language :: Python :: 3.12",
|
|
22
|
-
"Programming Language :: Python :: 3.13",
|
|
23
|
-
"Programming Language :: Python :: 3.14",
|
|
24
|
-
"Typing :: Typed",
|
|
25
|
-
]
|
|
26
|
-
dependencies = [
|
|
27
|
-
"Z0Z_tools>=2.1.0",
|
|
28
|
-
"hunterMakesPy>=0.7.0",
|
|
29
|
-
"numpy",
|
|
30
|
-
"resampy",
|
|
31
|
-
"scipy",
|
|
32
|
-
"soundfile>=0.14.0",
|
|
33
|
-
"tqdm",
|
|
34
|
-
"typing-extensions>=4.0.0",
|
|
35
|
-
]
|
|
36
|
-
optional-dependencies = { testing = [
|
|
37
|
-
"pytest",
|
|
38
|
-
"pytest-mock",
|
|
39
|
-
"pytest-xdist",
|
|
40
|
-
"torch"
|
|
41
|
-
], torch = ["torch"] }
|
|
42
|
-
|
|
43
|
-
[project.urls]
|
|
44
|
-
Context7 = "https://context7.com/hunterhogan/hunterhearspy"
|
|
45
|
-
Donate = "https://www.patreon.com/integrated"
|
|
46
|
-
Download = "https://pypi.org/project/hunterHearsPy"
|
|
47
|
-
Homepage = "https://github.com/hunterhogan/hunterHearsPy"
|
|
48
|
-
Issues = "https://github.com/hunterhogan/hunterHearsPy/issues"
|
|
49
|
-
Repository = "https://github.com/hunterhogan/hunterHearsPy.git"
|
|
50
|
-
|
|
51
|
-
[dependency-groups]
|
|
52
|
-
dev = ["astToolkit", "pytest-cov", "scipy-stubs", "types-resampy"]
|
|
53
|
-
pytorch = ["torch==2.10.0"]
|
|
54
|
-
|
|
55
|
-
[build-system]
|
|
56
|
-
requires = ["setuptools"]
|
|
57
|
-
build-backend = "setuptools.build_meta"
|
|
58
|
-
|
|
59
|
-
[tool.coverage]
|
|
60
|
-
report = { exclude_lines = ["if TYPE_CHECKING:", "if __name__ == \"__main__\":"], ignore_errors = false }
|
|
61
|
-
run = { branch = true, concurrency = ["multiprocessing"], data_file = "tests/coverage/.coverage", omit = [
|
|
62
|
-
"tests/*"
|
|
63
|
-
], parallel = true, source = ["."] }
|
|
64
|
-
|
|
65
|
-
[tool.pytest]
|
|
66
|
-
addopts = ["--color=auto", "--tb=line", "-n 4"]
|
|
67
|
-
log_auto_indent = "On"
|
|
68
|
-
pythonpath = ["src"]
|
|
69
|
-
testpaths = ["tests"]
|
|
70
|
-
|
|
71
|
-
[tool.updateCitation]
|
|
72
|
-
filenameCitationDOTcff = "CITATION.cff"
|
|
73
|
-
pathFilenameCitationSSOT = "citations/CITATION.cff"
|
|
74
|
-
|
|
75
|
-
[tool.uv]
|
|
76
|
-
build-backend = { module-name = "hunterHearsPy", module-root = "src" }
|
|
77
|
-
default-groups = ["dev"]
|
|
78
|
-
|
|
79
|
-
[[tool.uv.index]]
|
|
80
|
-
# NOTE I use this to install torch+cuda on my computer that doesn't have cuda. 2026-06-13
|
|
81
|
-
# `uv sync --group pytorch` OR `uv sync --all-groups` will trigger the following.
|
|
82
|
-
# dependency-groups.pytorch amends the dependencies: `torch`, limitation `==2.10.0`, group = "pytorch".
|
|
83
|
-
# At this point, the default source, pypi, is the highest priority on the `torch` sources list. (LIFO)
|
|
84
|
-
# tool.uv.sources sees '`torch`, group = "pytorch"' so pushes "indexPytorch" on top the `torch` sources list.
|
|
85
|
-
# tool.uv.index translates "indexPytorch" to the url of a package index.
|
|
86
|
-
# uv queries the package index for all `torch` that match all limitations.
|
|
87
|
-
# If the package index returns any matches, uv will install the "best" match.
|
|
88
|
-
# Because the package index only has cuda 12.8 versions, uv installs torch+cu128.
|
|
89
|
-
# I picked torch==2.10.0 + cuda 12.8 because that is the pre-installed package on Google Colab T4.
|
|
90
|
-
explicit = true
|
|
91
|
-
name = "indexPytorch"
|
|
92
|
-
url = "https://download.pytorch.org/whl/cu128"
|
|
93
|
-
|
|
94
|
-
[tool.uv.sources]
|
|
95
|
-
torch = { group = "pytorch", index = "indexPytorch" }
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
src/hunterHearsPy/__init__.py
|
|
5
|
-
src/hunterHearsPy/_arrays.py
|
|
6
|
-
src/hunterHearsPy/_fft.py
|
|
7
|
-
src/hunterHearsPy/_io.py
|
|
8
|
-
src/hunterHearsPy/_resample.py
|
|
9
|
-
src/hunterHearsPy/amplitude.py
|
|
10
|
-
src/hunterHearsPy/autoRevert.py
|
|
11
|
-
src/hunterHearsPy/clippingArrays.py
|
|
12
|
-
src/hunterHearsPy/dataBaskets.py
|
|
13
|
-
src/hunterHearsPy/py.typed
|
|
14
|
-
src/hunterHearsPy/theSSOT.py
|
|
15
|
-
src/hunterHearsPy/theTypes.py
|
|
16
|
-
src/hunterHearsPy/windowingFunctions.py
|
|
17
|
-
src/hunterHearsPy/windowingFunctionsTensor.py
|
|
18
|
-
src/hunterHearsPy.egg-info/PKG-INFO
|
|
19
|
-
src/hunterHearsPy.egg-info/SOURCES.txt
|
|
20
|
-
src/hunterHearsPy.egg-info/dependency_links.txt
|
|
21
|
-
src/hunterHearsPy.egg-info/requires.txt
|
|
22
|
-
src/hunterHearsPy.egg-info/top_level.txt
|
|
23
|
-
tests/test_arrays.py
|
|
24
|
-
tests/test_fft.py
|
|
25
|
-
tests/test_io.py
|
|
26
|
-
tests/test_old_amplitude.py
|
|
27
|
-
tests/test_old_autoRevert.py
|
|
28
|
-
tests/test_old_clippingArrays.py
|
|
29
|
-
tests/test_old_spectrogram.py
|
|
30
|
-
tests/test_old_waveform.py
|
|
31
|
-
tests/test_resample.py
|
|
32
|
-
tests/test_windowingFunctions.py
|
|
33
|
-
tests/test_windowingFunctionsTensor.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
hunterHearsPy
|
|
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
|