hunterHearsPy 1.2.0__tar.gz → 1.3.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.
Files changed (37) hide show
  1. {hunterhearspy-1.2.0/src/hunterHearsPy.egg-info → hunterhearspy-1.3.0}/PKG-INFO +1 -1
  2. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/pyproject.toml +1 -1
  3. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/__init__.py +8 -15
  4. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/_arrays.py +87 -54
  5. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/_fft.py +76 -60
  6. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/_io.py +2 -1
  7. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/_resample.py +1 -5
  8. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/amplitude.py +104 -112
  9. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/autoRevert.py +3 -4
  10. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/clippingArrays.py +1 -1
  11. hunterhearspy-1.3.0/src/hunterHearsPy/dataBaskets.py +69 -0
  12. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/theSSOT.py +3 -1
  13. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/theTypes.py +56 -78
  14. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/windowingFunctions.py +1 -1
  15. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/windowingFunctionsTensor.py +2 -1
  16. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0/src/hunterHearsPy.egg-info}/PKG-INFO +1 -1
  17. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy.egg-info/SOURCES.txt +1 -0
  18. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_arrays.py +20 -12
  19. hunterhearspy-1.3.0/tests/test_fft.py +81 -0
  20. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_io.py +2 -2
  21. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_old_amplitude.py +7 -5
  22. hunterhearspy-1.3.0/tests/test_old_spectrogram.py +100 -0
  23. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_resample.py +1 -1
  24. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_windowingFunctions.py +1 -1
  25. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_windowingFunctionsTensor.py +1 -1
  26. hunterhearspy-1.2.0/src/hunterHearsPy/dataBaskets.py +0 -34
  27. hunterhearspy-1.2.0/tests/test_old_spectrogram.py +0 -246
  28. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/LICENSE +0 -0
  29. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/README.md +0 -0
  30. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/setup.cfg +0 -0
  31. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy/py.typed +0 -0
  32. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy.egg-info/dependency_links.txt +0 -0
  33. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy.egg-info/requires.txt +0 -0
  34. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/src/hunterHearsPy.egg-info/top_level.txt +0 -0
  35. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_old_autoRevert.py +0 -0
  36. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_old_clippingArrays.py +0 -0
  37. {hunterhearspy-1.2.0 → hunterhearspy-1.3.0}/tests/test_old_waveform.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterHearsPy
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Audio processing.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  Maintainer-email: Hunter Hogan <HunterHogan@pm.me>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hunterHearsPy"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  description = "Audio processing."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,16 +1,7 @@
1
1
  # ruff: noqa: D104
2
2
  from __future__ import annotations
3
3
 
4
- from hunterHearsPy.theTypes import (
5
- ArraySpectrograms as ArraySpectrograms, ArraySpectrogramsShape as ArraySpectrogramsShape, ArrayWaveforms as ArrayWaveforms,
6
- ArrayWaveformsFloating as ArrayWaveformsFloating, ArrayWaveformsShape as ArrayWaveformsShape, AxisMetadata as AxisMetadata,
7
- callableReturnsNDArray as callableReturnsNDArray, E733TH4X0R as E733TH4X0R, FileDescriptorOrPath as FileDescriptorOrPath,
8
- NormalizationReverter as NormalizationReverter, OptionsAlign as OptionsAlign, Parameters_loadSpectrograms as Parameters_loadSpectrograms,
9
- Parameters_loadWaveforms as Parameters_loadWaveforms, Parameters_stft as Parameters_stft, ParametersShortTimeFFT as ParametersShortTimeFFT,
10
- Spectrogram as Spectrogram, SpectrogramDtype as SpectrogramDtype, Waveform as Waveform, WaveformDtype as WaveformDtype,
11
- WaveformFloating as WaveformFloating, WaveformFloatingDtype as WaveformFloatingDtype, WaveformMetadata as WaveformMetadata,
12
- WaveformShape as WaveformShape, WindowingFunction as WindowingFunction, WindowingFunctionDtype as WindowingFunctionDtype, 个 as 个,
13
- 形floating as 形floating, 形ndarray as 形ndarray, 形Shape as 形Shape)
4
+ from hunterHearsPy import theTypes as theTypes
14
5
 
15
6
  # isort: split
16
7
  from hunterHearsPy.windowingFunctions import cosineWings as cosineWings, equalPower as equalPower, halfsine as halfsine, tukey as tukey
@@ -24,10 +15,12 @@ with suppress(ModuleNotFoundError): # noqa: RUF067
24
15
  tukeyTensor as tukeyTensor)
25
16
 
26
17
  # isort: split
27
- from hunterHearsPy.theSSOT import getAxis as getAxis, setting as setting
18
+ from hunterHearsPy.dataBaskets import (
19
+ AxisMetadata as AxisMetadata, SpectrogramsAndMetadata as SpectrogramsAndMetadata, WaveformMetadata as WaveformMetadata,
20
+ WaveformsAndMetadata as WaveformsAndMetadata)
28
21
 
29
22
  # isort: split
30
- from hunterHearsPy.dataBaskets import Translator as Translator
23
+ from hunterHearsPy.theSSOT import getAxis as getAxis, setting as setting
31
24
 
32
25
  # isort: split
33
26
  from hunterHearsPy._resample import resampleWaveform as resampleWaveform
@@ -35,6 +28,9 @@ from hunterHearsPy.amplitude import (
35
28
  amplitudeIntegerToFloating as amplitudeIntegerToFloating, amplitudeToSoundfile as amplitudeToSoundfile,
36
29
  normalizeArrayWaveforms as normalizeArrayWaveforms, normalizeWaveform as normalizeWaveform)
37
30
 
31
+ # isort: split
32
+ from hunterHearsPy.autoRevert import moveToAxisOfOperation as moveToAxisOfOperation
33
+
38
34
  # isort: split
39
35
  from hunterHearsPy._fft import stft as stft, waveformSpectrogramWaveform as waveformSpectrogramWaveform
40
36
 
@@ -44,9 +40,6 @@ from hunterHearsPy._io import readAudioFile as readAudioFile, spectrogramToWAV a
44
40
  # isort: split
45
41
  from hunterHearsPy.clippingArrays import applyHardLimit as applyHardLimit, applyHardLimitComplexValued as applyHardLimitComplexValued
46
42
 
47
- # isort: split
48
- from hunterHearsPy.autoRevert import moveToAxisOfOperation as moveToAxisOfOperation
49
-
50
43
  # isort: split
51
44
  from hunterHearsPy._arrays import (
52
45
  getWaveformMetadata as getWaveformMetadata, loadSpectrograms as loadSpectrograms, loadWaveforms as loadWaveforms)
@@ -3,22 +3,38 @@ from __future__ import annotations
3
3
 
4
4
  from concurrent.futures import ThreadPoolExecutor
5
5
  from hunterHearsPy import (
6
- ArraySpectrogramsShape, ArrayWaveformsShape, AxisMetadata, getAxis, readAudioFile, setting, stft, WaveformMetadata, WaveformShape)
6
+ AxisMetadata, getAxis, readAudioFile, setting, SpectrogramsAndMetadata, stft, WaveformMetadata, WaveformsAndMetadata)
7
7
  from hunterMakesPy.parseParameters import defineConcurrencyLimit
8
8
  from tqdm.auto import tqdm
9
- from typing import TYPE_CHECKING
9
+ from typing import NamedTuple, TYPE_CHECKING
10
10
  from typing_extensions import Unpack
11
11
  import numpy
12
12
  import sys
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from collections.abc import Sequence
16
- from hunterHearsPy import (
16
+ from hunterHearsPy.theTypes import (
17
17
  ArraySpectrograms, ArrayWaveforms, FileDescriptorOrPath, OptionsAlign, Parameters_loadSpectrograms, Parameters_loadWaveforms, Waveform)
18
18
  from numpy.lib._arraypad_impl import _ModeKind
19
19
  from numpy.typing import DTypeLike
20
20
  from soundfile import dtype_str as Options_dtype_str
21
- from typing import Any
21
+
22
+ #======== Idiosyncratic classes tell type checkers about the shape of arrays ======================
23
+
24
+ class Axes2(NamedTuple):
25
+ a0: int
26
+ a1: int
27
+
28
+ class Axes3(NamedTuple):
29
+ a0: int
30
+ a1: int
31
+ a2: int
32
+
33
+ class Axes4(NamedTuple):
34
+ a0: int
35
+ a1: int
36
+ a2: int
37
+ a3: int
22
38
 
23
39
  def getWaveformMetadata(
24
40
  listPathFilenames: Sequence[FileDescriptorOrPath], sampleRate: float, align: OptionsAlign
@@ -61,72 +77,68 @@ def getWaveformMetadata(
61
77
 
62
78
  return dictionaryWaveformMetadata, axis
63
79
 
64
- def loadWaveforms(listPathFilenames: Sequence[FileDescriptorOrPath], *, CPUlimit: bool | float | int | None = None, **keywordArguments: Unpack[Parameters_loadWaveforms]) -> ArrayWaveforms:
80
+ def loadWaveforms(
81
+ listPathFilenames: Sequence[FileDescriptorOrPath]
82
+ , *
83
+ , CPUlimit: bool | float | int | None = None
84
+ , **keywordArguments: Unpack[Parameters_loadWaveforms]
85
+ ) -> WaveformsAndMetadata:
65
86
  """Load a list of audio files into a single stacked NumPy array."""
66
87
  #============== Initialize ==========================================================
88
+
67
89
  align: OptionsAlign = keywordArguments.get('align', setting.align)
68
- dtype: DTypeLike = keywordArguments.get('dtype', setting.dtypeWaveform)
90
+ dtypeWaveform: DTypeLike = keywordArguments.get('dtypeWaveform', setting.dtypeWaveform)
69
91
  dtype_str: Options_dtype_str = keywordArguments.get('dtype_str', setting.dtype_str)
70
- sampleRateDesired: float = keywordArguments.get('sampleRateDesired', setting.sampleRate)
92
+ sampleRate: float = keywordArguments.get('sampleRate', setting.sampleRate)
71
93
 
72
94
  max_workers: int = defineConcurrencyLimit(limit=CPUlimit)
73
95
 
74
96
  #============== Allocate memory ==========================================================
75
- dictionaryWaveformMetadata, axis = getWaveformMetadata(listPathFilenames, sampleRateDesired, align)
76
97
 
77
- arrayWaveforms: ArrayWaveforms = numpy.zeros(ArrayWaveformsShape(*(entry.size for entry in sorted(axis.values()))), dtype)
98
+ dictionaryWaveformMetadata, axis = getWaveformMetadata(listPathFilenames, sampleRate, align)
99
+ arrayWaveforms: ArrayWaveforms = numpy.zeros(Axes3(*(entry.size for entry in sorted(axis.values()))), dtypeWaveform)
78
100
 
79
101
  #============== Concurrent loading ==========================================================
102
+
80
103
  # TODO the axis order is hardcoded.
81
104
  def workhorse(index: int, metadata: WaveformMetadata) -> None:
82
105
  arrayWaveforms[:, metadata['samplesStart'] : metadata['samplesStop'], index] = readAudioFile(
83
- metadata['pathFilename'], sampleRateDesired, dtype_str
84
- ).astype(dtype, copy=False)
106
+ metadata['pathFilename'], sampleRate, dtype_str
107
+ ).astype(dtypeWaveform, copy=False)
85
108
 
86
109
  with ThreadPoolExecutor(max_workers=max_workers) as threadManager:
87
- tuple(tqdm(threadManager.map(workhorse, dictionaryWaveformMetadata, dictionaryWaveformMetadata.values()), total=len(dictionaryWaveformMetadata)))
88
-
89
- """# DEVELOPMENT
90
- test_loadWaveforms_align: this is an exception to the rule of 1 test function for non-error outcomes per function. The not-commented-out code in align.py is my proof of concept for this test. But I DO NOT want you to mimic the code I wrote. I want you to understand what we want to test, then I want you think about a generalized test function. I designed this ArrayWaveforms such that if all of the waveforms are align=start (which means "pad the end of the shorter waveforms"), then for the duration of the shortest waveform, that data is identical in all of the waveforms. In this case, the shortest is 20 seconds at 44100 Hz, so the first 44100*20 samples of the left channel in all 5 waveforms match each other and the first 44100*20 samples of the right channel in all 5 waveforms match each other. Said, differently, if you truncate to the shortest waveform, then all of the waveforms should be identical.
110
+ tuple(tqdm(threadManager.map(workhorse, dictionaryWaveformMetadata, dictionaryWaveformMetadata.values())
111
+ , desc='Loading waveforms.'
112
+ , total=len(dictionaryWaveformMetadata)
113
+ ))
91
114
 
92
- If align=stop, then if you truncate to the shortest waveform by removing the beginning of the longer waveforms, then all waveforms are identical.
115
+ return WaveformsAndMetadata(array=arrayWaveforms, metadata=dictionaryWaveformMetadata)
93
116
 
94
- If align=center, truncate by removing equal amounts from the beginning and end, and the waveforms are identical.
95
-
96
- That is the test I want you to make. The first three parameters are the same as test_loadWaveforms because they are necessary to get the array. Then use the align parameter in the test function to truncate/trim the array to the shortest waveform. Then do a round-robin assert_array_equal for each waveform in the ArrayWaveforms.
97
-
98
- How long is the shortest waveform? Well, fuck.
99
-
100
- # TODO Do I need to return `dictionaryWaveformMetadata`? In addition to the padding data, it is a convenient way to get the original pathFilename.
101
- I wrote a bunch of notes about this. I guess they are in old commits.
102
- Instead of returning a simple tuple, I could return a named tuple, which is tuple+, of course.
103
- """
104
-
105
- return arrayWaveforms
106
-
107
- def loadSpectrograms(listPathFilenames: Sequence[FileDescriptorOrPath], *, CPUlimit: bool | float | int | None = None, **keywordArguments: Any) -> tuple[ArraySpectrograms, dict[int, WaveformMetadata]]:
117
+ def loadSpectrograms(
118
+ listPathFilenames: Sequence[FileDescriptorOrPath], *, CPUlimit: bool | float | int | None = None, **keywordArguments: Unpack[Parameters_loadSpectrograms]
119
+ ) -> SpectrogramsAndMetadata:
108
120
  """Load spectrograms from a list of audio files."""
109
121
  #============== Initialize ==========================================================
110
122
 
111
123
  align: OptionsAlign = keywordArguments.get('align', setting.align)
112
124
  align_pad_mode: _ModeKind = keywordArguments.get('align_pad_mode', setting.align_pad_mode)
113
- dtype: DTypeLike = keywordArguments.get('dtype', setting.dtypeSpectrogram)
125
+ dtypeSpectrogram: DTypeLike = keywordArguments.get('dtypeSpectrogram', setting.dtypeSpectrogram)
114
126
  dtype_str: Options_dtype_str = keywordArguments.get('dtype_str', setting.dtype_str)
115
127
  dtypeWaveform: DTypeLike = keywordArguments.get('dtypeWaveform', setting.dtypeWaveform)
116
- sampleRateDesired: float = keywordArguments.get('sampleRateDesired', setting.sampleRate)
128
+ sampleRate: float = keywordArguments.get('sampleRate', setting.sampleRate)
117
129
 
118
130
  max_workers: int = defineConcurrencyLimit(limit=CPUlimit)
119
131
 
120
132
  #============== Allocate memory ==========================================================
121
133
 
122
- dictionaryWaveformMetadata, axis = getWaveformMetadata(listPathFilenames, sampleRateDesired, align)
134
+ dictionaryWaveformMetadata, axis = getWaveformMetadata(listPathFilenames, sampleRate, align)
123
135
 
124
- waveformShape = WaveformShape(*(entry.size for entry in sorted((axis['channel'], axis['time']))))
136
+ waveformShape = Axes2(*(entry.size for entry in sorted((axis['channel'], axis['time']))))
125
137
  axisSpectrogram = AxisMetadata(setting.axisSpectrogramIndexing, len(dictionaryWaveformMetadata))
126
138
  arraySpectrogramsShape: list[int] = list(stft(numpy.zeros(waveformShape, dtypeWaveform), **keywordArguments).shape)
127
139
  arraySpectrogramsShape.insert(axisSpectrogram.number, axisSpectrogram.size)
128
140
 
129
- arraySpectrograms: ArraySpectrograms = numpy.zeros(ArraySpectrogramsShape(*arraySpectrogramsShape), dtype)
141
+ arraySpectrograms: ArraySpectrograms = numpy.zeros(Axes4(*arraySpectrogramsShape), dtypeSpectrogram)
130
142
 
131
143
  #============== Concurrent loading ==========================================================
132
144
 
@@ -147,60 +159,81 @@ def loadSpectrograms(listPathFilenames: Sequence[FileDescriptorOrPath], *, CPUli
147
159
  # TODO axisSpectrogram.number is hardcoded here. grr!
148
160
  arraySpectrograms[..., index] = stft(
149
161
  numpy.pad(
150
- readAudioFile(metadata['pathFilename'], sampleRateDesired, dtype_str).astype(dtypeWaveform, copy=False)
151
- , pad_widthIntegralType, mode=align_pad_mode)
162
+ readAudioFile(metadata['pathFilename'], sampleRate, dtype_str).astype(dtypeWaveform, copy=False)
163
+ , pad_widthIntegralType
164
+ , align_pad_mode
165
+ )
152
166
  , **keywordArguments
153
167
  )
154
168
 
155
169
  if False: # This fails the current tests.
156
170
  arraySpectrogramsShape[axisSpectrogram.number] = index
157
171
  indices = numpy.indices(tuple(arraySpectrogramsShape))
158
- numpy.put_along_axis(arraySpectrograms, indices=indices, values=stft(
159
- numpy.pad(
160
- readAudioFile(metadata['pathFilename'], sampleRateDesired, dtype_str).astype(dtypeWaveform, copy=False)
161
- , pad_widthIntegralType, mode=align_pad_mode)
162
- , **keywordArguments
163
- ), axis=axisSpectrogram.number
164
- )
172
+ numpy.put_along_axis(
173
+ arraySpectrograms,
174
+ indices=indices,
175
+ values=stft(
176
+ numpy.pad(
177
+ readAudioFile(metadata['pathFilename'], sampleRate, dtype_str).astype(dtypeWaveform, copy=False),
178
+ pad_widthIntegralType,
179
+ mode=align_pad_mode,
180
+ ),
181
+ **keywordArguments,
182
+ ),
183
+ axis=axisSpectrogram.number,
184
+ )
165
185
 
166
186
  with ThreadPoolExecutor(max_workers=max_workers) as threadManager:
167
- tuple(tqdm(threadManager.map(workhorse, dictionaryWaveformMetadata.keys(), dictionaryWaveformMetadata.values()), desc='Loading spectrograms', total=axisSpectrogram.size))
187
+ tuple(tqdm(threadManager.map(workhorse, dictionaryWaveformMetadata.keys(), dictionaryWaveformMetadata.values())
188
+ , desc='Loading spectrograms.'
189
+ , total=axisSpectrogram.size
190
+ ))
168
191
 
169
- return arraySpectrograms, dictionaryWaveformMetadata
192
+ return SpectrogramsAndMetadata(arraySpectrograms, dictionaryWaveformMetadata)
170
193
 
171
- def BACKUPloadSpectrograms(listPathFilenames: Sequence[FileDescriptorOrPath], *, CPUlimit: bool | float | int | None = None, **keywordArguments: Any) -> tuple[ArraySpectrograms, dict[int, WaveformMetadata]]:
194
+ def BACKUPloadSpectrograms(
195
+ listPathFilenames: Sequence[FileDescriptorOrPath], *, CPUlimit: bool | float | int | None = None, **keywordArguments: Unpack[Parameters_loadSpectrograms]
196
+ ) -> tuple[ArraySpectrograms, dict[int, WaveformMetadata]]:
172
197
  """Load spectrograms from a list of audio files."""
173
198
  #============== Initialize ==========================================================
174
199
 
175
200
  align: OptionsAlign = keywordArguments.get('align', setting.align)
176
- dtype: DTypeLike = keywordArguments.get('dtype', setting.dtypeSpectrogram)
201
+ dtypeSpectrogram: DTypeLike = keywordArguments.get('dtypeSpectrogram', setting.dtypeSpectrogram)
177
202
  dtype_str: Options_dtype_str = keywordArguments.get('dtype_str', setting.dtype_str)
178
203
  dtypeWaveform: DTypeLike = keywordArguments.get('dtypeWaveform', setting.dtypeWaveform)
179
- sampleRateDesired: float = keywordArguments.get('sampleRateDesired', setting.sampleRate)
204
+ sampleRate: float = keywordArguments.get('sampleRate', setting.sampleRate)
180
205
 
181
206
  max_workers: int = defineConcurrencyLimit(limit=CPUlimit)
182
207
 
183
208
  #============== Allocate memory ==========================================================
184
209
 
185
- dictionaryWaveformMetadata, axis = getWaveformMetadata(listPathFilenames, sampleRateDesired, align)
210
+ dictionaryWaveformMetadata, axis = getWaveformMetadata(listPathFilenames, sampleRate, align)
186
211
 
187
- waveformShape = WaveformShape(*(entry.size for entry in sorted((axis['channel'], axis['time']))))
212
+ waveformShape = Axes2(*(entry.size for entry in sorted((axis['channel'], axis['time']))))
188
213
  waveformZeros: Waveform = numpy.zeros(waveformShape, dtypeWaveform)
189
214
 
190
215
  axisSpectrogram = AxisMetadata(setting.axisSpectrogramIndexing, len(dictionaryWaveformMetadata))
191
216
 
192
217
  arraySpectrogramsShape: list[int] = list(stft(waveformZeros, **keywordArguments).shape)
193
218
  arraySpectrogramsShape.insert(axisSpectrogram.number, axisSpectrogram.size)
194
- arraySpectrograms: ArraySpectrograms = numpy.zeros(ArraySpectrogramsShape(*arraySpectrogramsShape), dtype)
219
+ arraySpectrograms: ArraySpectrograms = numpy.zeros(Axes4(*arraySpectrogramsShape), dtypeSpectrogram)
195
220
 
196
221
  #============== Concurrent loading ==========================================================
197
222
 
198
223
  def workhorse(index: int, metadata: WaveformMetadata) -> None:
199
224
  waveform: Waveform = waveformZeros.copy()
200
- waveform[:, metadata['samplesStart'] : metadata['samplesStop']] = readAudioFile(metadata['pathFilename'], sampleRateDesired, dtype_str).astype(dtypeWaveform, copy=False)
225
+ waveform[:, metadata['samplesStart'] : metadata['samplesStop']] = readAudioFile(
226
+ metadata['pathFilename'], sampleRate, dtype_str
227
+ ).astype(dtypeWaveform, copy=False)
201
228
  arraySpectrograms[..., index] = stft(waveform, **keywordArguments)
202
229
 
203
230
  with ThreadPoolExecutor(max_workers=max_workers) as threadManager:
204
- tuple(tqdm(threadManager.map(workhorse, dictionaryWaveformMetadata.keys(), dictionaryWaveformMetadata.values()), desc='Loading spectrograms', total=axisSpectrogram.size))
231
+ tuple(
232
+ tqdm(
233
+ threadManager.map(workhorse, dictionaryWaveformMetadata.keys(), dictionaryWaveformMetadata.values()),
234
+ desc='Loading spectrograms',
235
+ total=axisSpectrogram.size,
236
+ )
237
+ )
205
238
 
206
239
  return arraySpectrograms, dictionaryWaveformMetadata
@@ -1,75 +1,101 @@
1
+ # pyright: reportArgumentType=false
2
+ # pyright: reportAssignmentType=false
3
+ # ty:ignore[invalid-argument-type]
4
+ # ty:ignore[invalid-assignment]
1
5
  from __future__ import annotations
2
6
 
3
7
  from humpy_cytoolz.dicttoolz import keyfilter, merge
4
- from hunterHearsPy import amplitudeIntegerToFloating, ParametersShortTimeFFT, setting, Translator
8
+ from hunterHearsPy import amplitudeIntegerToFloating, getAxis, setting
9
+ from hunterHearsPy.dataBaskets import ParametersShortTimeFFT
10
+ from numpy import complexfloating, floating, integer
5
11
  from scipy.signal import ShortTimeFFT
6
12
  from typing import overload, TYPE_CHECKING
7
13
  from typing_extensions import Unpack
14
+ import dataclasses
8
15
  import numpy
9
16
 
10
17
  if TYPE_CHECKING:
11
18
  from collections.abc import Callable
12
- from hunterHearsPy import (
19
+ from hunterHearsPy.theTypes import (
13
20
  ArraySpectrograms, ArrayWaveforms, ArrayWaveformsFloating, Parameters_stft, Spectrogram, Waveform, WaveformFloating)
14
21
  from pathlib import PurePath
15
22
  from scipy.signal._short_time_fft import _PadType
16
23
 
17
24
  @overload # stft 1 ndarray
18
- def stft(arrayTarget: Waveform, *, lengthWaveform: int = 0, indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]) -> Spectrogram: ...
25
+ def stft(
26
+ arrayTarget: Waveform, *, lengthWaveform: int = 0
27
+ , indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]
28
+ ) -> Spectrogram: ...
19
29
  @overload # stft many ndarray
20
- def stft(arrayTarget: ArrayWaveforms, *, lengthWaveform: int = 0, indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]) -> ArraySpectrograms: ...
30
+ def stft(
31
+ arrayTarget: ArrayWaveforms, *, lengthWaveform: int = 0
32
+ , indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]
33
+ ) -> ArraySpectrograms: ...
21
34
  @overload # istft 1 ndarray
22
- def stft(arrayTarget: Spectrogram, *, lengthWaveform: int, indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]) -> Waveform: ...
35
+ def stft(
36
+ arrayTarget: Spectrogram, *, lengthWaveform: int
37
+ , indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]
38
+ ) -> Waveform: ...
23
39
  @overload # istft many ndarray
24
- def stft(arrayTarget: ArraySpectrograms, *, lengthWaveform: int, indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]) -> ArrayWaveforms: ...
25
- def stft(arrayTarget: Waveform | ArrayWaveforms | Spectrogram | ArraySpectrograms
26
- , *
27
- , lengthWaveform: int = 0
28
- , indexingAxis: int = -1
29
- , **keywordArguments: Unpack[Parameters_stft]
30
- ) -> Waveform | ArrayWaveforms | Spectrogram | ArraySpectrograms:
31
- if numpy.issubdtype(arrayTarget.dtype, numpy.integer):
32
- if arrayTarget.ndim == 3:
33
- arrayFloating: ArrayWaveformsFloating = amplitudeIntegerToFloating(arrayTarget)
34
- if arrayTarget.ndim == 2:
35
- arrayFloating: WaveformFloating = amplitudeIntegerToFloating(arrayTarget)
36
- else:
37
- arrayFloating = arrayTarget
38
- if numpy.issubdtype(arrayTarget.dtype, numpy.complexfloating) and (lengthWaveform < 1):
39
- from hunterHearsPy._io import saveOnError # noqa: PLC0415
40
- pathFilename: PurePath = saveOnError(arrayTarget)
41
- message: str = (
42
- "I did not receive `lengthWaveform`, so I could not perform the inverse STFT. "
43
- "I saved `arrayTarget` to a file in this computer's temporary directory so you might recover the data. "
44
- f"{arrayTarget.shape = }, {arrayTarget.dtype = }\n"
45
- f"{pathFilename = }"
46
- )
47
- raise ValueError(message)
48
-
49
- parametersShortTimeFFT = Translator(**ParametersShortTimeFFT(
50
- keyfilter(setting.ShortTimeFFT.keys().__contains__, merge(setting.ShortTimeFFT, keywordArguments)))) # pyright: ignore[reportArgumentType] # ty:ignore[invalid-argument-type]
51
-
52
- padding: _PadType = keywordArguments.get('padding', setting.padding)
53
-
54
- workhorseSTFT: ShortTimeFFT = ShortTimeFFT(**parametersShortTimeFFT.e733T)
55
-
40
+ def stft(
41
+ arrayTarget: ArraySpectrograms, *, lengthWaveform: int
42
+ , indexingAxis: int = -1, **keywordArguments: Unpack[Parameters_stft]
43
+ ) -> ArrayWaveforms: ...
44
+ def stft(
45
+ arrayTarget: Waveform | ArrayWaveforms | Spectrogram | ArraySpectrograms
46
+ , *
47
+ , lengthWaveform: int = 0
48
+ , indexingAxis: int = -1
49
+ , **keywordArguments: Unpack[Parameters_stft]
50
+ ) -> Waveform | ArrayWaveforms | Spectrogram | ArraySpectrograms:
56
51
  def mushroom(waveform: WaveformFloating) -> Spectrogram:
57
52
  return workhorseSTFT.stft(x=waveform, padding=padding)
58
53
 
59
54
  def turtleShell(spectrogram: Spectrogram, lengthWaveform: int) -> WaveformFloating:
60
55
  return workhorseSTFT.istft(S=spectrogram, k1=lengthWaveform)
61
56
 
57
+ #============== Initialize ==========================================================
58
+
59
+ parametersShortTimeFFT = ParametersShortTimeFFT(**keyfilter(dataclasses.asdict(setting.ShortTimeFFT).keys().__contains__, merge(dataclasses.asdict(setting.ShortTimeFFT), keywordArguments)))
60
+ padding: _PadType = keywordArguments.get('padding', setting.padding)
61
+
62
+ workhorseSTFT: ShortTimeFFT = ShortTimeFFT(**parametersShortTimeFFT.e733T)
63
+
64
+ # DEVELOPMENT This assumes I will only run stft on floating-point valued waveform data. But is
65
+ # that true? Is that necessary? Is that desirable?
66
+ arrayFloating = arrayTarget
67
+ arrayWaveforms = arrayTarget
68
+ if numpy.issubdtype(arrayTarget.dtype, integer):
69
+ if arrayTarget.ndim == 3:
70
+ arrayWaveforms: ArrayWaveformsFloating = amplitudeIntegerToFloating(arrayTarget)
71
+ if arrayTarget.ndim == 2:
72
+ arrayFloating: WaveformFloating = amplitudeIntegerToFloating(arrayTarget)
73
+ elif numpy.issubdtype(arrayTarget.dtype, complexfloating) and (lengthWaveform < 1):
74
+ from hunterHearsPy._io import saveOnError # noqa: PLC0415
75
+
76
+ pathFilename: PurePath = saveOnError(arrayTarget)
77
+ message: str = (
78
+ 'I did not receive `lengthWaveform`, so I could not perform the inverse STFT. '
79
+ 'I saved `arrayTarget` to a file in the temporary directory of this computer so you might recover the data. '
80
+ f'{arrayTarget.shape = }, {arrayTarget.dtype = }\n'
81
+ f'{pathFilename = }'
82
+ )
83
+ raise ValueError(message)
84
+
85
+ # DEVELOPMENT There are eleventy different systems below because I have been trying to find 1)
86
+ # efficient logic that 2) the type checkers understand.
62
87
  if arrayFloating.ndim == 2:
63
- arrayFloating: ArrayWaveformsFloating = numpy.expand_dims(arrayFloating, indexingAxis)
64
- elif (arrayTarget.ndim == 3) and (numpy.issubdtype(arrayTarget.dtype, numpy.complexfloating)):
88
+ arrayWaveforms: ArrayWaveformsFloating = numpy.expand_dims(arrayFloating, indexingAxis)
89
+ elif (arrayTarget.ndim == 3) and (numpy.issubdtype(arrayTarget.dtype, complexfloating)):
65
90
  spectrogram: Spectrogram = arrayTarget
66
91
  return turtleShell(spectrogram, lengthWaveform)
67
92
 
68
- if (arrayFloating.ndim == 3) and (numpy.issubdtype(arrayFloating.dtype, numpy.floating)):
69
- arrayWaveforms: ArrayWaveformsFloating = arrayFloating
93
+ if (arrayWaveforms.ndim == 3) and (numpy.issubdtype(arrayWaveforms.dtype, floating)):
70
94
  arrayWaveforms = numpy.moveaxis(arrayWaveforms, indexingAxis, -1)
71
95
  index = 0
72
- arraySpectrograms: ArraySpectrograms = numpy.tile(mushroom(arrayWaveforms[..., index])[..., numpy.newaxis], arrayWaveforms.shape[-1])
96
+ arraySpectrograms: ArraySpectrograms = numpy.tile(
97
+ mushroom(arrayWaveforms[..., index])[..., numpy.newaxis], arrayWaveforms.shape[-1]
98
+ )
73
99
  for index in range(1, arrayWaveforms.shape[-1]):
74
100
  arraySpectrograms[..., index] = mushroom(arrayWaveforms[..., index])
75
101
  arraySpectrograms = numpy.moveaxis(arraySpectrograms, -1, indexingAxis)
@@ -77,11 +103,13 @@ def stft(arrayTarget: Waveform | ArrayWaveforms | Spectrogram | ArraySpectrogram
77
103
  arraySpectrograms = numpy.squeeze(arraySpectrograms, indexingAxis)
78
104
  return arraySpectrograms
79
105
 
80
- elif (arrayTarget.ndim == 4) and (numpy.issubdtype(arrayTarget.dtype, numpy.complexfloating)):
106
+ elif (arrayTarget.ndim == 4) and (numpy.issubdtype(arrayTarget.dtype, complexfloating)):
81
107
  arrayTARGET: ArraySpectrograms = arrayTarget
82
108
  arrayTARGET = numpy.moveaxis(arrayTARGET, indexingAxis, -1)
83
109
  index = 0
84
- arrayTransformed: ArrayWaveforms = numpy.tile(turtleShell(arrayTARGET[..., index], lengthWaveform)[..., numpy.newaxis], arrayTARGET.shape[-1])
110
+ arrayTransformed: ArrayWaveforms = numpy.tile(
111
+ turtleShell(arrayTARGET[..., index], lengthWaveform)[..., numpy.newaxis], arrayTARGET.shape[-1]
112
+ )
85
113
  for index in range(1, arrayTARGET.shape[-1]):
86
114
  arrayTransformed[..., index] = turtleShell(arrayTARGET[..., index], lengthWaveform)
87
115
  return numpy.moveaxis(arrayTransformed, -1, indexingAxis)
@@ -108,22 +136,10 @@ def waveformSpectrogramWaveform(callableNeedsSpectrogram: Callable[[Spectrogram]
108
136
  A function that accepts a `Waveform` [2], converts it to a `Spectrogram` [1], applies
109
137
  `callableNeedsSpectrogram`, and returns the reconstructed `Waveform` [2] at the original
110
138
  length.
111
-
112
- Time Axis Assumption
113
- --------------------
114
- The inner function `stft_istft` assumes the time axis of the input `Waveform` [2] is the last axis
115
- (`-1`). This matches the `(channels, samples)` shape convention.
116
-
117
- References
118
- ----------
119
- [1] `Spectrogram`
120
-
121
- [2] `Waveform`
122
-
123
139
  """
124
140
  def stft_istft(waveform: Waveform) -> Waveform:
125
- axisTime = -1
126
- arrayTarget = stft(waveform)
127
- spectrogram = callableNeedsSpectrogram(arrayTarget)
128
- return stft(spectrogram, lengthWaveform=waveform.shape[axisTime])
141
+ axisWaveformTime: int = getAxis()['time'].number
142
+
143
+ return stft(callableNeedsSpectrogram(stft(waveform)), lengthWaveform=waveform.shape[axisWaveformTime])
144
+
129
145
  return stft_istft
@@ -12,7 +12,8 @@ import tempfile
12
12
  import uuid
13
13
 
14
14
  if TYPE_CHECKING:
15
- from hunterHearsPy import AxisMetadata, FileDescriptorOrPath, Parameters_stft, Spectrogram, Waveform
15
+ from hunterHearsPy import AxisMetadata
16
+ from hunterHearsPy.theTypes import FileDescriptorOrPath, Parameters_stft, Spectrogram, Waveform
16
17
  from numpy import dtype, ndarray
17
18
  from soundfile import AudioData_2d, dtype_str as Options_dtype_str
18
19
  from typing import Any
@@ -7,7 +7,7 @@ from typing import overload, TYPE_CHECKING
7
7
  import resampy
8
8
 
9
9
  if TYPE_CHECKING:
10
- from hunterHearsPy import 形floating, 形Shape
10
+ from hunterHearsPy.theTypes import 形floating, 形Shape
11
11
  from numpy import dtype, float32, integer, ndarray
12
12
  from typing import Any
13
13
 
@@ -18,10 +18,6 @@ def resampleWaveform(waveform: ndarray[形Shape, dtype[形floating]], sampleRate
18
18
  def resampleWaveform(waveform: ndarray[形Shape, dtype[形floating]] | ndarray[形Shape, dtype[integer[Any]]], sampleRateDesired: float, sampleRateSource: float, axisTime: int = -1) -> ndarray[形Shape, dtype[形floating]] | ndarray[形Shape, dtype[float32]]:
19
19
  """Resample `waveform` array to `sampleRateDesired` along the `axisTime` axis.
20
20
 
21
- Warning
22
- -------
23
- The returned `ndarray` always has a floating-point `dtype`, even if the sample rate is unchanged.
24
-
25
21
  This function is _not_ regulated by the universal settings.
26
22
 
27
23
  Parameters