sonusai 0.19.9__py3-none-any.whl → 0.20.2__py3-none-any.whl
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.
- sonusai/calc_metric_spenh.py +265 -233
- sonusai/data/genmixdb.yml +4 -2
- sonusai/data/silero_vad_v5.1.jit +0 -0
- sonusai/data/silero_vad_v5.1.onnx +0 -0
- sonusai/doc/doc.py +14 -0
- sonusai/genft.py +1 -1
- sonusai/genmetrics.py +15 -18
- sonusai/genmix.py +1 -1
- sonusai/genmixdb.py +30 -52
- sonusai/ir_metric.py +555 -0
- sonusai/metrics_summary.py +322 -0
- sonusai/mixture/__init__.py +6 -2
- sonusai/mixture/audio.py +139 -15
- sonusai/mixture/augmentation.py +199 -84
- sonusai/mixture/config.py +9 -4
- sonusai/mixture/constants.py +0 -1
- sonusai/mixture/datatypes.py +19 -10
- sonusai/mixture/generation.py +52 -64
- sonusai/mixture/helpers.py +38 -26
- sonusai/mixture/ir_delay.py +63 -0
- sonusai/mixture/mixdb.py +190 -46
- sonusai/mixture/targets.py +3 -6
- sonusai/mixture/truth_functions/energy.py +9 -5
- sonusai/mixture/truth_functions/metrics.py +1 -1
- sonusai/mkwav.py +1 -1
- sonusai/onnx_predict.py +1 -1
- sonusai/queries/queries.py +1 -1
- sonusai/utils/__init__.py +2 -0
- sonusai/utils/asr.py +1 -1
- sonusai/utils/load_object.py +8 -2
- sonusai/utils/stratified_shuffle_split.py +1 -1
- sonusai/utils/temp_seed.py +13 -0
- {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/METADATA +2 -2
- {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/RECORD +36 -35
- {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/WHEEL +1 -1
- sonusai/mixture/soundfile_audio.py +0 -130
- sonusai/mixture/sox_audio.py +0 -476
- sonusai/mixture/sox_augmentation.py +0 -136
- sonusai/mixture/torchaudio_audio.py +0 -106
- sonusai/mixture/torchaudio_augmentation.py +0 -109
- {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/entry_points.txt +0 -0
sonusai/mixture/sox_audio.py
DELETED
@@ -1,476 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
|
3
|
-
import numpy as np
|
4
|
-
from sox import Transformer as SoxTransformer
|
5
|
-
|
6
|
-
from sonusai.mixture.datatypes import AudioT
|
7
|
-
from sonusai.mixture.datatypes import ImpulseResponseData
|
8
|
-
|
9
|
-
|
10
|
-
def read_impulse_response(name: str | Path) -> ImpulseResponseData:
|
11
|
-
"""Read impulse response data using SoX
|
12
|
-
|
13
|
-
:param name: File name
|
14
|
-
:return: ImpulseResponseData object
|
15
|
-
"""
|
16
|
-
from scipy.io import wavfile
|
17
|
-
|
18
|
-
from .datatypes import ImpulseResponseData
|
19
|
-
from .tokenized_shell_vars import tokenized_expand
|
20
|
-
|
21
|
-
expanded_name, _ = tokenized_expand(name)
|
22
|
-
|
23
|
-
# Read impulse response data from audio file
|
24
|
-
try:
|
25
|
-
sample_rate, data = wavfile.read(expanded_name)
|
26
|
-
except Exception as e:
|
27
|
-
if name != expanded_name:
|
28
|
-
raise OSError(f"Error reading {name} (expanded: {expanded_name}): {e}") from e
|
29
|
-
else:
|
30
|
-
raise OSError(f"Error reading {name}: {e}") from e
|
31
|
-
|
32
|
-
data = data.astype(np.float32)
|
33
|
-
offset = np.argmax(data)
|
34
|
-
data = data[offset:]
|
35
|
-
data = data / np.linalg.norm(data)
|
36
|
-
|
37
|
-
return ImpulseResponseData(name=str(name), sample_rate=sample_rate, data=data)
|
38
|
-
|
39
|
-
|
40
|
-
def read_audio(name: str | Path) -> AudioT:
|
41
|
-
"""Read audio data from a file using SoX
|
42
|
-
|
43
|
-
:param name: File name
|
44
|
-
:return: Array of time domain audio data
|
45
|
-
"""
|
46
|
-
from typing import Any
|
47
|
-
|
48
|
-
from sox.core import sox
|
49
|
-
|
50
|
-
from .constants import BIT_DEPTH
|
51
|
-
from .constants import CHANNEL_COUNT
|
52
|
-
from .constants import ENCODING
|
53
|
-
from .constants import SAMPLE_RATE
|
54
|
-
from .tokenized_shell_vars import tokenized_expand
|
55
|
-
|
56
|
-
def encode_output(buffer: Any) -> np.ndarray:
|
57
|
-
from .constants import BIT_DEPTH
|
58
|
-
from .constants import ENCODING
|
59
|
-
|
60
|
-
if BIT_DEPTH == 8:
|
61
|
-
return np.frombuffer(buffer, dtype=np.int8)
|
62
|
-
|
63
|
-
if BIT_DEPTH == 16:
|
64
|
-
return np.frombuffer(buffer, dtype=np.int16)
|
65
|
-
|
66
|
-
if BIT_DEPTH == 24:
|
67
|
-
return np.frombuffer(buffer, dtype=np.int32)
|
68
|
-
|
69
|
-
if BIT_DEPTH == 32:
|
70
|
-
if ENCODING == "floating-point":
|
71
|
-
return np.frombuffer(buffer, dtype=np.float32)
|
72
|
-
return np.frombuffer(buffer, dtype=np.int32)
|
73
|
-
|
74
|
-
if BIT_DEPTH == 64:
|
75
|
-
return np.frombuffer(buffer, dtype=np.float64)
|
76
|
-
|
77
|
-
raise ValueError(f"Invalid BIT_DEPTH {BIT_DEPTH}")
|
78
|
-
|
79
|
-
expanded_name, _ = tokenized_expand(name)
|
80
|
-
|
81
|
-
try:
|
82
|
-
# Read in and convert to desired format
|
83
|
-
# NOTE: pysox format transformations do not handle encoding properly; need to use direct call to sox instead
|
84
|
-
args = [
|
85
|
-
"-D",
|
86
|
-
"-G",
|
87
|
-
expanded_name,
|
88
|
-
"-t",
|
89
|
-
"raw",
|
90
|
-
"-r",
|
91
|
-
str(SAMPLE_RATE),
|
92
|
-
"-b",
|
93
|
-
str(BIT_DEPTH),
|
94
|
-
"-c",
|
95
|
-
str(CHANNEL_COUNT),
|
96
|
-
"-e",
|
97
|
-
ENCODING,
|
98
|
-
"-",
|
99
|
-
"remix",
|
100
|
-
"1",
|
101
|
-
]
|
102
|
-
status, out, err = sox(args, None, False)
|
103
|
-
if status != 0:
|
104
|
-
raise RuntimeError(f"sox stdout: {out}\nsox stderr: {err}") # noqa: TRY301
|
105
|
-
|
106
|
-
return encode_output(out)
|
107
|
-
|
108
|
-
except Exception as e:
|
109
|
-
if name != expanded_name:
|
110
|
-
raise OSError(f"Error reading {name} (expanded: {expanded_name}):\n{e}") from e
|
111
|
-
else:
|
112
|
-
raise OSError(f"Error reading {name}:\n{e}") from e
|
113
|
-
|
114
|
-
|
115
|
-
class Transformer(SoxTransformer):
|
116
|
-
"""Override certain sox.Transformer methods"""
|
117
|
-
|
118
|
-
def fir(self, coefficients):
|
119
|
-
"""Use SoX's FFT convolution engine with given FIR filter coefficients.
|
120
|
-
|
121
|
-
The SonusAI override allows coefficients to be either a list of numbers
|
122
|
-
or a string containing a text file with the coefficients.
|
123
|
-
|
124
|
-
Parameters
|
125
|
-
----------
|
126
|
-
coefficients : list or str
|
127
|
-
fir filter coefficients
|
128
|
-
|
129
|
-
"""
|
130
|
-
from sox.core import is_number
|
131
|
-
|
132
|
-
if not isinstance(coefficients, list) and not isinstance(coefficients, str):
|
133
|
-
raise TypeError("coefficients must be a list or a str.")
|
134
|
-
|
135
|
-
if isinstance(coefficients, list) and not all(is_number(c) for c in coefficients):
|
136
|
-
raise TypeError("coefficients list must be numbers.")
|
137
|
-
|
138
|
-
effect_args = ["fir"]
|
139
|
-
if isinstance(coefficients, list):
|
140
|
-
effect_args.extend([f"{c:f}" for c in coefficients])
|
141
|
-
else:
|
142
|
-
effect_args.append(coefficients)
|
143
|
-
|
144
|
-
self.effects.extend(effect_args)
|
145
|
-
self.effects_log.append("fir")
|
146
|
-
|
147
|
-
return self
|
148
|
-
|
149
|
-
def tempo(self, factor, audio_type=None, quick=False):
|
150
|
-
"""Time stretch audio without changing pitch.
|
151
|
-
|
152
|
-
This effect uses the WSOLA algorithm. The audio is chopped up into
|
153
|
-
segments which are then shifted in the time domain and overlapped
|
154
|
-
(cross-faded) at points where their waveforms are most similar as
|
155
|
-
determined by measurement of least squares.
|
156
|
-
|
157
|
-
The SonusAI override does not generate a warning for small factors.
|
158
|
-
The sox.Transformer's implementation of stretch does not invert
|
159
|
-
the factor even though it says that it does; this invalidates the
|
160
|
-
factor size check and produces the wrong result.
|
161
|
-
|
162
|
-
Parameters
|
163
|
-
----------
|
164
|
-
factor : float
|
165
|
-
The ratio of new tempo to the old tempo.
|
166
|
-
For ex. 1.1 speeds up the tempo by 10%; 0.9 slows it down by 10%.
|
167
|
-
audio_type : str
|
168
|
-
Type of audio, which optimizes algorithm parameters. One of:
|
169
|
-
* m : Music,
|
170
|
-
* s : Speech,
|
171
|
-
* l : Linear (useful when factor is close to 1),
|
172
|
-
quick : bool, default=False
|
173
|
-
If True, this effect will run faster but with lower sound quality.
|
174
|
-
|
175
|
-
See Also
|
176
|
-
--------
|
177
|
-
stretch, speed, pitch
|
178
|
-
|
179
|
-
"""
|
180
|
-
from sox.core import is_number
|
181
|
-
from sox.log import logger
|
182
|
-
|
183
|
-
if not is_number(factor) or factor <= 0:
|
184
|
-
raise ValueError("factor must be a positive number")
|
185
|
-
|
186
|
-
if factor < 0.5 or factor > 2:
|
187
|
-
logger.warning("Using an extreme time stretching factor. Quality of results will be poor")
|
188
|
-
|
189
|
-
if audio_type not in [None, "m", "s", "l"]:
|
190
|
-
raise ValueError("audio_type must be one of None, 'm', 's', or 'l'.")
|
191
|
-
|
192
|
-
if not isinstance(quick, bool):
|
193
|
-
raise TypeError("quick must be a boolean")
|
194
|
-
|
195
|
-
effect_args = ["tempo"]
|
196
|
-
|
197
|
-
if quick:
|
198
|
-
effect_args.append("-q")
|
199
|
-
|
200
|
-
if audio_type is not None:
|
201
|
-
effect_args.append(f"-{audio_type}")
|
202
|
-
|
203
|
-
effect_args.append(f"{factor:f}")
|
204
|
-
|
205
|
-
self.effects.extend(effect_args)
|
206
|
-
self.effects_log.append("tempo")
|
207
|
-
|
208
|
-
return self
|
209
|
-
|
210
|
-
def build( # pyright: ignore [reportIncompatibleMethodOverride]
|
211
|
-
self,
|
212
|
-
input_filepath: str | Path | None = None,
|
213
|
-
output_filepath: str | Path | None = None,
|
214
|
-
input_array: np.ndarray | None = None,
|
215
|
-
sample_rate_in: float | None = None,
|
216
|
-
extra_args: list[str] | None = None,
|
217
|
-
return_output: bool = False,
|
218
|
-
) -> tuple[bool, str | None, str | None]:
|
219
|
-
"""Given an input file or array, creates an output_file on disk by
|
220
|
-
executing the current set of commands. This function returns True on
|
221
|
-
success. If return_output is True, this function returns a triple of
|
222
|
-
(status, out, err), giving the success state, along with stdout and
|
223
|
-
stderr returned by sox.
|
224
|
-
|
225
|
-
Parameters
|
226
|
-
----------
|
227
|
-
input_filepath : str or None
|
228
|
-
Either path to input audio file or None for array input.
|
229
|
-
output_filepath : str
|
230
|
-
Path to desired output file. If a file already exists at
|
231
|
-
the given path, the file will be overwritten.
|
232
|
-
If '-n', no file is created.
|
233
|
-
input_array : np.ndarray or None
|
234
|
-
An np.ndarray of an waveform with shape (n_samples, n_channels).
|
235
|
-
sample_rate_in must also be provided.
|
236
|
-
If None, input_filepath must be specified.
|
237
|
-
sample_rate_in : int
|
238
|
-
Sample rate of input_array.
|
239
|
-
This argument is ignored if input_array is None.
|
240
|
-
extra_args : list or None, default=None
|
241
|
-
If a list is given, these additional arguments are passed to SoX
|
242
|
-
at the end of the list of effects.
|
243
|
-
Don't use this argument unless you know exactly what you're doing!
|
244
|
-
return_output : bool, default=False
|
245
|
-
If True, returns the status and information sent to stderr and
|
246
|
-
stdout as a tuple (status, stdout, stderr).
|
247
|
-
If output_filepath is None, return_output=True by default.
|
248
|
-
If False, returns True on success.
|
249
|
-
|
250
|
-
Returns
|
251
|
-
-------
|
252
|
-
status : bool
|
253
|
-
True on success.
|
254
|
-
out : str (optional)
|
255
|
-
This is not returned unless return_output is True.
|
256
|
-
When returned, captures the stdout produced by sox.
|
257
|
-
err : str (optional)
|
258
|
-
This is not returned unless return_output is True.
|
259
|
-
When returned, captures the stderr produced by sox.
|
260
|
-
|
261
|
-
Examples
|
262
|
-
--------
|
263
|
-
> import numpy as np
|
264
|
-
> import sox
|
265
|
-
> tfm = sox.Transformer()
|
266
|
-
> sample_rate = 44100
|
267
|
-
> y = np.sin(2 * np.pi * 440.0 * np.arange(sample_rate * 1.0) / sample_rate)
|
268
|
-
|
269
|
-
file in, file out - basic usage
|
270
|
-
|
271
|
-
> status = tfm.build('path/to/input.wav', 'path/to/output.mp3')
|
272
|
-
|
273
|
-
file in, file out - equivalent usage
|
274
|
-
|
275
|
-
> status = tfm.build(
|
276
|
-
input_filepath='path/to/input.wav',
|
277
|
-
output_filepath='path/to/output.mp3'
|
278
|
-
)
|
279
|
-
|
280
|
-
array in, file out
|
281
|
-
|
282
|
-
> status = tfm.build(
|
283
|
-
input_array=y, sample_rate_in=sample_rate,
|
284
|
-
output_filepath='path/to/output.mp3'
|
285
|
-
)
|
286
|
-
|
287
|
-
"""
|
288
|
-
from sox import file_info
|
289
|
-
from sox.core import SoxError
|
290
|
-
from sox.core import sox
|
291
|
-
from sox.log import logger
|
292
|
-
|
293
|
-
input_format, input_filepath = self._parse_inputs(input_filepath, input_array, sample_rate_in)
|
294
|
-
|
295
|
-
if output_filepath is None:
|
296
|
-
raise ValueError("output_filepath is not specified!")
|
297
|
-
|
298
|
-
# set output parameters
|
299
|
-
if input_filepath == output_filepath:
|
300
|
-
raise ValueError("input_filepath must be different from output_filepath.")
|
301
|
-
file_info.validate_output_file(output_filepath)
|
302
|
-
|
303
|
-
args = []
|
304
|
-
args.extend(self.globals)
|
305
|
-
args.extend(self._input_format_args(input_format))
|
306
|
-
args.append(input_filepath)
|
307
|
-
args.extend(self._output_format_args(self.output_format))
|
308
|
-
args.append(output_filepath)
|
309
|
-
args.extend(self.effects)
|
310
|
-
|
311
|
-
if extra_args is not None:
|
312
|
-
if not isinstance(extra_args, list):
|
313
|
-
raise ValueError("extra_args must be a list.")
|
314
|
-
args.extend(extra_args)
|
315
|
-
|
316
|
-
status, out, err = sox(args, input_array, True)
|
317
|
-
if status != 0:
|
318
|
-
raise SoxError(f"Stdout: {out}\nStderr: {err}")
|
319
|
-
|
320
|
-
logger.info("Created %s with effects: %s", output_filepath, " ".join(self.effects_log))
|
321
|
-
|
322
|
-
if return_output:
|
323
|
-
return status, out, err # pyright: ignore [reportReturnType]
|
324
|
-
|
325
|
-
return True, None, None
|
326
|
-
|
327
|
-
def build_array( # pyright: ignore [reportIncompatibleMethodOverride]
|
328
|
-
self,
|
329
|
-
input_filepath: str | Path | None = None,
|
330
|
-
input_array: np.ndarray | None = None,
|
331
|
-
sample_rate_in: int | None = None,
|
332
|
-
extra_args: list[str] | None = None,
|
333
|
-
) -> np.ndarray:
|
334
|
-
"""Given an input file or array, returns the output as a numpy array
|
335
|
-
by executing the current set of commands. By default, the array will
|
336
|
-
have the same sample rate as the input file unless otherwise specified
|
337
|
-
using set_output_format. Functions such as channels and convert
|
338
|
-
will be ignored!
|
339
|
-
|
340
|
-
The SonusAI override does not generate a warning for rate transforms.
|
341
|
-
|
342
|
-
Parameters
|
343
|
-
----------
|
344
|
-
input_filepath : str, Path or None
|
345
|
-
Either path to input audio file or None.
|
346
|
-
input_array : np.ndarray or None
|
347
|
-
A np.ndarray of a waveform with shape (n_samples, n_channels).
|
348
|
-
If this argument is passed, sample_rate_in must also be provided.
|
349
|
-
If None, input_filepath must be specified.
|
350
|
-
sample_rate_in : int
|
351
|
-
Sample rate of input_array.
|
352
|
-
This argument is ignored if input_array is None.
|
353
|
-
extra_args : list or None, default=None
|
354
|
-
If a list is given, these additional arguments are passed to SoX
|
355
|
-
at the end of the list of effects.
|
356
|
-
Don't use this argument unless you know exactly what you're doing!
|
357
|
-
|
358
|
-
Returns
|
359
|
-
-------
|
360
|
-
output_array : np.ndarray
|
361
|
-
Output audio as a numpy array
|
362
|
-
|
363
|
-
Examples
|
364
|
-
--------
|
365
|
-
|
366
|
-
> import numpy as np
|
367
|
-
> import sox
|
368
|
-
> tfm = sox.Transformer()
|
369
|
-
> sample_rate = 44100
|
370
|
-
> y = np.sin(2 * np.pi * 440.0 * np.arange(sample_rate * 1.0) / sample_rate)
|
371
|
-
|
372
|
-
file in, array out
|
373
|
-
|
374
|
-
> output_array = tfm.build(input_filepath='path/to/input.wav')
|
375
|
-
|
376
|
-
array in, array out
|
377
|
-
|
378
|
-
> output_array = tfm.build(input_array=y, sample_rate_in=sample_rate)
|
379
|
-
|
380
|
-
specifying the output sample rate
|
381
|
-
|
382
|
-
> tfm.set_output_format(rate=8000)
|
383
|
-
> output_array = tfm.build(input_array=y, sample_rate_in=sample_rate)
|
384
|
-
|
385
|
-
if an effect changes the number of channels, you must explicitly
|
386
|
-
specify the number of output channels
|
387
|
-
|
388
|
-
> tfm.remix(remix_dictionary={1: [1], 2: [1], 3: [1]})
|
389
|
-
> tfm.set_output_format(channels=3)
|
390
|
-
> output_array = tfm.build(input_array=y, sample_rate_in=sample_rate)
|
391
|
-
|
392
|
-
|
393
|
-
"""
|
394
|
-
from sox.core import SoxError
|
395
|
-
from sox.core import sox
|
396
|
-
from sox.log import logger
|
397
|
-
from sox.transform import ENCODINGS_MAPPING
|
398
|
-
|
399
|
-
input_format, input_filepath = self._parse_inputs(input_filepath, input_array, sample_rate_in)
|
400
|
-
|
401
|
-
# check if any of the below commands are part of the effects chain
|
402
|
-
ignored_commands = ["channels", "convert"]
|
403
|
-
if set(ignored_commands) & set(self.effects_log):
|
404
|
-
logger.warning(
|
405
|
-
"When outputting to an array, channels and convert "
|
406
|
-
+ "effects may be ignored. Use set_output_format() to "
|
407
|
-
+ "specify output formats."
|
408
|
-
)
|
409
|
-
|
410
|
-
output_filepath = "-"
|
411
|
-
|
412
|
-
if input_format.get("file_type") is None:
|
413
|
-
encoding_out = np.int16
|
414
|
-
else:
|
415
|
-
encoding_out = next(k for k, v in ENCODINGS_MAPPING.items() if input_format["file_type"] == v)
|
416
|
-
|
417
|
-
n_bits = np.dtype(encoding_out).itemsize * 8
|
418
|
-
|
419
|
-
output_format = {
|
420
|
-
"file_type": "raw",
|
421
|
-
"rate": sample_rate_in,
|
422
|
-
"bits": n_bits,
|
423
|
-
"channels": input_format["channels"],
|
424
|
-
"encoding": None,
|
425
|
-
"comments": None,
|
426
|
-
"append_comments": True,
|
427
|
-
}
|
428
|
-
|
429
|
-
if self.output_format.get("rate") is not None:
|
430
|
-
output_format["rate"] = self.output_format["rate"]
|
431
|
-
|
432
|
-
if self.output_format.get("channels") is not None:
|
433
|
-
output_format["channels"] = self.output_format["channels"]
|
434
|
-
|
435
|
-
if self.output_format.get("bits") is not None:
|
436
|
-
n_bits = self.output_format["bits"]
|
437
|
-
output_format["bits"] = n_bits
|
438
|
-
|
439
|
-
match n_bits:
|
440
|
-
case 8:
|
441
|
-
encoding_out = np.int8 # type: ignore[assignment]
|
442
|
-
case 16:
|
443
|
-
encoding_out = np.int16
|
444
|
-
case 32:
|
445
|
-
encoding_out = np.float32 # type: ignore[assignment]
|
446
|
-
case 64:
|
447
|
-
encoding_out = np.float64 # type: ignore[assignment]
|
448
|
-
case _:
|
449
|
-
raise ValueError(f"invalid n_bits {n_bits}")
|
450
|
-
|
451
|
-
args = []
|
452
|
-
args.extend(self.globals)
|
453
|
-
args.extend(self._input_format_args(input_format))
|
454
|
-
args.append(input_filepath)
|
455
|
-
args.extend(self._output_format_args(output_format))
|
456
|
-
args.append(output_filepath)
|
457
|
-
args.extend(self.effects)
|
458
|
-
|
459
|
-
if extra_args is not None:
|
460
|
-
if not isinstance(extra_args, list):
|
461
|
-
raise ValueError("extra_args must be a list.")
|
462
|
-
args.extend(extra_args)
|
463
|
-
|
464
|
-
status, out, err = sox(args, input_array, False)
|
465
|
-
if status != 0:
|
466
|
-
raise SoxError(f"Stdout: {out}\nStderr: {err}")
|
467
|
-
|
468
|
-
out = np.frombuffer(out, dtype=encoding_out) # pyright: ignore [reportArgumentType, reportCallIssue]
|
469
|
-
if output_format["channels"] > 1:
|
470
|
-
out = out.reshape(
|
471
|
-
(output_format["channels"], int(len(out) / output_format["channels"])),
|
472
|
-
order="F",
|
473
|
-
).T
|
474
|
-
logger.info("Created array with effects: %s", " ".join(self.effects_log))
|
475
|
-
|
476
|
-
return out
|
@@ -1,136 +0,0 @@
|
|
1
|
-
from sonusai.mixture.datatypes import AudioT
|
2
|
-
from sonusai.mixture.datatypes import Augmentation
|
3
|
-
from sonusai.mixture.datatypes import ImpulseResponseData
|
4
|
-
|
5
|
-
|
6
|
-
def apply_augmentation(audio: AudioT, augmentation: Augmentation, frame_length: int = 1) -> AudioT:
|
7
|
-
"""Apply augmentations to audio data using SoX
|
8
|
-
|
9
|
-
:param audio: Audio
|
10
|
-
:param augmentation: Augmentation
|
11
|
-
:param frame_length: Pad resulting audio to be a multiple of this
|
12
|
-
:return: Augmented audio
|
13
|
-
"""
|
14
|
-
from .augmentation import pad_audio_to_frame
|
15
|
-
from .constants import BIT_DEPTH
|
16
|
-
from .constants import CHANNEL_COUNT
|
17
|
-
from .constants import ENCODING
|
18
|
-
from .constants import SAMPLE_RATE
|
19
|
-
from .sox_audio import Transformer
|
20
|
-
|
21
|
-
has_effects = False
|
22
|
-
|
23
|
-
try:
|
24
|
-
# Apply augmentations
|
25
|
-
tfm = Transformer()
|
26
|
-
tfm.set_input_format(rate=SAMPLE_RATE, bits=BIT_DEPTH, channels=CHANNEL_COUNT, encoding=ENCODING)
|
27
|
-
tfm.set_output_format(rate=SAMPLE_RATE, bits=BIT_DEPTH, channels=CHANNEL_COUNT, encoding=ENCODING)
|
28
|
-
|
29
|
-
# TODO
|
30
|
-
# Always normalize and remove normalize from list of available augmentations
|
31
|
-
# Normalize to globally set level (should this be a global config parameter,
|
32
|
-
# or hard-coded into the script?)
|
33
|
-
if augmentation.normalize is not None:
|
34
|
-
tfm.norm(db_level=augmentation.normalize)
|
35
|
-
has_effects = True
|
36
|
-
|
37
|
-
if augmentation.gain is not None:
|
38
|
-
tfm.gain(gain_db=augmentation.gain, normalize=False)
|
39
|
-
has_effects = True
|
40
|
-
|
41
|
-
if augmentation.pitch is not None:
|
42
|
-
tfm.pitch(n_semitones=float(augmentation.pitch) / 100)
|
43
|
-
tfm.rate(samplerate=SAMPLE_RATE)
|
44
|
-
has_effects = True
|
45
|
-
|
46
|
-
if augmentation.tempo is not None:
|
47
|
-
tfm.tempo(factor=float(augmentation.tempo), audio_type="s")
|
48
|
-
has_effects = True
|
49
|
-
|
50
|
-
if augmentation.eq1 is not None:
|
51
|
-
tfm.equalizer(*augmentation.eq1)
|
52
|
-
has_effects = True
|
53
|
-
|
54
|
-
if augmentation.eq2 is not None:
|
55
|
-
tfm.equalizer(*augmentation.eq2)
|
56
|
-
has_effects = True
|
57
|
-
|
58
|
-
if augmentation.eq3 is not None:
|
59
|
-
tfm.equalizer(*augmentation.eq3)
|
60
|
-
has_effects = True
|
61
|
-
|
62
|
-
if augmentation.lpf is not None:
|
63
|
-
tfm.lowpass(frequency=augmentation.lpf)
|
64
|
-
has_effects = True
|
65
|
-
|
66
|
-
if has_effects:
|
67
|
-
audio_out = tfm.build_array(input_array=audio, sample_rate_in=SAMPLE_RATE)
|
68
|
-
else:
|
69
|
-
audio_out = audio
|
70
|
-
|
71
|
-
except Exception as e:
|
72
|
-
raise RuntimeError(f"Error applying {augmentation}: {e}") from e
|
73
|
-
|
74
|
-
# make sure length is multiple of frame_length
|
75
|
-
return pad_audio_to_frame(audio=audio_out, frame_length=frame_length)
|
76
|
-
|
77
|
-
|
78
|
-
def apply_impulse_response(audio: AudioT, ir: ImpulseResponseData) -> AudioT:
|
79
|
-
"""Apply impulse response to audio data using SoX
|
80
|
-
|
81
|
-
:param audio: Audio
|
82
|
-
:param ir: Impulse response data
|
83
|
-
:return: Augmented audio
|
84
|
-
"""
|
85
|
-
import math
|
86
|
-
import tempfile
|
87
|
-
from pathlib import Path
|
88
|
-
|
89
|
-
import numpy as np
|
90
|
-
|
91
|
-
from sonusai.utils import linear_to_db
|
92
|
-
|
93
|
-
from .constants import SAMPLE_RATE
|
94
|
-
from .sox_audio import Transformer
|
95
|
-
|
96
|
-
# Early exit if no ir or if all audio is zero
|
97
|
-
if ir is None or not audio.any():
|
98
|
-
return audio
|
99
|
-
|
100
|
-
# Get current maximum level in dB
|
101
|
-
max_db = linear_to_db(max(abs(audio)))
|
102
|
-
|
103
|
-
# Convert audio to IR sample rate
|
104
|
-
tfm = Transformer()
|
105
|
-
tfm.set_output_format(rate=ir.sample_rate)
|
106
|
-
audio_out = tfm.build_array(input_array=audio, sample_rate_in=SAMPLE_RATE)
|
107
|
-
|
108
|
-
# Pad audio to align with original and give enough room for IR tail
|
109
|
-
pad = math.ceil(ir.length / 2)
|
110
|
-
audio_out = np.pad(array=audio_out, pad_width=(pad, pad))
|
111
|
-
|
112
|
-
# Write coefficients to temporary file
|
113
|
-
temp = tempfile.NamedTemporaryFile(mode="w+t")
|
114
|
-
for d in ir.data:
|
115
|
-
temp.write(f"{d:f}\n")
|
116
|
-
temp.seek(0)
|
117
|
-
|
118
|
-
# Apply IR and convert back to global sample rate
|
119
|
-
tfm = Transformer()
|
120
|
-
tfm.set_output_format(rate=SAMPLE_RATE)
|
121
|
-
tfm.fir(coefficients=temp.name) # pyright: ignore [reportArgumentType]
|
122
|
-
try:
|
123
|
-
audio_out = tfm.build_array(input_array=audio_out, sample_rate_in=ir.sample_rate)
|
124
|
-
except Exception as e:
|
125
|
-
raise RuntimeError(f"Error applying IR: {e}") from e
|
126
|
-
|
127
|
-
path = Path(temp.name)
|
128
|
-
temp.close()
|
129
|
-
path.unlink()
|
130
|
-
|
131
|
-
# Reset level to previous max value
|
132
|
-
tfm = Transformer()
|
133
|
-
tfm.norm(db_level=max_db)
|
134
|
-
audio_out = tfm.build_array(input_array=audio_out, sample_rate_in=SAMPLE_RATE)
|
135
|
-
|
136
|
-
return audio_out[: len(audio)]
|