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.
Files changed (35) hide show
  1. {hunterhearspy-1.3.0/src/hunterHearsPy.egg-info → hunterhearspy-1.4.0}/PKG-INFO +2 -8
  2. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/README.md +1 -7
  3. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/pyproject.toml +1 -1
  4. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/__init__.py +3 -1
  5. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_arrays.py +2 -1
  6. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_io.py +43 -47
  7. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/dataBaskets.py +1 -1
  8. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0/src/hunterHearsPy.egg-info}/PKG-INFO +2 -8
  9. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_io.py +5 -5
  10. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/LICENSE +0 -0
  11. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/setup.cfg +0 -0
  12. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_fft.py +0 -0
  13. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/_resample.py +0 -0
  14. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/amplitude.py +0 -0
  15. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/autoRevert.py +0 -0
  16. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/clippingArrays.py +0 -0
  17. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/py.typed +0 -0
  18. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/theSSOT.py +0 -0
  19. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/theTypes.py +0 -0
  20. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/windowingFunctions.py +0 -0
  21. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy/windowingFunctionsTensor.py +0 -0
  22. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/SOURCES.txt +0 -0
  23. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/dependency_links.txt +0 -0
  24. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/requires.txt +0 -0
  25. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/src/hunterHearsPy.egg-info/top_level.txt +0 -0
  26. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_arrays.py +0 -0
  27. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_fft.py +0 -0
  28. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_amplitude.py +0 -0
  29. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_autoRevert.py +0 -0
  30. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_clippingArrays.py +0 -0
  31. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_spectrogram.py +0 -0
  32. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_old_waveform.py +0 -0
  33. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_resample.py +0 -0
  34. {hunterhearspy-1.3.0 → hunterhearspy-1.4.0}/tests/test_windowingFunctions.py +0 -0
  35. {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.0
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` is giving me problems in the "ingest" functions.
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` is giving me problems in the "ingest" functions.
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "hunterHearsPy"
3
- version = "1.3.0"
3
+ version = "1.4.0"
4
4
  description = "Audio processing."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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 readAudioFile as readAudioFile, spectrogramToWAV as spectrogramToWAV, writeWAV as writeWAV
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
- # TODO document that if `samplesPadding` is odd, the extra pad-sample is added to samplesStop.
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 readAudioFile(pathFilename: FileDescriptorOrPath, sampleRateDesired: float | None = None, dtype_str: Options_dtype_str | None = None) -> Waveform:
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 `sampleRateDesired` differs from the file's
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
- sampleRateDesired : float | None = 44100
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 = sampleRateDesired or setting.sampleRate
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
- # NOTE If the following values were assigned directly to a `slice` object, the slice object would
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.0
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` is giving me problems in the "ingest" functions.
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 `sampleRateDesired`.
14
- @pytest.mark.parametrize('sampleRateDesired', (44100, 48000))
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, sampleRateDesired: float, dtype_str: Options_dtype_str | None, expected: Waveform) -> None:
28
- actual: Waveform = readAudioFile(pathFilename, sampleRateDesired, dtype_str)
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, sampleRateDesired=sampleRateDesired, dtype_str=dtype_str)
30
+ assert_array_equal(actual, expected, 'readAudioFile', pathFilename.name, sampleRate=sampleRate, dtype_str=dtype_str)
File without changes
File without changes