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.
Files changed (41) hide show
  1. sonusai/calc_metric_spenh.py +265 -233
  2. sonusai/data/genmixdb.yml +4 -2
  3. sonusai/data/silero_vad_v5.1.jit +0 -0
  4. sonusai/data/silero_vad_v5.1.onnx +0 -0
  5. sonusai/doc/doc.py +14 -0
  6. sonusai/genft.py +1 -1
  7. sonusai/genmetrics.py +15 -18
  8. sonusai/genmix.py +1 -1
  9. sonusai/genmixdb.py +30 -52
  10. sonusai/ir_metric.py +555 -0
  11. sonusai/metrics_summary.py +322 -0
  12. sonusai/mixture/__init__.py +6 -2
  13. sonusai/mixture/audio.py +139 -15
  14. sonusai/mixture/augmentation.py +199 -84
  15. sonusai/mixture/config.py +9 -4
  16. sonusai/mixture/constants.py +0 -1
  17. sonusai/mixture/datatypes.py +19 -10
  18. sonusai/mixture/generation.py +52 -64
  19. sonusai/mixture/helpers.py +38 -26
  20. sonusai/mixture/ir_delay.py +63 -0
  21. sonusai/mixture/mixdb.py +190 -46
  22. sonusai/mixture/targets.py +3 -6
  23. sonusai/mixture/truth_functions/energy.py +9 -5
  24. sonusai/mixture/truth_functions/metrics.py +1 -1
  25. sonusai/mkwav.py +1 -1
  26. sonusai/onnx_predict.py +1 -1
  27. sonusai/queries/queries.py +1 -1
  28. sonusai/utils/__init__.py +2 -0
  29. sonusai/utils/asr.py +1 -1
  30. sonusai/utils/load_object.py +8 -2
  31. sonusai/utils/stratified_shuffle_split.py +1 -1
  32. sonusai/utils/temp_seed.py +13 -0
  33. {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/METADATA +2 -2
  34. {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/RECORD +36 -35
  35. {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/WHEEL +1 -1
  36. sonusai/mixture/soundfile_audio.py +0 -130
  37. sonusai/mixture/sox_audio.py +0 -476
  38. sonusai/mixture/sox_augmentation.py +0 -136
  39. sonusai/mixture/torchaudio_audio.py +0 -106
  40. sonusai/mixture/torchaudio_augmentation.py +0 -109
  41. {sonusai-0.19.9.dist-info → sonusai-0.20.2.dist-info}/entry_points.txt +0 -0
@@ -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)]