Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/torchaudio/_backend/__init__.py +61 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/common.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/ffmpeg.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/soundfile.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/soundfile_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/sox.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_backend/backend.py +53 -0
- lib/python3.12/site-packages/torchaudio/_backend/common.py +52 -0
- lib/python3.12/site-packages/torchaudio/_backend/ffmpeg.py +334 -0
- lib/python3.12/site-packages/torchaudio/_backend/soundfile.py +54 -0
- lib/python3.12/site-packages/torchaudio/_backend/soundfile_backend.py +457 -0
- lib/python3.12/site-packages/torchaudio/_backend/sox.py +91 -0
- lib/python3.12/site-packages/torchaudio/_backend/utils.py +317 -0
- lib/python3.12/site-packages/torchaudio/_extension/__init__.py +74 -0
- lib/python3.12/site-packages/torchaudio/_extension/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_extension/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/_extension/utils.py +180 -0
- lib/python3.12/site-packages/torchaudio/backend/__init__.py +8 -0
- lib/python3.12/site-packages/torchaudio/backend/_no_backend.py +25 -0
- lib/python3.12/site-packages/torchaudio/backend/_sox_io_backend.py +294 -0
- lib/python3.12/site-packages/torchaudio/backend/common.py +13 -0
- lib/python3.12/site-packages/torchaudio/backend/soundfile_backend.py +14 -0
- lib/python3.12/site-packages/torchaudio/backend/sox_io_backend.py +14 -0
- lib/python3.12/site-packages/torchaudio/datasets/__pycache__/cmudict.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/datasets/__pycache__/gtzan.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/datasets/__pycache__/ljspeech.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/datasets/__pycache__/musdb_hq.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/functional/__init__.py +127 -0
- lib/python3.12/site-packages/torchaudio/functional/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/functional/__pycache__/_alignment.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/functional/__pycache__/filtering.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/functional/_alignment.py +128 -0
- lib/python3.12/site-packages/torchaudio/functional/filtering.py +1670 -0
- lib/python3.12/site-packages/torchaudio/functional/functional.py +2535 -0
- lib/python3.12/site-packages/torchaudio/lib/__init__.py +0 -0
- lib/python3.12/site-packages/torchaudio/lib/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__init__.py +85 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/_hdemucs.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/conformer.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/conv_tasnet.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/deepspeech.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/emformer.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/rnnt.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/rnnt_decoder.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/tacotron2.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/torchaudio/models/__pycache__/wav2letter.cpython-312.pyc +0 -0
lib/python3.12/site-packages/torchaudio/_backend/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Optional
|
| 2 |
+
|
| 3 |
+
from torchaudio._internal.module_utils import deprecated
|
| 4 |
+
|
| 5 |
+
from . import utils
|
| 6 |
+
from .common import AudioMetaData
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"AudioMetaData",
|
| 10 |
+
"load",
|
| 11 |
+
"info",
|
| 12 |
+
"save",
|
| 13 |
+
"list_audio_backends",
|
| 14 |
+
"get_audio_backend",
|
| 15 |
+
"set_audio_backend",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
info = utils.get_info_func()
|
| 20 |
+
load = utils.get_load_func()
|
| 21 |
+
save = utils.get_save_func()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def list_audio_backends() -> List[str]:
|
| 25 |
+
"""List available backends
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
list of str: The list of available backends.
|
| 29 |
+
|
| 30 |
+
The possible values are; ``"ffmpeg"``, ``"sox"`` and ``"soundfile"``.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
return list(utils.get_available_backends().keys())
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Temporary until global backend is removed
|
| 37 |
+
@deprecated("With dispatcher enabled, this function is no-op. You can remove the function call.")
|
| 38 |
+
def get_audio_backend() -> Optional[str]:
|
| 39 |
+
"""Get the name of the current global backend
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
str or None:
|
| 43 |
+
If dispatcher mode is enabled, returns ``None`` otherwise,
|
| 44 |
+
the name of current backend or ``None`` (no backend is set).
|
| 45 |
+
"""
|
| 46 |
+
return None
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# Temporary until global backend is removed
|
| 50 |
+
@deprecated("With dispatcher enabled, this function is no-op. You can remove the function call.")
|
| 51 |
+
def set_audio_backend(backend: Optional[str]): # noqa
|
| 52 |
+
"""Set the global backend.
|
| 53 |
+
|
| 54 |
+
This is a no-op when dispatcher mode is enabled.
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
backend (str or None): Name of the backend.
|
| 58 |
+
One of ``"sox_io"`` or ``"soundfile"`` based on availability
|
| 59 |
+
of the system. If ``None`` is provided the current backend is unassigned.
|
| 60 |
+
"""
|
| 61 |
+
pass
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (2.15 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/backend.cpython-312.pyc
ADDED
|
Binary file (2.73 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/common.cpython-312.pyc
ADDED
|
Binary file (2.31 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/ffmpeg.cpython-312.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/soundfile.cpython-312.pyc
ADDED
|
Binary file (2.89 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/soundfile_backend.cpython-312.pyc
ADDED
|
Binary file (16.8 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/sox.cpython-312.pyc
ADDED
|
Binary file (4.47 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (15.5 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_backend/backend.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from abc import ABC, abstractmethod
|
| 3 |
+
from typing import BinaryIO, Optional, Tuple, Union
|
| 4 |
+
|
| 5 |
+
from torch import Tensor
|
| 6 |
+
from torchaudio.io import CodecConfig
|
| 7 |
+
|
| 8 |
+
from .common import AudioMetaData
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Backend(ABC):
|
| 12 |
+
@staticmethod
|
| 13 |
+
@abstractmethod
|
| 14 |
+
def info(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str], buffer_size: int = 4096) -> AudioMetaData:
|
| 15 |
+
raise NotImplementedError
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
@abstractmethod
|
| 19 |
+
def load(
|
| 20 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 21 |
+
frame_offset: int = 0,
|
| 22 |
+
num_frames: int = -1,
|
| 23 |
+
normalize: bool = True,
|
| 24 |
+
channels_first: bool = True,
|
| 25 |
+
format: Optional[str] = None,
|
| 26 |
+
buffer_size: int = 4096,
|
| 27 |
+
) -> Tuple[Tensor, int]:
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
@staticmethod
|
| 31 |
+
@abstractmethod
|
| 32 |
+
def save(
|
| 33 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 34 |
+
src: Tensor,
|
| 35 |
+
sample_rate: int,
|
| 36 |
+
channels_first: bool = True,
|
| 37 |
+
format: Optional[str] = None,
|
| 38 |
+
encoding: Optional[str] = None,
|
| 39 |
+
bits_per_sample: Optional[int] = None,
|
| 40 |
+
buffer_size: int = 4096,
|
| 41 |
+
compression: Optional[Union[CodecConfig, float, int]] = None,
|
| 42 |
+
) -> None:
|
| 43 |
+
raise NotImplementedError
|
| 44 |
+
|
| 45 |
+
@staticmethod
|
| 46 |
+
@abstractmethod
|
| 47 |
+
def can_decode(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str]) -> bool:
|
| 48 |
+
raise NotImplementedError
|
| 49 |
+
|
| 50 |
+
@staticmethod
|
| 51 |
+
@abstractmethod
|
| 52 |
+
def can_encode(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str]) -> bool:
|
| 53 |
+
raise NotImplementedError
|
lib/python3.12/site-packages/torchaudio/_backend/common.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class AudioMetaData:
|
| 2 |
+
"""AudioMetaData()
|
| 3 |
+
|
| 4 |
+
Return type of ``torchaudio.info`` function.
|
| 5 |
+
|
| 6 |
+
:ivar int sample_rate: Sample rate
|
| 7 |
+
:ivar int num_frames: The number of frames
|
| 8 |
+
:ivar int num_channels: The number of channels
|
| 9 |
+
:ivar int bits_per_sample: The number of bits per sample. This is 0 for lossy formats,
|
| 10 |
+
or when it cannot be accurately inferred.
|
| 11 |
+
:ivar str encoding: Audio encoding
|
| 12 |
+
The values encoding can take are one of the following:
|
| 13 |
+
|
| 14 |
+
* ``PCM_S``: Signed integer linear PCM
|
| 15 |
+
* ``PCM_U``: Unsigned integer linear PCM
|
| 16 |
+
* ``PCM_F``: Floating point linear PCM
|
| 17 |
+
* ``FLAC``: Flac, Free Lossless Audio Codec
|
| 18 |
+
* ``ULAW``: Mu-law
|
| 19 |
+
* ``ALAW``: A-law
|
| 20 |
+
* ``MP3`` : MP3, MPEG-1 Audio Layer III
|
| 21 |
+
* ``VORBIS``: OGG Vorbis
|
| 22 |
+
* ``AMR_WB``: Adaptive Multi-Rate Wideband
|
| 23 |
+
* ``AMR_NB``: Adaptive Multi-Rate Narrowband
|
| 24 |
+
* ``OPUS``: Opus
|
| 25 |
+
* ``HTK``: Single channel 16-bit PCM
|
| 26 |
+
* ``UNKNOWN`` : None of above
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(
|
| 30 |
+
self,
|
| 31 |
+
sample_rate: int,
|
| 32 |
+
num_frames: int,
|
| 33 |
+
num_channels: int,
|
| 34 |
+
bits_per_sample: int,
|
| 35 |
+
encoding: str,
|
| 36 |
+
):
|
| 37 |
+
self.sample_rate = sample_rate
|
| 38 |
+
self.num_frames = num_frames
|
| 39 |
+
self.num_channels = num_channels
|
| 40 |
+
self.bits_per_sample = bits_per_sample
|
| 41 |
+
self.encoding = encoding
|
| 42 |
+
|
| 43 |
+
def __str__(self):
|
| 44 |
+
return (
|
| 45 |
+
f"AudioMetaData("
|
| 46 |
+
f"sample_rate={self.sample_rate}, "
|
| 47 |
+
f"num_frames={self.num_frames}, "
|
| 48 |
+
f"num_channels={self.num_channels}, "
|
| 49 |
+
f"bits_per_sample={self.bits_per_sample}, "
|
| 50 |
+
f"encoding={self.encoding}"
|
| 51 |
+
f")"
|
| 52 |
+
)
|
lib/python3.12/site-packages/torchaudio/_backend/ffmpeg.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import sys
|
| 4 |
+
from typing import BinaryIO, Optional, Tuple, Union
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torchaudio
|
| 8 |
+
|
| 9 |
+
from .backend import Backend
|
| 10 |
+
from .common import AudioMetaData
|
| 11 |
+
|
| 12 |
+
InputType = Union[BinaryIO, str, os.PathLike]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def info_audio(
|
| 16 |
+
src: InputType,
|
| 17 |
+
format: Optional[str],
|
| 18 |
+
buffer_size: int = 4096,
|
| 19 |
+
) -> AudioMetaData:
|
| 20 |
+
s = torchaudio.io.StreamReader(src, format, None, buffer_size)
|
| 21 |
+
sinfo = s.get_src_stream_info(s.default_audio_stream)
|
| 22 |
+
if sinfo.num_frames == 0:
|
| 23 |
+
waveform = _load_audio(s)
|
| 24 |
+
num_frames = waveform.size(1)
|
| 25 |
+
else:
|
| 26 |
+
num_frames = sinfo.num_frames
|
| 27 |
+
return AudioMetaData(
|
| 28 |
+
int(sinfo.sample_rate),
|
| 29 |
+
num_frames,
|
| 30 |
+
sinfo.num_channels,
|
| 31 |
+
sinfo.bits_per_sample,
|
| 32 |
+
sinfo.codec.upper(),
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _get_load_filter(
|
| 37 |
+
frame_offset: int = 0,
|
| 38 |
+
num_frames: int = -1,
|
| 39 |
+
convert: bool = True,
|
| 40 |
+
) -> Optional[str]:
|
| 41 |
+
if frame_offset < 0:
|
| 42 |
+
raise RuntimeError("Invalid argument: frame_offset must be non-negative. Found: {}".format(frame_offset))
|
| 43 |
+
if num_frames == 0 or num_frames < -1:
|
| 44 |
+
raise RuntimeError("Invalid argument: num_frames must be -1 or greater than 0. Found: {}".format(num_frames))
|
| 45 |
+
|
| 46 |
+
# All default values -> no filter
|
| 47 |
+
if frame_offset == 0 and num_frames == -1 and not convert:
|
| 48 |
+
return None
|
| 49 |
+
# Only convert
|
| 50 |
+
aformat = "aformat=sample_fmts=fltp"
|
| 51 |
+
if frame_offset == 0 and num_frames == -1 and convert:
|
| 52 |
+
return aformat
|
| 53 |
+
# At least one of frame_offset or num_frames has non-default value
|
| 54 |
+
if num_frames > 0:
|
| 55 |
+
atrim = "atrim=start_sample={}:end_sample={}".format(frame_offset, frame_offset + num_frames)
|
| 56 |
+
else:
|
| 57 |
+
atrim = "atrim=start_sample={}".format(frame_offset)
|
| 58 |
+
if not convert:
|
| 59 |
+
return atrim
|
| 60 |
+
return "{},{}".format(atrim, aformat)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _load_audio(
|
| 64 |
+
s: "torchaudio.io.StreamReader",
|
| 65 |
+
filter: Optional[str] = None,
|
| 66 |
+
channels_first: bool = True,
|
| 67 |
+
) -> torch.Tensor:
|
| 68 |
+
s.add_audio_stream(-1, -1, filter_desc=filter)
|
| 69 |
+
s.process_all_packets()
|
| 70 |
+
chunk = s.pop_chunks()[0]
|
| 71 |
+
if chunk is None:
|
| 72 |
+
raise RuntimeError("Failed to decode audio.")
|
| 73 |
+
waveform = chunk._elem
|
| 74 |
+
return waveform.T if channels_first else waveform
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def load_audio(
|
| 78 |
+
src: InputType,
|
| 79 |
+
frame_offset: int = 0,
|
| 80 |
+
num_frames: int = -1,
|
| 81 |
+
convert: bool = True,
|
| 82 |
+
channels_first: bool = True,
|
| 83 |
+
format: Optional[str] = None,
|
| 84 |
+
buffer_size: int = 4096,
|
| 85 |
+
) -> Tuple[torch.Tensor, int]:
|
| 86 |
+
if hasattr(src, "read") and format == "vorbis":
|
| 87 |
+
format = "ogg"
|
| 88 |
+
s = torchaudio.io.StreamReader(src, format, None, buffer_size)
|
| 89 |
+
sample_rate = int(s.get_src_stream_info(s.default_audio_stream).sample_rate)
|
| 90 |
+
filter = _get_load_filter(frame_offset, num_frames, convert)
|
| 91 |
+
waveform = _load_audio(s, filter, channels_first)
|
| 92 |
+
return waveform, sample_rate
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _get_sample_format(dtype: torch.dtype) -> str:
|
| 96 |
+
dtype_to_format = {
|
| 97 |
+
torch.uint8: "u8",
|
| 98 |
+
torch.int16: "s16",
|
| 99 |
+
torch.int32: "s32",
|
| 100 |
+
torch.int64: "s64",
|
| 101 |
+
torch.float32: "flt",
|
| 102 |
+
torch.float64: "dbl",
|
| 103 |
+
}
|
| 104 |
+
format = dtype_to_format.get(dtype)
|
| 105 |
+
if format is None:
|
| 106 |
+
raise ValueError(f"No format found for dtype {dtype}; dtype must be one of {list(dtype_to_format.keys())}.")
|
| 107 |
+
return format
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _native_endianness() -> str:
|
| 111 |
+
if sys.byteorder == "little":
|
| 112 |
+
return "le"
|
| 113 |
+
else:
|
| 114 |
+
return "be"
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _get_encoder_for_wav(encoding: str, bits_per_sample: int) -> str:
|
| 118 |
+
if bits_per_sample not in {None, 8, 16, 24, 32, 64}:
|
| 119 |
+
raise ValueError(f"Invalid bits_per_sample {bits_per_sample} for WAV encoding.")
|
| 120 |
+
endianness = _native_endianness()
|
| 121 |
+
if not encoding:
|
| 122 |
+
if not bits_per_sample:
|
| 123 |
+
# default to PCM S16
|
| 124 |
+
return f"pcm_s16{endianness}"
|
| 125 |
+
if bits_per_sample == 8:
|
| 126 |
+
return "pcm_u8"
|
| 127 |
+
return f"pcm_s{bits_per_sample}{endianness}"
|
| 128 |
+
if encoding == "PCM_S":
|
| 129 |
+
if not bits_per_sample:
|
| 130 |
+
bits_per_sample = 16
|
| 131 |
+
if bits_per_sample == 8:
|
| 132 |
+
raise ValueError("For WAV signed PCM, 8-bit encoding is not supported.")
|
| 133 |
+
return f"pcm_s{bits_per_sample}{endianness}"
|
| 134 |
+
if encoding == "PCM_U":
|
| 135 |
+
if bits_per_sample in (None, 8):
|
| 136 |
+
return "pcm_u8"
|
| 137 |
+
raise ValueError("For WAV unsigned PCM, only 8-bit encoding is supported.")
|
| 138 |
+
if encoding == "PCM_F":
|
| 139 |
+
if not bits_per_sample:
|
| 140 |
+
bits_per_sample = 32
|
| 141 |
+
if bits_per_sample in (32, 64):
|
| 142 |
+
return f"pcm_f{bits_per_sample}{endianness}"
|
| 143 |
+
raise ValueError("For WAV float PCM, only 32- and 64-bit encodings are supported.")
|
| 144 |
+
if encoding == "ULAW":
|
| 145 |
+
if bits_per_sample in (None, 8):
|
| 146 |
+
return "pcm_mulaw"
|
| 147 |
+
raise ValueError("For WAV PCM mu-law, only 8-bit encoding is supported.")
|
| 148 |
+
if encoding == "ALAW":
|
| 149 |
+
if bits_per_sample in (None, 8):
|
| 150 |
+
return "pcm_alaw"
|
| 151 |
+
raise ValueError("For WAV PCM A-law, only 8-bit encoding is supported.")
|
| 152 |
+
raise ValueError(f"WAV encoding {encoding} is not supported.")
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _get_flac_sample_fmt(bps):
|
| 156 |
+
if bps is None or bps == 16:
|
| 157 |
+
return "s16"
|
| 158 |
+
if bps == 24:
|
| 159 |
+
return "s32"
|
| 160 |
+
raise ValueError(f"FLAC only supports bits_per_sample values of 16 and 24 ({bps} specified).")
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def _parse_save_args(
|
| 164 |
+
ext: Optional[str],
|
| 165 |
+
format: Optional[str],
|
| 166 |
+
encoding: Optional[str],
|
| 167 |
+
bps: Optional[int],
|
| 168 |
+
):
|
| 169 |
+
# torchaudio's save function accepts the followings, which do not 1to1 map
|
| 170 |
+
# to FFmpeg.
|
| 171 |
+
#
|
| 172 |
+
# - format: audio format
|
| 173 |
+
# - bits_per_sample: encoder sample format
|
| 174 |
+
# - encoding: such as PCM_U8.
|
| 175 |
+
#
|
| 176 |
+
# In FFmpeg, format is specified with the following three (and more)
|
| 177 |
+
#
|
| 178 |
+
# - muxer: could be audio format or container format.
|
| 179 |
+
# the one we passed to the constructor of StreamWriter
|
| 180 |
+
# - encoder: the audio encoder used to encode audio
|
| 181 |
+
# - encoder sample format: the format used by encoder to encode audio.
|
| 182 |
+
#
|
| 183 |
+
# If encoder sample format is different from source sample format, StreamWriter
|
| 184 |
+
# will insert a filter automatically.
|
| 185 |
+
#
|
| 186 |
+
def _type(spec):
|
| 187 |
+
# either format is exactly the specified one
|
| 188 |
+
# or extension matches to the spec AND there is no format override.
|
| 189 |
+
return format == spec or (format is None and ext == spec)
|
| 190 |
+
|
| 191 |
+
if _type("wav") or _type("amb"):
|
| 192 |
+
# wav is special because it supports different encoding through encoders
|
| 193 |
+
# each encoder only supports one encoder format
|
| 194 |
+
#
|
| 195 |
+
# amb format is a special case originated from libsox.
|
| 196 |
+
# It is basically a WAV format, with slight modification.
|
| 197 |
+
# https://github.com/chirlu/sox/commit/4a4ea33edbca5972a1ed8933cc3512c7302fa67a#diff-39171191a858add9df87f5f210a34a776ac2c026842ae6db6ce97f5e68836795
|
| 198 |
+
# It is a format so that decoders will recognize it as ambisonic.
|
| 199 |
+
# https://www.ambisonia.com/Members/mleese/file-format-for-b-format/
|
| 200 |
+
# FFmpeg does not recognize amb because it is basically a WAV format.
|
| 201 |
+
muxer = "wav"
|
| 202 |
+
encoder = _get_encoder_for_wav(encoding, bps)
|
| 203 |
+
sample_fmt = None
|
| 204 |
+
elif _type("vorbis"):
|
| 205 |
+
# FFpmeg does not recognize vorbis extension, while libsox used to do.
|
| 206 |
+
# For the sake of bakward compatibility, (and the simplicity),
|
| 207 |
+
# we support the case where users want to do save("foo.vorbis")
|
| 208 |
+
muxer = "ogg"
|
| 209 |
+
encoder = "vorbis"
|
| 210 |
+
sample_fmt = None
|
| 211 |
+
else:
|
| 212 |
+
muxer = format
|
| 213 |
+
encoder = None
|
| 214 |
+
sample_fmt = None
|
| 215 |
+
if _type("flac"):
|
| 216 |
+
sample_fmt = _get_flac_sample_fmt(bps)
|
| 217 |
+
if _type("ogg"):
|
| 218 |
+
sample_fmt = _get_flac_sample_fmt(bps)
|
| 219 |
+
return muxer, encoder, sample_fmt
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def save_audio(
|
| 223 |
+
uri: InputType,
|
| 224 |
+
src: torch.Tensor,
|
| 225 |
+
sample_rate: int,
|
| 226 |
+
channels_first: bool = True,
|
| 227 |
+
format: Optional[str] = None,
|
| 228 |
+
encoding: Optional[str] = None,
|
| 229 |
+
bits_per_sample: Optional[int] = None,
|
| 230 |
+
buffer_size: int = 4096,
|
| 231 |
+
compression: Optional[torchaudio.io.CodecConfig] = None,
|
| 232 |
+
) -> None:
|
| 233 |
+
ext = None
|
| 234 |
+
if hasattr(uri, "write"):
|
| 235 |
+
if format is None:
|
| 236 |
+
raise RuntimeError("'format' is required when saving to file object.")
|
| 237 |
+
else:
|
| 238 |
+
uri = os.path.normpath(uri)
|
| 239 |
+
if tokens := str(uri).split(".")[1:]:
|
| 240 |
+
ext = tokens[-1].lower()
|
| 241 |
+
|
| 242 |
+
muxer, encoder, enc_fmt = _parse_save_args(ext, format, encoding, bits_per_sample)
|
| 243 |
+
|
| 244 |
+
if channels_first:
|
| 245 |
+
src = src.T
|
| 246 |
+
|
| 247 |
+
s = torchaudio.io.StreamWriter(uri, format=muxer, buffer_size=buffer_size)
|
| 248 |
+
s.add_audio_stream(
|
| 249 |
+
sample_rate,
|
| 250 |
+
num_channels=src.size(-1),
|
| 251 |
+
format=_get_sample_format(src.dtype),
|
| 252 |
+
encoder=encoder,
|
| 253 |
+
encoder_format=enc_fmt,
|
| 254 |
+
codec_config=compression,
|
| 255 |
+
)
|
| 256 |
+
with s.open():
|
| 257 |
+
s.write_audio_chunk(0, src)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def _map_encoding(encoding: str) -> str:
|
| 261 |
+
for dst in ["PCM_S", "PCM_U", "PCM_F"]:
|
| 262 |
+
if dst in encoding:
|
| 263 |
+
return dst
|
| 264 |
+
if encoding == "PCM_MULAW":
|
| 265 |
+
return "ULAW"
|
| 266 |
+
elif encoding == "PCM_ALAW":
|
| 267 |
+
return "ALAW"
|
| 268 |
+
return encoding
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def _get_bits_per_sample(encoding: str, bits_per_sample: int) -> str:
|
| 272 |
+
if m := re.search(r"PCM_\w(\d+)\w*", encoding):
|
| 273 |
+
return int(m.group(1))
|
| 274 |
+
elif encoding in ["PCM_ALAW", "PCM_MULAW"]:
|
| 275 |
+
return 8
|
| 276 |
+
return bits_per_sample
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
class FFmpegBackend(Backend):
|
| 280 |
+
@staticmethod
|
| 281 |
+
def info(uri: InputType, format: Optional[str], buffer_size: int = 4096) -> AudioMetaData:
|
| 282 |
+
metadata = info_audio(uri, format, buffer_size)
|
| 283 |
+
metadata.bits_per_sample = _get_bits_per_sample(metadata.encoding, metadata.bits_per_sample)
|
| 284 |
+
metadata.encoding = _map_encoding(metadata.encoding)
|
| 285 |
+
return metadata
|
| 286 |
+
|
| 287 |
+
@staticmethod
|
| 288 |
+
def load(
|
| 289 |
+
uri: InputType,
|
| 290 |
+
frame_offset: int = 0,
|
| 291 |
+
num_frames: int = -1,
|
| 292 |
+
normalize: bool = True,
|
| 293 |
+
channels_first: bool = True,
|
| 294 |
+
format: Optional[str] = None,
|
| 295 |
+
buffer_size: int = 4096,
|
| 296 |
+
) -> Tuple[torch.Tensor, int]:
|
| 297 |
+
return load_audio(uri, frame_offset, num_frames, normalize, channels_first, format)
|
| 298 |
+
|
| 299 |
+
@staticmethod
|
| 300 |
+
def save(
|
| 301 |
+
uri: InputType,
|
| 302 |
+
src: torch.Tensor,
|
| 303 |
+
sample_rate: int,
|
| 304 |
+
channels_first: bool = True,
|
| 305 |
+
format: Optional[str] = None,
|
| 306 |
+
encoding: Optional[str] = None,
|
| 307 |
+
bits_per_sample: Optional[int] = None,
|
| 308 |
+
buffer_size: int = 4096,
|
| 309 |
+
compression: Optional[Union[torchaudio.io.CodecConfig, float, int]] = None,
|
| 310 |
+
) -> None:
|
| 311 |
+
if not isinstance(compression, (torchaudio.io.CodecConfig, type(None))):
|
| 312 |
+
raise ValueError(
|
| 313 |
+
"FFmpeg backend expects non-`None` value for argument `compression` to be of ",
|
| 314 |
+
f"type `torchaudio.io.CodecConfig`, but received value of type {type(compression)}",
|
| 315 |
+
)
|
| 316 |
+
save_audio(
|
| 317 |
+
uri,
|
| 318 |
+
src,
|
| 319 |
+
sample_rate,
|
| 320 |
+
channels_first,
|
| 321 |
+
format,
|
| 322 |
+
encoding,
|
| 323 |
+
bits_per_sample,
|
| 324 |
+
buffer_size,
|
| 325 |
+
compression,
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
@staticmethod
|
| 329 |
+
def can_decode(uri: InputType, format: Optional[str]) -> bool:
|
| 330 |
+
return True
|
| 331 |
+
|
| 332 |
+
@staticmethod
|
| 333 |
+
def can_encode(uri: InputType, format: Optional[str]) -> bool:
|
| 334 |
+
return True
|
lib/python3.12/site-packages/torchaudio/_backend/soundfile.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import BinaryIO, Optional, Tuple, Union
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from torchaudio.io import CodecConfig
|
| 6 |
+
|
| 7 |
+
from . import soundfile_backend
|
| 8 |
+
from .backend import Backend
|
| 9 |
+
from .common import AudioMetaData
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class SoundfileBackend(Backend):
|
| 13 |
+
@staticmethod
|
| 14 |
+
def info(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str], buffer_size: int = 4096) -> AudioMetaData:
|
| 15 |
+
return soundfile_backend.info(uri, format)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def load(
|
| 19 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 20 |
+
frame_offset: int = 0,
|
| 21 |
+
num_frames: int = -1,
|
| 22 |
+
normalize: bool = True,
|
| 23 |
+
channels_first: bool = True,
|
| 24 |
+
format: Optional[str] = None,
|
| 25 |
+
buffer_size: int = 4096,
|
| 26 |
+
) -> Tuple[torch.Tensor, int]:
|
| 27 |
+
return soundfile_backend.load(uri, frame_offset, num_frames, normalize, channels_first, format)
|
| 28 |
+
|
| 29 |
+
@staticmethod
|
| 30 |
+
def save(
|
| 31 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 32 |
+
src: torch.Tensor,
|
| 33 |
+
sample_rate: int,
|
| 34 |
+
channels_first: bool = True,
|
| 35 |
+
format: Optional[str] = None,
|
| 36 |
+
encoding: Optional[str] = None,
|
| 37 |
+
bits_per_sample: Optional[int] = None,
|
| 38 |
+
buffer_size: int = 4096,
|
| 39 |
+
compression: Optional[Union[CodecConfig, float, int]] = None,
|
| 40 |
+
) -> None:
|
| 41 |
+
if compression:
|
| 42 |
+
raise ValueError("soundfile backend does not support argument `compression`.")
|
| 43 |
+
|
| 44 |
+
soundfile_backend.save(
|
| 45 |
+
uri, src, sample_rate, channels_first, format=format, encoding=encoding, bits_per_sample=bits_per_sample
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
@staticmethod
|
| 49 |
+
def can_decode(uri, format) -> bool:
|
| 50 |
+
return True
|
| 51 |
+
|
| 52 |
+
@staticmethod
|
| 53 |
+
def can_encode(uri, format) -> bool:
|
| 54 |
+
return True
|
lib/python3.12/site-packages/torchaudio/_backend/soundfile_backend.py
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The new soundfile backend which will become default in 0.8.0 onward"""
|
| 2 |
+
import warnings
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torchaudio._internal import module_utils as _mod_utils
|
| 7 |
+
|
| 8 |
+
from .common import AudioMetaData
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
_IS_SOUNDFILE_AVAILABLE = False
|
| 12 |
+
|
| 13 |
+
# TODO: import soundfile only when it is used.
|
| 14 |
+
if _mod_utils.is_module_available("soundfile"):
|
| 15 |
+
try:
|
| 16 |
+
import soundfile
|
| 17 |
+
|
| 18 |
+
_requires_soundfile = _mod_utils.no_op
|
| 19 |
+
_IS_SOUNDFILE_AVAILABLE = True
|
| 20 |
+
except Exception:
|
| 21 |
+
_requires_soundfile = _mod_utils.fail_with_message(
|
| 22 |
+
"requires soundfile, but we failed to import it. Please check the installation of soundfile."
|
| 23 |
+
)
|
| 24 |
+
else:
|
| 25 |
+
_requires_soundfile = _mod_utils.fail_with_message(
|
| 26 |
+
"requires soundfile, but it is not installed. Please install soundfile."
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# Mapping from soundfile subtype to number of bits per sample.
|
| 31 |
+
# This is mostly heuristical and the value is set to 0 when it is irrelevant
|
| 32 |
+
# (lossy formats) or when it can't be inferred.
|
| 33 |
+
# For ADPCM (and G72X) subtypes, it's hard to infer the bit depth because it's not part of the standard:
|
| 34 |
+
# According to https://en.wikipedia.org/wiki/Adaptive_differential_pulse-code_modulation#In_telephony,
|
| 35 |
+
# the default seems to be 8 bits but it can be compressed further to 4 bits.
|
| 36 |
+
# The dict is inspired from
|
| 37 |
+
# https://github.com/bastibe/python-soundfile/blob/744efb4b01abc72498a96b09115b42a4cabd85e4/soundfile.py#L66-L94
|
| 38 |
+
_SUBTYPE_TO_BITS_PER_SAMPLE = {
|
| 39 |
+
"PCM_S8": 8, # Signed 8 bit data
|
| 40 |
+
"PCM_16": 16, # Signed 16 bit data
|
| 41 |
+
"PCM_24": 24, # Signed 24 bit data
|
| 42 |
+
"PCM_32": 32, # Signed 32 bit data
|
| 43 |
+
"PCM_U8": 8, # Unsigned 8 bit data (WAV and RAW only)
|
| 44 |
+
"FLOAT": 32, # 32 bit float data
|
| 45 |
+
"DOUBLE": 64, # 64 bit float data
|
| 46 |
+
"ULAW": 8, # U-Law encoded. See https://en.wikipedia.org/wiki/G.711#Types
|
| 47 |
+
"ALAW": 8, # A-Law encoded. See https://en.wikipedia.org/wiki/G.711#Types
|
| 48 |
+
"IMA_ADPCM": 0, # IMA ADPCM.
|
| 49 |
+
"MS_ADPCM": 0, # Microsoft ADPCM.
|
| 50 |
+
"GSM610": 0, # GSM 6.10 encoding. (Wikipedia says 1.625 bit depth?? https://en.wikipedia.org/wiki/Full_Rate)
|
| 51 |
+
"VOX_ADPCM": 0, # OKI / Dialogix ADPCM
|
| 52 |
+
"G721_32": 0, # 32kbs G721 ADPCM encoding.
|
| 53 |
+
"G723_24": 0, # 24kbs G723 ADPCM encoding.
|
| 54 |
+
"G723_40": 0, # 40kbs G723 ADPCM encoding.
|
| 55 |
+
"DWVW_12": 12, # 12 bit Delta Width Variable Word encoding.
|
| 56 |
+
"DWVW_16": 16, # 16 bit Delta Width Variable Word encoding.
|
| 57 |
+
"DWVW_24": 24, # 24 bit Delta Width Variable Word encoding.
|
| 58 |
+
"DWVW_N": 0, # N bit Delta Width Variable Word encoding.
|
| 59 |
+
"DPCM_8": 8, # 8 bit differential PCM (XI only)
|
| 60 |
+
"DPCM_16": 16, # 16 bit differential PCM (XI only)
|
| 61 |
+
"VORBIS": 0, # Xiph Vorbis encoding. (lossy)
|
| 62 |
+
"ALAC_16": 16, # Apple Lossless Audio Codec (16 bit).
|
| 63 |
+
"ALAC_20": 20, # Apple Lossless Audio Codec (20 bit).
|
| 64 |
+
"ALAC_24": 24, # Apple Lossless Audio Codec (24 bit).
|
| 65 |
+
"ALAC_32": 32, # Apple Lossless Audio Codec (32 bit).
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _get_bit_depth(subtype):
|
| 70 |
+
if subtype not in _SUBTYPE_TO_BITS_PER_SAMPLE:
|
| 71 |
+
warnings.warn(
|
| 72 |
+
f"The {subtype} subtype is unknown to TorchAudio. As a result, the bits_per_sample "
|
| 73 |
+
"attribute will be set to 0. If you are seeing this warning, please "
|
| 74 |
+
"report by opening an issue on github (after checking for existing/closed ones). "
|
| 75 |
+
"You may otherwise ignore this warning."
|
| 76 |
+
)
|
| 77 |
+
return _SUBTYPE_TO_BITS_PER_SAMPLE.get(subtype, 0)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
_SUBTYPE_TO_ENCODING = {
|
| 81 |
+
"PCM_S8": "PCM_S",
|
| 82 |
+
"PCM_16": "PCM_S",
|
| 83 |
+
"PCM_24": "PCM_S",
|
| 84 |
+
"PCM_32": "PCM_S",
|
| 85 |
+
"PCM_U8": "PCM_U",
|
| 86 |
+
"FLOAT": "PCM_F",
|
| 87 |
+
"DOUBLE": "PCM_F",
|
| 88 |
+
"ULAW": "ULAW",
|
| 89 |
+
"ALAW": "ALAW",
|
| 90 |
+
"VORBIS": "VORBIS",
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _get_encoding(format: str, subtype: str):
|
| 95 |
+
if format == "FLAC":
|
| 96 |
+
return "FLAC"
|
| 97 |
+
return _SUBTYPE_TO_ENCODING.get(subtype, "UNKNOWN")
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
@_requires_soundfile
|
| 101 |
+
def info(filepath: str, format: Optional[str] = None) -> AudioMetaData:
|
| 102 |
+
"""Get signal information of an audio file.
|
| 103 |
+
|
| 104 |
+
Note:
|
| 105 |
+
``filepath`` argument is intentionally annotated as ``str`` only, even though it accepts
|
| 106 |
+
``pathlib.Path`` object as well. This is for the consistency with ``"sox_io"`` backend,
|
| 107 |
+
which has a restriction on type annotation due to TorchScript compiler compatiblity.
|
| 108 |
+
|
| 109 |
+
Args:
|
| 110 |
+
filepath (path-like object or file-like object):
|
| 111 |
+
Source of audio data.
|
| 112 |
+
format (str or None, optional):
|
| 113 |
+
Not used. PySoundFile does not accept format hint.
|
| 114 |
+
|
| 115 |
+
Returns:
|
| 116 |
+
AudioMetaData: meta data of the given audio.
|
| 117 |
+
|
| 118 |
+
"""
|
| 119 |
+
sinfo = soundfile.info(filepath)
|
| 120 |
+
return AudioMetaData(
|
| 121 |
+
sinfo.samplerate,
|
| 122 |
+
sinfo.frames,
|
| 123 |
+
sinfo.channels,
|
| 124 |
+
bits_per_sample=_get_bit_depth(sinfo.subtype),
|
| 125 |
+
encoding=_get_encoding(sinfo.format, sinfo.subtype),
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
_SUBTYPE2DTYPE = {
|
| 130 |
+
"PCM_S8": "int8",
|
| 131 |
+
"PCM_U8": "uint8",
|
| 132 |
+
"PCM_16": "int16",
|
| 133 |
+
"PCM_32": "int32",
|
| 134 |
+
"FLOAT": "float32",
|
| 135 |
+
"DOUBLE": "float64",
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@_requires_soundfile
|
| 140 |
+
def load(
|
| 141 |
+
filepath: str,
|
| 142 |
+
frame_offset: int = 0,
|
| 143 |
+
num_frames: int = -1,
|
| 144 |
+
normalize: bool = True,
|
| 145 |
+
channels_first: bool = True,
|
| 146 |
+
format: Optional[str] = None,
|
| 147 |
+
) -> Tuple[torch.Tensor, int]:
|
| 148 |
+
"""Load audio data from file.
|
| 149 |
+
|
| 150 |
+
Note:
|
| 151 |
+
The formats this function can handle depend on the soundfile installation.
|
| 152 |
+
This function is tested on the following formats;
|
| 153 |
+
|
| 154 |
+
* WAV
|
| 155 |
+
|
| 156 |
+
* 32-bit floating-point
|
| 157 |
+
* 32-bit signed integer
|
| 158 |
+
* 16-bit signed integer
|
| 159 |
+
* 8-bit unsigned integer
|
| 160 |
+
|
| 161 |
+
* FLAC
|
| 162 |
+
* OGG/VORBIS
|
| 163 |
+
* SPHERE
|
| 164 |
+
|
| 165 |
+
By default (``normalize=True``, ``channels_first=True``), this function returns Tensor with
|
| 166 |
+
``float32`` dtype, and the shape of `[channel, time]`.
|
| 167 |
+
|
| 168 |
+
.. warning::
|
| 169 |
+
|
| 170 |
+
``normalize`` argument does not perform volume normalization.
|
| 171 |
+
It only converts the sample type to `torch.float32` from the native sample
|
| 172 |
+
type.
|
| 173 |
+
|
| 174 |
+
When the input format is WAV with integer type, such as 32-bit signed integer, 16-bit
|
| 175 |
+
signed integer, 24-bit signed integer, and 8-bit unsigned integer, by providing ``normalize=False``,
|
| 176 |
+
this function can return integer Tensor, where the samples are expressed within the whole range
|
| 177 |
+
of the corresponding dtype, that is, ``int32`` tensor for 32-bit signed PCM,
|
| 178 |
+
``int16`` for 16-bit signed PCM and ``uint8`` for 8-bit unsigned PCM. Since torch does not
|
| 179 |
+
support ``int24`` dtype, 24-bit signed PCM are converted to ``int32`` tensors.
|
| 180 |
+
|
| 181 |
+
``normalize`` argument has no effect on 32-bit floating-point WAV and other formats, such as
|
| 182 |
+
``flac`` and ``mp3``.
|
| 183 |
+
|
| 184 |
+
For these formats, this function always returns ``float32`` Tensor with values.
|
| 185 |
+
|
| 186 |
+
Note:
|
| 187 |
+
``filepath`` argument is intentionally annotated as ``str`` only, even though it accepts
|
| 188 |
+
``pathlib.Path`` object as well. This is for the consistency with ``"sox_io"`` backend,
|
| 189 |
+
which has a restriction on type annotation due to TorchScript compiler compatiblity.
|
| 190 |
+
|
| 191 |
+
Args:
|
| 192 |
+
filepath (path-like object or file-like object):
|
| 193 |
+
Source of audio data.
|
| 194 |
+
frame_offset (int, optional):
|
| 195 |
+
Number of frames to skip before start reading data.
|
| 196 |
+
num_frames (int, optional):
|
| 197 |
+
Maximum number of frames to read. ``-1`` reads all the remaining samples,
|
| 198 |
+
starting from ``frame_offset``.
|
| 199 |
+
This function may return the less number of frames if there is not enough
|
| 200 |
+
frames in the given file.
|
| 201 |
+
normalize (bool, optional):
|
| 202 |
+
When ``True``, this function converts the native sample type to ``float32``.
|
| 203 |
+
Default: ``True``.
|
| 204 |
+
|
| 205 |
+
If input file is integer WAV, giving ``False`` will change the resulting Tensor type to
|
| 206 |
+
integer type.
|
| 207 |
+
This argument has no effect for formats other than integer WAV type.
|
| 208 |
+
|
| 209 |
+
channels_first (bool, optional):
|
| 210 |
+
When True, the returned Tensor has dimension `[channel, time]`.
|
| 211 |
+
Otherwise, the returned Tensor's dimension is `[time, channel]`.
|
| 212 |
+
format (str or None, optional):
|
| 213 |
+
Not used. PySoundFile does not accept format hint.
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
(torch.Tensor, int): Resulting Tensor and sample rate.
|
| 217 |
+
If the input file has integer wav format and normalization is off, then it has
|
| 218 |
+
integer type, else ``float32`` type. If ``channels_first=True``, it has
|
| 219 |
+
`[channel, time]` else `[time, channel]`.
|
| 220 |
+
"""
|
| 221 |
+
with soundfile.SoundFile(filepath, "r") as file_:
|
| 222 |
+
if file_.format != "WAV" or normalize:
|
| 223 |
+
dtype = "float32"
|
| 224 |
+
elif file_.subtype not in _SUBTYPE2DTYPE:
|
| 225 |
+
raise ValueError(f"Unsupported subtype: {file_.subtype}")
|
| 226 |
+
else:
|
| 227 |
+
dtype = _SUBTYPE2DTYPE[file_.subtype]
|
| 228 |
+
|
| 229 |
+
frames = file_._prepare_read(frame_offset, None, num_frames)
|
| 230 |
+
waveform = file_.read(frames, dtype, always_2d=True)
|
| 231 |
+
sample_rate = file_.samplerate
|
| 232 |
+
|
| 233 |
+
waveform = torch.from_numpy(waveform)
|
| 234 |
+
if channels_first:
|
| 235 |
+
waveform = waveform.t()
|
| 236 |
+
return waveform, sample_rate
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def _get_subtype_for_wav(dtype: torch.dtype, encoding: str, bits_per_sample: int):
|
| 240 |
+
if not encoding:
|
| 241 |
+
if not bits_per_sample:
|
| 242 |
+
subtype = {
|
| 243 |
+
torch.uint8: "PCM_U8",
|
| 244 |
+
torch.int16: "PCM_16",
|
| 245 |
+
torch.int32: "PCM_32",
|
| 246 |
+
torch.float32: "FLOAT",
|
| 247 |
+
torch.float64: "DOUBLE",
|
| 248 |
+
}.get(dtype)
|
| 249 |
+
if not subtype:
|
| 250 |
+
raise ValueError(f"Unsupported dtype for wav: {dtype}")
|
| 251 |
+
return subtype
|
| 252 |
+
if bits_per_sample == 8:
|
| 253 |
+
return "PCM_U8"
|
| 254 |
+
return f"PCM_{bits_per_sample}"
|
| 255 |
+
if encoding == "PCM_S":
|
| 256 |
+
if not bits_per_sample:
|
| 257 |
+
return "PCM_32"
|
| 258 |
+
if bits_per_sample == 8:
|
| 259 |
+
raise ValueError("wav does not support 8-bit signed PCM encoding.")
|
| 260 |
+
return f"PCM_{bits_per_sample}"
|
| 261 |
+
if encoding == "PCM_U":
|
| 262 |
+
if bits_per_sample in (None, 8):
|
| 263 |
+
return "PCM_U8"
|
| 264 |
+
raise ValueError("wav only supports 8-bit unsigned PCM encoding.")
|
| 265 |
+
if encoding == "PCM_F":
|
| 266 |
+
if bits_per_sample in (None, 32):
|
| 267 |
+
return "FLOAT"
|
| 268 |
+
if bits_per_sample == 64:
|
| 269 |
+
return "DOUBLE"
|
| 270 |
+
raise ValueError("wav only supports 32/64-bit float PCM encoding.")
|
| 271 |
+
if encoding == "ULAW":
|
| 272 |
+
if bits_per_sample in (None, 8):
|
| 273 |
+
return "ULAW"
|
| 274 |
+
raise ValueError("wav only supports 8-bit mu-law encoding.")
|
| 275 |
+
if encoding == "ALAW":
|
| 276 |
+
if bits_per_sample in (None, 8):
|
| 277 |
+
return "ALAW"
|
| 278 |
+
raise ValueError("wav only supports 8-bit a-law encoding.")
|
| 279 |
+
raise ValueError(f"wav does not support {encoding}.")
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def _get_subtype_for_sphere(encoding: str, bits_per_sample: int):
|
| 283 |
+
if encoding in (None, "PCM_S"):
|
| 284 |
+
return f"PCM_{bits_per_sample}" if bits_per_sample else "PCM_32"
|
| 285 |
+
if encoding in ("PCM_U", "PCM_F"):
|
| 286 |
+
raise ValueError(f"sph does not support {encoding} encoding.")
|
| 287 |
+
if encoding == "ULAW":
|
| 288 |
+
if bits_per_sample in (None, 8):
|
| 289 |
+
return "ULAW"
|
| 290 |
+
raise ValueError("sph only supports 8-bit for mu-law encoding.")
|
| 291 |
+
if encoding == "ALAW":
|
| 292 |
+
return "ALAW"
|
| 293 |
+
raise ValueError(f"sph does not support {encoding}.")
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def _get_subtype(dtype: torch.dtype, format: str, encoding: str, bits_per_sample: int):
|
| 297 |
+
if format == "wav":
|
| 298 |
+
return _get_subtype_for_wav(dtype, encoding, bits_per_sample)
|
| 299 |
+
if format == "flac":
|
| 300 |
+
if encoding:
|
| 301 |
+
raise ValueError("flac does not support encoding.")
|
| 302 |
+
if not bits_per_sample:
|
| 303 |
+
return "PCM_16"
|
| 304 |
+
if bits_per_sample > 24:
|
| 305 |
+
raise ValueError("flac does not support bits_per_sample > 24.")
|
| 306 |
+
return "PCM_S8" if bits_per_sample == 8 else f"PCM_{bits_per_sample}"
|
| 307 |
+
if format in ("ogg", "vorbis"):
|
| 308 |
+
if bits_per_sample:
|
| 309 |
+
raise ValueError("ogg/vorbis does not support bits_per_sample.")
|
| 310 |
+
if encoding is None or encoding == "vorbis":
|
| 311 |
+
return "VORBIS"
|
| 312 |
+
if encoding == "opus":
|
| 313 |
+
return "OPUS"
|
| 314 |
+
raise ValueError(f"Unexpected encoding: {encoding}")
|
| 315 |
+
if format == "mp3":
|
| 316 |
+
return "MPEG_LAYER_III"
|
| 317 |
+
if format == "sph":
|
| 318 |
+
return _get_subtype_for_sphere(encoding, bits_per_sample)
|
| 319 |
+
if format in ("nis", "nist"):
|
| 320 |
+
return "PCM_16"
|
| 321 |
+
raise ValueError(f"Unsupported format: {format}")
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
@_requires_soundfile
|
| 325 |
+
def save(
|
| 326 |
+
filepath: str,
|
| 327 |
+
src: torch.Tensor,
|
| 328 |
+
sample_rate: int,
|
| 329 |
+
channels_first: bool = True,
|
| 330 |
+
compression: Optional[float] = None,
|
| 331 |
+
format: Optional[str] = None,
|
| 332 |
+
encoding: Optional[str] = None,
|
| 333 |
+
bits_per_sample: Optional[int] = None,
|
| 334 |
+
):
|
| 335 |
+
"""Save audio data to file.
|
| 336 |
+
|
| 337 |
+
Note:
|
| 338 |
+
The formats this function can handle depend on the soundfile installation.
|
| 339 |
+
This function is tested on the following formats;
|
| 340 |
+
|
| 341 |
+
* WAV
|
| 342 |
+
|
| 343 |
+
* 32-bit floating-point
|
| 344 |
+
* 32-bit signed integer
|
| 345 |
+
* 16-bit signed integer
|
| 346 |
+
* 8-bit unsigned integer
|
| 347 |
+
|
| 348 |
+
* FLAC
|
| 349 |
+
* OGG/VORBIS
|
| 350 |
+
* SPHERE
|
| 351 |
+
|
| 352 |
+
Note:
|
| 353 |
+
``filepath`` argument is intentionally annotated as ``str`` only, even though it accepts
|
| 354 |
+
``pathlib.Path`` object as well. This is for the consistency with ``"sox_io"`` backend,
|
| 355 |
+
which has a restriction on type annotation due to TorchScript compiler compatiblity.
|
| 356 |
+
|
| 357 |
+
Args:
|
| 358 |
+
filepath (str or pathlib.Path): Path to audio file.
|
| 359 |
+
src (torch.Tensor): Audio data to save. must be 2D tensor.
|
| 360 |
+
sample_rate (int): sampling rate
|
| 361 |
+
channels_first (bool, optional): If ``True``, the given tensor is interpreted as `[channel, time]`,
|
| 362 |
+
otherwise `[time, channel]`.
|
| 363 |
+
compression (float of None, optional): Not used.
|
| 364 |
+
It is here only for interface compatibility reson with "sox_io" backend.
|
| 365 |
+
format (str or None, optional): Override the audio format.
|
| 366 |
+
When ``filepath`` argument is path-like object, audio format is
|
| 367 |
+
inferred from file extension. If the file extension is missing or
|
| 368 |
+
different, you can specify the correct format with this argument.
|
| 369 |
+
|
| 370 |
+
When ``filepath`` argument is file-like object,
|
| 371 |
+
this argument is required.
|
| 372 |
+
|
| 373 |
+
Valid values are ``"wav"``, ``"ogg"``, ``"vorbis"``,
|
| 374 |
+
``"flac"`` and ``"sph"``.
|
| 375 |
+
encoding (str or None, optional): Changes the encoding for supported formats.
|
| 376 |
+
This argument is effective only for supported formats, sush as
|
| 377 |
+
``"wav"``, ``""flac"`` and ``"sph"``. Valid values are;
|
| 378 |
+
|
| 379 |
+
- ``"PCM_S"`` (signed integer Linear PCM)
|
| 380 |
+
- ``"PCM_U"`` (unsigned integer Linear PCM)
|
| 381 |
+
- ``"PCM_F"`` (floating point PCM)
|
| 382 |
+
- ``"ULAW"`` (mu-law)
|
| 383 |
+
- ``"ALAW"`` (a-law)
|
| 384 |
+
|
| 385 |
+
bits_per_sample (int or None, optional): Changes the bit depth for the
|
| 386 |
+
supported formats.
|
| 387 |
+
When ``format`` is one of ``"wav"``, ``"flac"`` or ``"sph"``,
|
| 388 |
+
you can change the bit depth.
|
| 389 |
+
Valid values are ``8``, ``16``, ``24``, ``32`` and ``64``.
|
| 390 |
+
|
| 391 |
+
Supported formats/encodings/bit depth/compression are:
|
| 392 |
+
|
| 393 |
+
``"wav"``
|
| 394 |
+
- 32-bit floating-point PCM
|
| 395 |
+
- 32-bit signed integer PCM
|
| 396 |
+
- 24-bit signed integer PCM
|
| 397 |
+
- 16-bit signed integer PCM
|
| 398 |
+
- 8-bit unsigned integer PCM
|
| 399 |
+
- 8-bit mu-law
|
| 400 |
+
- 8-bit a-law
|
| 401 |
+
|
| 402 |
+
Note:
|
| 403 |
+
Default encoding/bit depth is determined by the dtype of
|
| 404 |
+
the input Tensor.
|
| 405 |
+
|
| 406 |
+
``"flac"``
|
| 407 |
+
- 8-bit
|
| 408 |
+
- 16-bit (default)
|
| 409 |
+
- 24-bit
|
| 410 |
+
|
| 411 |
+
``"ogg"``, ``"vorbis"``
|
| 412 |
+
- Doesn't accept changing configuration.
|
| 413 |
+
|
| 414 |
+
``"sph"``
|
| 415 |
+
- 8-bit signed integer PCM
|
| 416 |
+
- 16-bit signed integer PCM
|
| 417 |
+
- 24-bit signed integer PCM
|
| 418 |
+
- 32-bit signed integer PCM (default)
|
| 419 |
+
- 8-bit mu-law
|
| 420 |
+
- 8-bit a-law
|
| 421 |
+
- 16-bit a-law
|
| 422 |
+
- 24-bit a-law
|
| 423 |
+
- 32-bit a-law
|
| 424 |
+
|
| 425 |
+
"""
|
| 426 |
+
if src.ndim != 2:
|
| 427 |
+
raise ValueError(f"Expected 2D Tensor, got {src.ndim}D.")
|
| 428 |
+
if compression is not None:
|
| 429 |
+
warnings.warn(
|
| 430 |
+
'`save` function of "soundfile" backend does not support "compression" parameter. '
|
| 431 |
+
"The argument is silently ignored."
|
| 432 |
+
)
|
| 433 |
+
if hasattr(filepath, "write"):
|
| 434 |
+
if format is None:
|
| 435 |
+
raise RuntimeError("`format` is required when saving to file object.")
|
| 436 |
+
ext = format.lower()
|
| 437 |
+
else:
|
| 438 |
+
ext = str(filepath).split(".")[-1].lower()
|
| 439 |
+
|
| 440 |
+
if bits_per_sample not in (None, 8, 16, 24, 32, 64):
|
| 441 |
+
raise ValueError("Invalid bits_per_sample.")
|
| 442 |
+
if bits_per_sample == 24:
|
| 443 |
+
warnings.warn(
|
| 444 |
+
"Saving audio with 24 bits per sample might warp samples near -1. "
|
| 445 |
+
"Using 16 bits per sample might be able to avoid this."
|
| 446 |
+
)
|
| 447 |
+
subtype = _get_subtype(src.dtype, ext, encoding, bits_per_sample)
|
| 448 |
+
|
| 449 |
+
# sph is a extension used in TED-LIUM but soundfile does not recognize it as NIST format,
|
| 450 |
+
# so we extend the extensions manually here
|
| 451 |
+
if ext in ["nis", "nist", "sph"] and format is None:
|
| 452 |
+
format = "NIST"
|
| 453 |
+
|
| 454 |
+
if channels_first:
|
| 455 |
+
src = src.t()
|
| 456 |
+
|
| 457 |
+
soundfile.write(file=filepath, data=src, samplerate=sample_rate, subtype=subtype, format=format)
|
lib/python3.12/site-packages/torchaudio/_backend/sox.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import BinaryIO, Optional, Tuple, Union
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torchaudio
|
| 6 |
+
|
| 7 |
+
from .backend import Backend
|
| 8 |
+
from .common import AudioMetaData
|
| 9 |
+
|
| 10 |
+
sox_ext = torchaudio._extension.lazy_import_sox_ext()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class SoXBackend(Backend):
|
| 14 |
+
@staticmethod
|
| 15 |
+
def info(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str], buffer_size: int = 4096) -> AudioMetaData:
|
| 16 |
+
if hasattr(uri, "read"):
|
| 17 |
+
raise ValueError(
|
| 18 |
+
"SoX backend does not support reading from file-like objects. ",
|
| 19 |
+
"Please use an alternative backend that does support reading from file-like objects, e.g. FFmpeg.",
|
| 20 |
+
)
|
| 21 |
+
else:
|
| 22 |
+
sinfo = sox_ext.get_info(uri, format)
|
| 23 |
+
if sinfo:
|
| 24 |
+
return AudioMetaData(*sinfo)
|
| 25 |
+
else:
|
| 26 |
+
raise RuntimeError(f"Failed to fetch metadata for {uri}.")
|
| 27 |
+
|
| 28 |
+
@staticmethod
|
| 29 |
+
def load(
|
| 30 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 31 |
+
frame_offset: int = 0,
|
| 32 |
+
num_frames: int = -1,
|
| 33 |
+
normalize: bool = True,
|
| 34 |
+
channels_first: bool = True,
|
| 35 |
+
format: Optional[str] = None,
|
| 36 |
+
buffer_size: int = 4096,
|
| 37 |
+
) -> Tuple[torch.Tensor, int]:
|
| 38 |
+
if hasattr(uri, "read"):
|
| 39 |
+
raise ValueError(
|
| 40 |
+
"SoX backend does not support loading from file-like objects. ",
|
| 41 |
+
"Please use an alternative backend that does support loading from file-like objects, e.g. FFmpeg.",
|
| 42 |
+
)
|
| 43 |
+
else:
|
| 44 |
+
ret = sox_ext.load_audio_file(uri, frame_offset, num_frames, normalize, channels_first, format)
|
| 45 |
+
if not ret:
|
| 46 |
+
raise RuntimeError(f"Failed to load audio from {uri}.")
|
| 47 |
+
return ret
|
| 48 |
+
|
| 49 |
+
@staticmethod
|
| 50 |
+
def save(
|
| 51 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 52 |
+
src: torch.Tensor,
|
| 53 |
+
sample_rate: int,
|
| 54 |
+
channels_first: bool = True,
|
| 55 |
+
format: Optional[str] = None,
|
| 56 |
+
encoding: Optional[str] = None,
|
| 57 |
+
bits_per_sample: Optional[int] = None,
|
| 58 |
+
buffer_size: int = 4096,
|
| 59 |
+
compression: Optional[Union[torchaudio.io.CodecConfig, float, int]] = None,
|
| 60 |
+
) -> None:
|
| 61 |
+
if not isinstance(compression, (float, int, type(None))):
|
| 62 |
+
raise ValueError(
|
| 63 |
+
"SoX backend expects non-`None` value for argument `compression` to be of ",
|
| 64 |
+
f"type `float` or `int`, but received value of type {type(compression)}",
|
| 65 |
+
)
|
| 66 |
+
if hasattr(uri, "write"):
|
| 67 |
+
raise ValueError(
|
| 68 |
+
"SoX backend does not support writing to file-like objects. ",
|
| 69 |
+
"Please use an alternative backend that does support writing to file-like objects, e.g. FFmpeg.",
|
| 70 |
+
)
|
| 71 |
+
else:
|
| 72 |
+
sox_ext.save_audio_file(
|
| 73 |
+
uri,
|
| 74 |
+
src,
|
| 75 |
+
sample_rate,
|
| 76 |
+
channels_first,
|
| 77 |
+
compression,
|
| 78 |
+
format,
|
| 79 |
+
encoding,
|
| 80 |
+
bits_per_sample,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
@staticmethod
|
| 84 |
+
def can_decode(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str]) -> bool:
|
| 85 |
+
# i.e. not a file-like object.
|
| 86 |
+
return not hasattr(uri, "read")
|
| 87 |
+
|
| 88 |
+
@staticmethod
|
| 89 |
+
def can_encode(uri: Union[BinaryIO, str, os.PathLike], format: Optional[str]) -> bool:
|
| 90 |
+
# i.e. not a file-like object.
|
| 91 |
+
return not hasattr(uri, "write")
|
lib/python3.12/site-packages/torchaudio/_backend/utils.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from functools import lru_cache
|
| 3 |
+
from typing import BinaryIO, Dict, Optional, Tuple, Type, Union
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from torchaudio._extension import lazy_import_sox_ext
|
| 8 |
+
from torchaudio.io import CodecConfig
|
| 9 |
+
from torio._extension import lazy_import_ffmpeg_ext
|
| 10 |
+
|
| 11 |
+
from . import soundfile_backend
|
| 12 |
+
|
| 13 |
+
from .backend import Backend
|
| 14 |
+
from .common import AudioMetaData
|
| 15 |
+
from .ffmpeg import FFmpegBackend
|
| 16 |
+
from .soundfile import SoundfileBackend
|
| 17 |
+
from .sox import SoXBackend
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@lru_cache(None)
|
| 21 |
+
def get_available_backends() -> Dict[str, Type[Backend]]:
|
| 22 |
+
backend_specs: Dict[str, Type[Backend]] = {}
|
| 23 |
+
if lazy_import_ffmpeg_ext().is_available():
|
| 24 |
+
backend_specs["ffmpeg"] = FFmpegBackend
|
| 25 |
+
if lazy_import_sox_ext().is_available():
|
| 26 |
+
backend_specs["sox"] = SoXBackend
|
| 27 |
+
if soundfile_backend._IS_SOUNDFILE_AVAILABLE:
|
| 28 |
+
backend_specs["soundfile"] = SoundfileBackend
|
| 29 |
+
return backend_specs
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def get_backend(backend_name, backends) -> Backend:
|
| 33 |
+
if backend := backends.get(backend_name):
|
| 34 |
+
return backend
|
| 35 |
+
else:
|
| 36 |
+
raise ValueError(
|
| 37 |
+
f"Unsupported backend '{backend_name}' specified; ",
|
| 38 |
+
f"please select one of {list(backends.keys())} instead.",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def get_info_func():
|
| 43 |
+
backends = get_available_backends()
|
| 44 |
+
|
| 45 |
+
def dispatcher(
|
| 46 |
+
uri: Union[BinaryIO, str, os.PathLike], format: Optional[str], backend_name: Optional[str]
|
| 47 |
+
) -> Backend:
|
| 48 |
+
if backend_name is not None:
|
| 49 |
+
return get_backend(backend_name, backends)
|
| 50 |
+
|
| 51 |
+
for backend in backends.values():
|
| 52 |
+
if backend.can_decode(uri, format):
|
| 53 |
+
return backend
|
| 54 |
+
raise RuntimeError(f"Couldn't find appropriate backend to handle uri {uri} and format {format}.")
|
| 55 |
+
|
| 56 |
+
def info(
|
| 57 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 58 |
+
format: Optional[str] = None,
|
| 59 |
+
buffer_size: int = 4096,
|
| 60 |
+
backend: Optional[str] = None,
|
| 61 |
+
) -> AudioMetaData:
|
| 62 |
+
"""Get signal information of an audio file.
|
| 63 |
+
|
| 64 |
+
Note:
|
| 65 |
+
When the input type is file-like object, this function cannot
|
| 66 |
+
get the correct length (``num_samples``) for certain formats,
|
| 67 |
+
such as ``vorbis``.
|
| 68 |
+
In this case, the value of ``num_samples`` is ``0``.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
uri (path-like object or file-like object):
|
| 72 |
+
Source of audio data. The following types are accepted:
|
| 73 |
+
|
| 74 |
+
* ``path-like``: File path or URL.
|
| 75 |
+
* ``file-like``: Object with ``read(size: int) -> bytes`` method,
|
| 76 |
+
which returns byte string of at most ``size`` length.
|
| 77 |
+
|
| 78 |
+
format (str or None, optional):
|
| 79 |
+
If not ``None``, interpreted as hint that may allow backend to override the detected format.
|
| 80 |
+
(Default: ``None``)
|
| 81 |
+
|
| 82 |
+
buffer_size (int, optional):
|
| 83 |
+
Size of buffer to use when processing file-like objects, in bytes. (Default: ``4096``)
|
| 84 |
+
|
| 85 |
+
backend (str or None, optional):
|
| 86 |
+
I/O backend to use.
|
| 87 |
+
If ``None``, function selects backend given input and available backends.
|
| 88 |
+
Otherwise, must be one of [``"ffmpeg"``, ``"sox"``, ``"soundfile"``],
|
| 89 |
+
with the corresponding backend available.
|
| 90 |
+
(Default: ``None``)
|
| 91 |
+
|
| 92 |
+
.. seealso::
|
| 93 |
+
:ref:`backend`
|
| 94 |
+
|
| 95 |
+
Returns:
|
| 96 |
+
AudioMetaData
|
| 97 |
+
"""
|
| 98 |
+
backend = dispatcher(uri, format, backend)
|
| 99 |
+
return backend.info(uri, format, buffer_size)
|
| 100 |
+
|
| 101 |
+
return info
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def get_load_func():
|
| 105 |
+
backends = get_available_backends()
|
| 106 |
+
|
| 107 |
+
def dispatcher(
|
| 108 |
+
uri: Union[BinaryIO, str, os.PathLike], format: Optional[str], backend_name: Optional[str]
|
| 109 |
+
) -> Backend:
|
| 110 |
+
if backend_name is not None:
|
| 111 |
+
return get_backend(backend_name, backends)
|
| 112 |
+
|
| 113 |
+
for backend in backends.values():
|
| 114 |
+
if backend.can_decode(uri, format):
|
| 115 |
+
return backend
|
| 116 |
+
raise RuntimeError(f"Couldn't find appropriate backend to handle uri {uri} and format {format}.")
|
| 117 |
+
|
| 118 |
+
def load(
|
| 119 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 120 |
+
frame_offset: int = 0,
|
| 121 |
+
num_frames: int = -1,
|
| 122 |
+
normalize: bool = True,
|
| 123 |
+
channels_first: bool = True,
|
| 124 |
+
format: Optional[str] = None,
|
| 125 |
+
buffer_size: int = 4096,
|
| 126 |
+
backend: Optional[str] = None,
|
| 127 |
+
) -> Tuple[torch.Tensor, int]:
|
| 128 |
+
"""Load audio data from source.
|
| 129 |
+
|
| 130 |
+
By default (``normalize=True``, ``channels_first=True``), this function returns Tensor with
|
| 131 |
+
``float32`` dtype, and the shape of `[channel, time]`.
|
| 132 |
+
|
| 133 |
+
Note:
|
| 134 |
+
The formats this function can handle depend on the availability of backends.
|
| 135 |
+
Please use the following functions to fetch the supported formats.
|
| 136 |
+
|
| 137 |
+
- FFmpeg: :py:func:`torchaudio.utils.ffmpeg_utils.get_audio_decoders`
|
| 138 |
+
- Sox: :py:func:`torchaudio.utils.sox_utils.list_read_formats`
|
| 139 |
+
- SoundFile: Refer to `the official document <https://pysoundfile.readthedocs.io/>`__.
|
| 140 |
+
|
| 141 |
+
.. warning::
|
| 142 |
+
|
| 143 |
+
``normalize`` argument does not perform volume normalization.
|
| 144 |
+
It only converts the sample type to `torch.float32` from the native sample
|
| 145 |
+
type.
|
| 146 |
+
|
| 147 |
+
When the input format is WAV with integer type, such as 32-bit signed integer, 16-bit
|
| 148 |
+
signed integer, 24-bit signed integer, and 8-bit unsigned integer, by providing ``normalize=False``,
|
| 149 |
+
this function can return integer Tensor, where the samples are expressed within the whole range
|
| 150 |
+
of the corresponding dtype, that is, ``int32`` tensor for 32-bit signed PCM,
|
| 151 |
+
``int16`` for 16-bit signed PCM and ``uint8`` for 8-bit unsigned PCM. Since torch does not
|
| 152 |
+
support ``int24`` dtype, 24-bit signed PCM are converted to ``int32`` tensors.
|
| 153 |
+
|
| 154 |
+
``normalize`` argument has no effect on 32-bit floating-point WAV and other formats, such as
|
| 155 |
+
``flac`` and ``mp3``.
|
| 156 |
+
|
| 157 |
+
For these formats, this function always returns ``float32`` Tensor with values.
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
Args:
|
| 161 |
+
uri (path-like object or file-like object):
|
| 162 |
+
Source of audio data.
|
| 163 |
+
frame_offset (int, optional):
|
| 164 |
+
Number of frames to skip before start reading data.
|
| 165 |
+
num_frames (int, optional):
|
| 166 |
+
Maximum number of frames to read. ``-1`` reads all the remaining samples,
|
| 167 |
+
starting from ``frame_offset``.
|
| 168 |
+
This function may return the less number of frames if there is not enough
|
| 169 |
+
frames in the given file.
|
| 170 |
+
normalize (bool, optional):
|
| 171 |
+
When ``True``, this function converts the native sample type to ``float32``.
|
| 172 |
+
Default: ``True``.
|
| 173 |
+
|
| 174 |
+
If input file is integer WAV, giving ``False`` will change the resulting Tensor type to
|
| 175 |
+
integer type.
|
| 176 |
+
This argument has no effect for formats other than integer WAV type.
|
| 177 |
+
|
| 178 |
+
channels_first (bool, optional):
|
| 179 |
+
When True, the returned Tensor has dimension `[channel, time]`.
|
| 180 |
+
Otherwise, the returned Tensor's dimension is `[time, channel]`.
|
| 181 |
+
|
| 182 |
+
format (str or None, optional):
|
| 183 |
+
If not ``None``, interpreted as hint that may allow backend to override the detected format.
|
| 184 |
+
(Default: ``None``)
|
| 185 |
+
|
| 186 |
+
buffer_size (int, optional):
|
| 187 |
+
Size of buffer to use when processing file-like objects, in bytes. (Default: ``4096``)
|
| 188 |
+
|
| 189 |
+
backend (str or None, optional):
|
| 190 |
+
I/O backend to use.
|
| 191 |
+
If ``None``, function selects backend given input and available backends.
|
| 192 |
+
Otherwise, must be one of [``"ffmpeg"``, ``"sox"``, ``"soundfile"``],
|
| 193 |
+
with the corresponding backend being available. (Default: ``None``)
|
| 194 |
+
|
| 195 |
+
.. seealso::
|
| 196 |
+
:ref:`backend`
|
| 197 |
+
|
| 198 |
+
Returns:
|
| 199 |
+
(torch.Tensor, int): Resulting Tensor and sample rate.
|
| 200 |
+
If the input file has integer wav format and normalization is off, then it has
|
| 201 |
+
integer type, else ``float32`` type. If ``channels_first=True``, it has
|
| 202 |
+
`[channel, time]` else `[time, channel]`.
|
| 203 |
+
"""
|
| 204 |
+
backend = dispatcher(uri, format, backend)
|
| 205 |
+
return backend.load(uri, frame_offset, num_frames, normalize, channels_first, format, buffer_size)
|
| 206 |
+
|
| 207 |
+
return load
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def get_save_func():
|
| 211 |
+
backends = get_available_backends()
|
| 212 |
+
|
| 213 |
+
def dispatcher(
|
| 214 |
+
uri: Union[BinaryIO, str, os.PathLike], format: Optional[str], backend_name: Optional[str]
|
| 215 |
+
) -> Backend:
|
| 216 |
+
if backend_name is not None:
|
| 217 |
+
return get_backend(backend_name, backends)
|
| 218 |
+
|
| 219 |
+
for backend in backends.values():
|
| 220 |
+
if backend.can_encode(uri, format):
|
| 221 |
+
return backend
|
| 222 |
+
raise RuntimeError(f"Couldn't find appropriate backend to handle uri {uri} and format {format}.")
|
| 223 |
+
|
| 224 |
+
def save(
|
| 225 |
+
uri: Union[BinaryIO, str, os.PathLike],
|
| 226 |
+
src: torch.Tensor,
|
| 227 |
+
sample_rate: int,
|
| 228 |
+
channels_first: bool = True,
|
| 229 |
+
format: Optional[str] = None,
|
| 230 |
+
encoding: Optional[str] = None,
|
| 231 |
+
bits_per_sample: Optional[int] = None,
|
| 232 |
+
buffer_size: int = 4096,
|
| 233 |
+
backend: Optional[str] = None,
|
| 234 |
+
compression: Optional[Union[CodecConfig, float, int]] = None,
|
| 235 |
+
):
|
| 236 |
+
"""Save audio data to file.
|
| 237 |
+
|
| 238 |
+
Note:
|
| 239 |
+
The formats this function can handle depend on the availability of backends.
|
| 240 |
+
Please use the following functions to fetch the supported formats.
|
| 241 |
+
|
| 242 |
+
- FFmpeg: :py:func:`torchaudio.utils.ffmpeg_utils.get_audio_encoders`
|
| 243 |
+
- Sox: :py:func:`torchaudio.utils.sox_utils.list_write_formats`
|
| 244 |
+
- SoundFile: Refer to `the official document <https://pysoundfile.readthedocs.io/>`__.
|
| 245 |
+
|
| 246 |
+
Args:
|
| 247 |
+
uri (str or pathlib.Path): Path to audio file.
|
| 248 |
+
src (torch.Tensor): Audio data to save. must be 2D tensor.
|
| 249 |
+
sample_rate (int): sampling rate
|
| 250 |
+
channels_first (bool, optional): If ``True``, the given tensor is interpreted as `[channel, time]`,
|
| 251 |
+
otherwise `[time, channel]`.
|
| 252 |
+
format (str or None, optional): Override the audio format.
|
| 253 |
+
When ``uri`` argument is path-like object, audio format is
|
| 254 |
+
inferred from file extension. If the file extension is missing or
|
| 255 |
+
different, you can specify the correct format with this argument.
|
| 256 |
+
|
| 257 |
+
When ``uri`` argument is file-like object,
|
| 258 |
+
this argument is required.
|
| 259 |
+
|
| 260 |
+
Valid values are ``"wav"``, ``"ogg"``, and ``"flac"``.
|
| 261 |
+
encoding (str or None, optional): Changes the encoding for supported formats.
|
| 262 |
+
This argument is effective only for supported formats, i.e.
|
| 263 |
+
``"wav"`` and ``""flac"```. Valid values are
|
| 264 |
+
|
| 265 |
+
- ``"PCM_S"`` (signed integer Linear PCM)
|
| 266 |
+
- ``"PCM_U"`` (unsigned integer Linear PCM)
|
| 267 |
+
- ``"PCM_F"`` (floating point PCM)
|
| 268 |
+
- ``"ULAW"`` (mu-law)
|
| 269 |
+
- ``"ALAW"`` (a-law)
|
| 270 |
+
|
| 271 |
+
bits_per_sample (int or None, optional): Changes the bit depth for the
|
| 272 |
+
supported formats.
|
| 273 |
+
When ``format`` is one of ``"wav"`` and ``"flac"``,
|
| 274 |
+
you can change the bit depth.
|
| 275 |
+
Valid values are ``8``, ``16``, ``24``, ``32`` and ``64``.
|
| 276 |
+
|
| 277 |
+
buffer_size (int, optional):
|
| 278 |
+
Size of buffer to use when processing file-like objects, in bytes. (Default: ``4096``)
|
| 279 |
+
|
| 280 |
+
backend (str or None, optional):
|
| 281 |
+
I/O backend to use.
|
| 282 |
+
If ``None``, function selects backend given input and available backends.
|
| 283 |
+
Otherwise, must be one of [``"ffmpeg"``, ``"sox"``, ``"soundfile"``],
|
| 284 |
+
with the corresponding backend being available.
|
| 285 |
+
(Default: ``None``)
|
| 286 |
+
|
| 287 |
+
.. seealso::
|
| 288 |
+
:ref:`backend`
|
| 289 |
+
|
| 290 |
+
compression (CodecConfig, float, int, or None, optional):
|
| 291 |
+
Compression configuration to apply.
|
| 292 |
+
|
| 293 |
+
If the selected backend is FFmpeg, an instance of :py:class:`CodecConfig` must be provided.
|
| 294 |
+
|
| 295 |
+
Otherwise, if the selected backend is SoX, a float or int value corresponding to option ``-C`` of the
|
| 296 |
+
``sox`` command line interface must be provided. For instance:
|
| 297 |
+
|
| 298 |
+
``"mp3"``
|
| 299 |
+
Either bitrate (in ``kbps``) with quality factor, such as ``128.2``, or
|
| 300 |
+
VBR encoding with quality factor such as ``-4.2``. Default: ``-4.5``.
|
| 301 |
+
|
| 302 |
+
``"flac"``
|
| 303 |
+
Whole number from ``0`` to ``8``. ``8`` is default and highest compression.
|
| 304 |
+
|
| 305 |
+
``"ogg"``, ``"vorbis"``
|
| 306 |
+
Number from ``-1`` to ``10``; ``-1`` is the highest compression
|
| 307 |
+
and lowest quality. Default: ``3``.
|
| 308 |
+
|
| 309 |
+
Refer to http://sox.sourceforge.net/soxformat.html for more details.
|
| 310 |
+
|
| 311 |
+
"""
|
| 312 |
+
backend = dispatcher(uri, format, backend)
|
| 313 |
+
return backend.save(
|
| 314 |
+
uri, src, sample_rate, channels_first, format, encoding, bits_per_sample, buffer_size, compression
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
return save
|
lib/python3.12/site-packages/torchaudio/_extension/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
from torchaudio._internal.module_utils import fail_with_message, is_module_available, no_op
|
| 6 |
+
|
| 7 |
+
from .utils import _check_cuda_version, _init_dll_path, _init_sox, _LazyImporter, _load_lib
|
| 8 |
+
|
| 9 |
+
_LG = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Note:
|
| 13 |
+
# `_check_cuda_version` is not meant to be used by regular users.
|
| 14 |
+
# Builder uses it for debugging purpose, so we export it.
|
| 15 |
+
# https://github.com/pytorch/builder/blob/e2e4542b8eb0bdf491214451a1a4128bd606cce2/test/smoke_test/smoke_test.py#L80
|
| 16 |
+
__all__ = [
|
| 17 |
+
"_check_cuda_version",
|
| 18 |
+
"_IS_TORCHAUDIO_EXT_AVAILABLE",
|
| 19 |
+
"_IS_RIR_AVAILABLE",
|
| 20 |
+
"lazy_import_sox_ext",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
if os.name == "nt" and (3, 8) <= sys.version_info < (3, 9):
|
| 25 |
+
_init_dll_path()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# When the extension module is built, we initialize it.
|
| 29 |
+
# In case of an error, we do not catch the failure as it suggests there is something
|
| 30 |
+
# wrong with the installation.
|
| 31 |
+
_IS_TORCHAUDIO_EXT_AVAILABLE = is_module_available("torchaudio.lib._torchaudio")
|
| 32 |
+
# RIR features are implemented in _torchaudio extension, but they can be individually
|
| 33 |
+
# turned on/off at build time. Available means that _torchaudio is loaded properly, and
|
| 34 |
+
# RIR features are found there.
|
| 35 |
+
_IS_RIR_AVAILABLE = False
|
| 36 |
+
_IS_ALIGN_AVAILABLE = False
|
| 37 |
+
if _IS_TORCHAUDIO_EXT_AVAILABLE:
|
| 38 |
+
_load_lib("libtorchaudio")
|
| 39 |
+
|
| 40 |
+
import torchaudio.lib._torchaudio # noqa
|
| 41 |
+
|
| 42 |
+
_check_cuda_version()
|
| 43 |
+
_IS_RIR_AVAILABLE = torchaudio.lib._torchaudio.is_rir_available()
|
| 44 |
+
_IS_ALIGN_AVAILABLE = torchaudio.lib._torchaudio.is_align_available()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
_SOX_EXT = None
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def lazy_import_sox_ext():
|
| 51 |
+
"""Load SoX integration based on availability in lazy manner"""
|
| 52 |
+
|
| 53 |
+
global _SOX_EXT
|
| 54 |
+
if _SOX_EXT is None:
|
| 55 |
+
_SOX_EXT = _LazyImporter("_torchaudio_sox", _init_sox)
|
| 56 |
+
return _SOX_EXT
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
fail_if_no_rir = (
|
| 60 |
+
no_op
|
| 61 |
+
if _IS_RIR_AVAILABLE
|
| 62 |
+
else fail_with_message(
|
| 63 |
+
"requires RIR extension, but TorchAudio is not compiled with it. Please build TorchAudio with RIR support."
|
| 64 |
+
)
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
fail_if_no_align = (
|
| 68 |
+
no_op
|
| 69 |
+
if _IS_ALIGN_AVAILABLE
|
| 70 |
+
else fail_with_message(
|
| 71 |
+
"Requires alignment extension, but TorchAudio is not compiled with it. \
|
| 72 |
+
Please build TorchAudio with alignment support."
|
| 73 |
+
)
|
| 74 |
+
)
|
lib/python3.12/site-packages/torchaudio/_extension/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (2.02 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_extension/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (8.35 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/_extension/utils.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Module to implement logics used for initializing extensions.
|
| 2 |
+
|
| 3 |
+
The implementations here should be stateless.
|
| 4 |
+
They should not depend on external state.
|
| 5 |
+
Anything that depends on external state should happen in __init__.py
|
| 6 |
+
"""
|
| 7 |
+
import importlib
|
| 8 |
+
import logging
|
| 9 |
+
import os
|
| 10 |
+
import types
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from torchaudio._internal.module_utils import eval_env
|
| 15 |
+
|
| 16 |
+
_LG = logging.getLogger(__name__)
|
| 17 |
+
_LIB_DIR = Path(__file__).parent.parent / "lib"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _get_lib_path(lib: str):
|
| 21 |
+
suffix = "pyd" if os.name == "nt" else "so"
|
| 22 |
+
path = _LIB_DIR / f"{lib}.{suffix}"
|
| 23 |
+
return path
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _load_lib(lib: str) -> bool:
|
| 27 |
+
"""Load extension module
|
| 28 |
+
|
| 29 |
+
Note:
|
| 30 |
+
In case `torchaudio` is deployed with `pex` format, the library file
|
| 31 |
+
is not in a standard location.
|
| 32 |
+
In this case, we expect that `libtorchaudio` is available somewhere
|
| 33 |
+
in the search path of dynamic loading mechanism, so that importing
|
| 34 |
+
`_torchaudio` will have library loader find and load `libtorchaudio`.
|
| 35 |
+
This is the reason why the function should not raising an error when the library
|
| 36 |
+
file is not found.
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
bool:
|
| 40 |
+
True if the library file is found AND the library loaded without failure.
|
| 41 |
+
False if the library file is not found (like in the case where torchaudio
|
| 42 |
+
is deployed with pex format, thus the shared library file is
|
| 43 |
+
in a non-standard location.).
|
| 44 |
+
If the library file is found but there is an issue loading the library,
|
| 45 |
+
(such as missing dependency) then this function raises the exception as-is.
|
| 46 |
+
|
| 47 |
+
Raises:
|
| 48 |
+
Exception:
|
| 49 |
+
If the library file is found, but there is an issue loading the library file,
|
| 50 |
+
(when underlying `ctype.DLL` throws an exception), this function will pass
|
| 51 |
+
the exception as-is, instead of catching it and returning bool.
|
| 52 |
+
The expected case is `OSError` thrown by `ctype.DLL` when a dynamic dependency
|
| 53 |
+
is not found.
|
| 54 |
+
This behavior was chosen because the expected failure case is not recoverable.
|
| 55 |
+
If a dependency is missing, then users have to install it.
|
| 56 |
+
"""
|
| 57 |
+
path = _get_lib_path(lib)
|
| 58 |
+
if not path.exists():
|
| 59 |
+
return False
|
| 60 |
+
torch.ops.load_library(path)
|
| 61 |
+
return True
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _import_sox_ext():
|
| 65 |
+
if os.name == "nt":
|
| 66 |
+
raise RuntimeError("sox extension is not supported on Windows")
|
| 67 |
+
if not eval_env("TORCHAUDIO_USE_SOX", True):
|
| 68 |
+
raise RuntimeError("sox extension is disabled. (TORCHAUDIO_USE_SOX=0)")
|
| 69 |
+
|
| 70 |
+
ext = "torchaudio.lib._torchaudio_sox"
|
| 71 |
+
|
| 72 |
+
if not importlib.util.find_spec(ext):
|
| 73 |
+
raise RuntimeError(
|
| 74 |
+
# fmt: off
|
| 75 |
+
"TorchAudio is not built with sox extension. "
|
| 76 |
+
"Please build TorchAudio with libsox support. (BUILD_SOX=1)"
|
| 77 |
+
# fmt: on
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
_load_lib("libtorchaudio_sox")
|
| 81 |
+
return importlib.import_module(ext)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _init_sox():
|
| 85 |
+
ext = _import_sox_ext()
|
| 86 |
+
ext.set_verbosity(0)
|
| 87 |
+
|
| 88 |
+
import atexit
|
| 89 |
+
|
| 90 |
+
torch.ops.torchaudio_sox.initialize_sox_effects()
|
| 91 |
+
atexit.register(torch.ops.torchaudio_sox.shutdown_sox_effects)
|
| 92 |
+
|
| 93 |
+
# Bundle functions registered with TORCH_LIBRARY into extension
|
| 94 |
+
# so that they can also be accessed in the same (lazy) manner
|
| 95 |
+
# from the extension.
|
| 96 |
+
keys = [
|
| 97 |
+
"get_info",
|
| 98 |
+
"load_audio_file",
|
| 99 |
+
"save_audio_file",
|
| 100 |
+
"apply_effects_tensor",
|
| 101 |
+
"apply_effects_file",
|
| 102 |
+
]
|
| 103 |
+
for key in keys:
|
| 104 |
+
setattr(ext, key, getattr(torch.ops.torchaudio_sox, key))
|
| 105 |
+
|
| 106 |
+
return ext
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class _LazyImporter(types.ModuleType):
|
| 110 |
+
"""Lazily import module/extension."""
|
| 111 |
+
|
| 112 |
+
def __init__(self, name, import_func):
|
| 113 |
+
super().__init__(name)
|
| 114 |
+
self.import_func = import_func
|
| 115 |
+
self.module = None
|
| 116 |
+
|
| 117 |
+
# Note:
|
| 118 |
+
# Python caches what was retrieved with `__getattr__`, so this method will not be
|
| 119 |
+
# called again for the same item.
|
| 120 |
+
def __getattr__(self, item):
|
| 121 |
+
self._import_once()
|
| 122 |
+
return getattr(self.module, item)
|
| 123 |
+
|
| 124 |
+
def __repr__(self):
|
| 125 |
+
if self.module is None:
|
| 126 |
+
return f"<module '{self.__module__}.{self.__class__.__name__}(\"{self.name}\")'>"
|
| 127 |
+
return repr(self.module)
|
| 128 |
+
|
| 129 |
+
def __dir__(self):
|
| 130 |
+
self._import_once()
|
| 131 |
+
return dir(self.module)
|
| 132 |
+
|
| 133 |
+
def _import_once(self):
|
| 134 |
+
if self.module is None:
|
| 135 |
+
self.module = self.import_func()
|
| 136 |
+
# Note:
|
| 137 |
+
# By attaching the module attributes to self,
|
| 138 |
+
# module attributes are directly accessible.
|
| 139 |
+
# This allows to avoid calling __getattr__ for every attribute access.
|
| 140 |
+
self.__dict__.update(self.module.__dict__)
|
| 141 |
+
|
| 142 |
+
def is_available(self):
|
| 143 |
+
try:
|
| 144 |
+
self._import_once()
|
| 145 |
+
except Exception:
|
| 146 |
+
return False
|
| 147 |
+
return True
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def _init_dll_path():
|
| 151 |
+
# On Windows Python-3.8+ has `os.add_dll_directory` call,
|
| 152 |
+
# which is called to configure dll search path.
|
| 153 |
+
# To find cuda related dlls we need to make sure the
|
| 154 |
+
# conda environment/bin path is configured Please take a look:
|
| 155 |
+
# https://stackoverflow.com/questions/59330863/cant-import-dll-module-in-python
|
| 156 |
+
# Please note: if some path can't be added using add_dll_directory we simply ignore this path
|
| 157 |
+
for path in os.environ.get("PATH", "").split(";"):
|
| 158 |
+
if os.path.exists(path):
|
| 159 |
+
try:
|
| 160 |
+
os.add_dll_directory(path)
|
| 161 |
+
except Exception:
|
| 162 |
+
pass
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def _check_cuda_version():
|
| 166 |
+
import torchaudio.lib._torchaudio
|
| 167 |
+
|
| 168 |
+
version = torchaudio.lib._torchaudio.cuda_version()
|
| 169 |
+
if version is not None and torch.version.cuda is not None:
|
| 170 |
+
version_str = str(version)
|
| 171 |
+
ta_version = f"{version_str[:-3]}.{version_str[-2]}"
|
| 172 |
+
t_version = torch.version.cuda.split(".")
|
| 173 |
+
t_version = f"{t_version[0]}.{t_version[1]}"
|
| 174 |
+
if ta_version != t_version:
|
| 175 |
+
raise RuntimeError(
|
| 176 |
+
"Detected that PyTorch and TorchAudio were compiled with different CUDA versions. "
|
| 177 |
+
f"PyTorch has CUDA version {t_version} whereas TorchAudio has CUDA version {ta_version}. "
|
| 178 |
+
"Please install the TorchAudio version that matches your PyTorch version."
|
| 179 |
+
)
|
| 180 |
+
return version
|
lib/python3.12/site-packages/torchaudio/backend/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NOTE:
|
| 2 |
+
# The entire `torchaudio.backend` module is deprecated.
|
| 3 |
+
# New things should be added to `torchaudio._backend`.
|
| 4 |
+
# Only things related to backward compatibility should be placed here.
|
| 5 |
+
|
| 6 |
+
from . import common, no_backend, soundfile_backend, sox_io_backend # noqa
|
| 7 |
+
|
| 8 |
+
__all__ = []
|
lib/python3.12/site-packages/torchaudio/backend/_no_backend.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
from typing import Callable, Optional, Tuple, Union
|
| 3 |
+
|
| 4 |
+
from torch import Tensor
|
| 5 |
+
from torchaudio import AudioMetaData
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def load(
|
| 9 |
+
filepath: Union[str, Path],
|
| 10 |
+
out: Optional[Tensor] = None,
|
| 11 |
+
normalization: Union[bool, float, Callable] = True,
|
| 12 |
+
channels_first: bool = True,
|
| 13 |
+
num_frames: int = 0,
|
| 14 |
+
offset: int = 0,
|
| 15 |
+
filetype: Optional[str] = None,
|
| 16 |
+
) -> Tuple[Tensor, int]:
|
| 17 |
+
raise RuntimeError("No audio I/O backend is available.")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def save(filepath: str, src: Tensor, sample_rate: int, precision: int = 16, channels_first: bool = True) -> None:
|
| 21 |
+
raise RuntimeError("No audio I/O backend is available.")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def info(filepath: str) -> AudioMetaData:
|
| 25 |
+
raise RuntimeError("No audio I/O backend is available.")
|
lib/python3.12/site-packages/torchaudio/backend/_sox_io_backend.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torchaudio
|
| 6 |
+
from torchaudio import AudioMetaData
|
| 7 |
+
|
| 8 |
+
sox_ext = torchaudio._extension.lazy_import_sox_ext()
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def info(
|
| 12 |
+
filepath: str,
|
| 13 |
+
format: Optional[str] = None,
|
| 14 |
+
) -> AudioMetaData:
|
| 15 |
+
"""Get signal information of an audio file.
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
filepath (str):
|
| 19 |
+
Source of audio data.
|
| 20 |
+
|
| 21 |
+
format (str or None, optional):
|
| 22 |
+
Override the format detection with the given format.
|
| 23 |
+
Providing the argument might help when libsox can not infer the format
|
| 24 |
+
from header or extension.
|
| 25 |
+
|
| 26 |
+
Returns:
|
| 27 |
+
AudioMetaData: Metadata of the given audio.
|
| 28 |
+
"""
|
| 29 |
+
if not torch.jit.is_scripting():
|
| 30 |
+
if hasattr(filepath, "read"):
|
| 31 |
+
raise RuntimeError("sox_io backend does not support file-like object.")
|
| 32 |
+
filepath = os.fspath(filepath)
|
| 33 |
+
sinfo = sox_ext.get_info(filepath, format)
|
| 34 |
+
return AudioMetaData(*sinfo)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def load(
|
| 38 |
+
filepath: str,
|
| 39 |
+
frame_offset: int = 0,
|
| 40 |
+
num_frames: int = -1,
|
| 41 |
+
normalize: bool = True,
|
| 42 |
+
channels_first: bool = True,
|
| 43 |
+
format: Optional[str] = None,
|
| 44 |
+
) -> Tuple[torch.Tensor, int]:
|
| 45 |
+
"""Load audio data from file.
|
| 46 |
+
|
| 47 |
+
Note:
|
| 48 |
+
This function can handle all the codecs that underlying libsox can handle,
|
| 49 |
+
however it is tested on the following formats;
|
| 50 |
+
|
| 51 |
+
* WAV, AMB
|
| 52 |
+
|
| 53 |
+
* 32-bit floating-point
|
| 54 |
+
* 32-bit signed integer
|
| 55 |
+
* 24-bit signed integer
|
| 56 |
+
* 16-bit signed integer
|
| 57 |
+
* 8-bit unsigned integer (WAV only)
|
| 58 |
+
|
| 59 |
+
* MP3
|
| 60 |
+
* FLAC
|
| 61 |
+
* OGG/VORBIS
|
| 62 |
+
* OPUS
|
| 63 |
+
* SPHERE
|
| 64 |
+
* AMR-NB
|
| 65 |
+
|
| 66 |
+
To load ``MP3``, ``FLAC``, ``OGG/VORBIS``, ``OPUS`` and other codecs ``libsox`` does not
|
| 67 |
+
handle natively, your installation of ``torchaudio`` has to be linked to ``libsox``
|
| 68 |
+
and corresponding codec libraries such as ``libmad`` or ``libmp3lame`` etc.
|
| 69 |
+
|
| 70 |
+
By default (``normalize=True``, ``channels_first=True``), this function returns Tensor with
|
| 71 |
+
``float32`` dtype, and the shape of `[channel, time]`.
|
| 72 |
+
|
| 73 |
+
.. warning::
|
| 74 |
+
|
| 75 |
+
``normalize`` argument does not perform volume normalization.
|
| 76 |
+
It only converts the sample type to `torch.float32` from the native sample
|
| 77 |
+
type.
|
| 78 |
+
|
| 79 |
+
When the input format is WAV with integer type, such as 32-bit signed integer, 16-bit
|
| 80 |
+
signed integer, 24-bit signed integer, and 8-bit unsigned integer, by providing ``normalize=False``,
|
| 81 |
+
this function can return integer Tensor, where the samples are expressed within the whole range
|
| 82 |
+
of the corresponding dtype, that is, ``int32`` tensor for 32-bit signed PCM,
|
| 83 |
+
``int16`` for 16-bit signed PCM and ``uint8`` for 8-bit unsigned PCM. Since torch does not
|
| 84 |
+
support ``int24`` dtype, 24-bit signed PCM are converted to ``int32`` tensors.
|
| 85 |
+
|
| 86 |
+
``normalize`` argument has no effect on 32-bit floating-point WAV and other formats, such as
|
| 87 |
+
``flac`` and ``mp3``.
|
| 88 |
+
|
| 89 |
+
For these formats, this function always returns ``float32`` Tensor with values.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
filepath (path-like object): Source of audio data.
|
| 93 |
+
frame_offset (int):
|
| 94 |
+
Number of frames to skip before start reading data.
|
| 95 |
+
num_frames (int, optional):
|
| 96 |
+
Maximum number of frames to read. ``-1`` reads all the remaining samples,
|
| 97 |
+
starting from ``frame_offset``.
|
| 98 |
+
This function may return the less number of frames if there is not enough
|
| 99 |
+
frames in the given file.
|
| 100 |
+
normalize (bool, optional):
|
| 101 |
+
When ``True``, this function converts the native sample type to ``float32``.
|
| 102 |
+
Default: ``True``.
|
| 103 |
+
|
| 104 |
+
If input file is integer WAV, giving ``False`` will change the resulting Tensor type to
|
| 105 |
+
integer type.
|
| 106 |
+
This argument has no effect for formats other than integer WAV type.
|
| 107 |
+
|
| 108 |
+
channels_first (bool, optional):
|
| 109 |
+
When True, the returned Tensor has dimension `[channel, time]`.
|
| 110 |
+
Otherwise, the returned Tensor's dimension is `[time, channel]`.
|
| 111 |
+
format (str or None, optional):
|
| 112 |
+
Override the format detection with the given format.
|
| 113 |
+
Providing the argument might help when libsox can not infer the format
|
| 114 |
+
from header or extension.
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
(torch.Tensor, int): Resulting Tensor and sample rate.
|
| 118 |
+
If the input file has integer wav format and ``normalize=False``, then it has
|
| 119 |
+
integer type, else ``float32`` type. If ``channels_first=True``, it has
|
| 120 |
+
`[channel, time]` else `[time, channel]`.
|
| 121 |
+
"""
|
| 122 |
+
if not torch.jit.is_scripting():
|
| 123 |
+
if hasattr(filepath, "read"):
|
| 124 |
+
raise RuntimeError("sox_io backend does not support file-like object.")
|
| 125 |
+
filepath = os.fspath(filepath)
|
| 126 |
+
return sox_ext.load_audio_file(filepath, frame_offset, num_frames, normalize, channels_first, format)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def save(
|
| 130 |
+
filepath: str,
|
| 131 |
+
src: torch.Tensor,
|
| 132 |
+
sample_rate: int,
|
| 133 |
+
channels_first: bool = True,
|
| 134 |
+
compression: Optional[float] = None,
|
| 135 |
+
format: Optional[str] = None,
|
| 136 |
+
encoding: Optional[str] = None,
|
| 137 |
+
bits_per_sample: Optional[int] = None,
|
| 138 |
+
):
|
| 139 |
+
"""Save audio data to file.
|
| 140 |
+
|
| 141 |
+
Args:
|
| 142 |
+
filepath (path-like object): Path to save file.
|
| 143 |
+
src (torch.Tensor): Audio data to save. must be 2D tensor.
|
| 144 |
+
sample_rate (int): sampling rate
|
| 145 |
+
channels_first (bool, optional): If ``True``, the given tensor is interpreted as `[channel, time]`,
|
| 146 |
+
otherwise `[time, channel]`.
|
| 147 |
+
compression (float or None, optional): Used for formats other than WAV.
|
| 148 |
+
This corresponds to ``-C`` option of ``sox`` command.
|
| 149 |
+
|
| 150 |
+
``"mp3"``
|
| 151 |
+
Either bitrate (in ``kbps``) with quality factor, such as ``128.2``, or
|
| 152 |
+
VBR encoding with quality factor such as ``-4.2``. Default: ``-4.5``.
|
| 153 |
+
|
| 154 |
+
``"flac"``
|
| 155 |
+
Whole number from ``0`` to ``8``. ``8`` is default and highest compression.
|
| 156 |
+
|
| 157 |
+
``"ogg"``, ``"vorbis"``
|
| 158 |
+
Number from ``-1`` to ``10``; ``-1`` is the highest compression
|
| 159 |
+
and lowest quality. Default: ``3``.
|
| 160 |
+
|
| 161 |
+
See the detail at http://sox.sourceforge.net/soxformat.html.
|
| 162 |
+
format (str or None, optional): Override the audio format.
|
| 163 |
+
When ``filepath`` argument is path-like object, audio format is infered from
|
| 164 |
+
file extension. If file extension is missing or different, you can specify the
|
| 165 |
+
correct format with this argument.
|
| 166 |
+
|
| 167 |
+
When ``filepath`` argument is file-like object, this argument is required.
|
| 168 |
+
|
| 169 |
+
Valid values are ``"wav"``, ``"mp3"``, ``"ogg"``, ``"vorbis"``, ``"amr-nb"``,
|
| 170 |
+
``"amb"``, ``"flac"``, ``"sph"``, ``"gsm"``, and ``"htk"``.
|
| 171 |
+
|
| 172 |
+
encoding (str or None, optional): Changes the encoding for the supported formats.
|
| 173 |
+
This argument is effective only for supported formats, such as ``"wav"``, ``""amb"``
|
| 174 |
+
and ``"sph"``. Valid values are;
|
| 175 |
+
|
| 176 |
+
- ``"PCM_S"`` (signed integer Linear PCM)
|
| 177 |
+
- ``"PCM_U"`` (unsigned integer Linear PCM)
|
| 178 |
+
- ``"PCM_F"`` (floating point PCM)
|
| 179 |
+
- ``"ULAW"`` (mu-law)
|
| 180 |
+
- ``"ALAW"`` (a-law)
|
| 181 |
+
|
| 182 |
+
Default values
|
| 183 |
+
If not provided, the default value is picked based on ``format`` and ``bits_per_sample``.
|
| 184 |
+
|
| 185 |
+
``"wav"``, ``"amb"``
|
| 186 |
+
- | If both ``encoding`` and ``bits_per_sample`` are not provided, the ``dtype`` of the
|
| 187 |
+
| Tensor is used to determine the default value.
|
| 188 |
+
|
| 189 |
+
- ``"PCM_U"`` if dtype is ``uint8``
|
| 190 |
+
- ``"PCM_S"`` if dtype is ``int16`` or ``int32``
|
| 191 |
+
- ``"PCM_F"`` if dtype is ``float32``
|
| 192 |
+
|
| 193 |
+
- ``"PCM_U"`` if ``bits_per_sample=8``
|
| 194 |
+
- ``"PCM_S"`` otherwise
|
| 195 |
+
|
| 196 |
+
``"sph"`` format;
|
| 197 |
+
- the default value is ``"PCM_S"``
|
| 198 |
+
|
| 199 |
+
bits_per_sample (int or None, optional): Changes the bit depth for the supported formats.
|
| 200 |
+
When ``format`` is one of ``"wav"``, ``"flac"``, ``"sph"``, or ``"amb"``, you can change the
|
| 201 |
+
bit depth. Valid values are ``8``, ``16``, ``32`` and ``64``.
|
| 202 |
+
|
| 203 |
+
Default Value;
|
| 204 |
+
If not provided, the default values are picked based on ``format`` and ``"encoding"``;
|
| 205 |
+
|
| 206 |
+
``"wav"``, ``"amb"``;
|
| 207 |
+
- | If both ``encoding`` and ``bits_per_sample`` are not provided, the ``dtype`` of the
|
| 208 |
+
| Tensor is used.
|
| 209 |
+
|
| 210 |
+
- ``8`` if dtype is ``uint8``
|
| 211 |
+
- ``16`` if dtype is ``int16``
|
| 212 |
+
- ``32`` if dtype is ``int32`` or ``float32``
|
| 213 |
+
|
| 214 |
+
- ``8`` if ``encoding`` is ``"PCM_U"``, ``"ULAW"`` or ``"ALAW"``
|
| 215 |
+
- ``16`` if ``encoding`` is ``"PCM_S"``
|
| 216 |
+
- ``32`` if ``encoding`` is ``"PCM_F"``
|
| 217 |
+
|
| 218 |
+
``"flac"`` format;
|
| 219 |
+
- the default value is ``24``
|
| 220 |
+
|
| 221 |
+
``"sph"`` format;
|
| 222 |
+
- ``16`` if ``encoding`` is ``"PCM_U"``, ``"PCM_S"``, ``"PCM_F"`` or not provided.
|
| 223 |
+
- ``8`` if ``encoding`` is ``"ULAW"`` or ``"ALAW"``
|
| 224 |
+
|
| 225 |
+
``"amb"`` format;
|
| 226 |
+
- ``8`` if ``encoding`` is ``"PCM_U"``, ``"ULAW"`` or ``"ALAW"``
|
| 227 |
+
- ``16`` if ``encoding`` is ``"PCM_S"`` or not provided.
|
| 228 |
+
- ``32`` if ``encoding`` is ``"PCM_F"``
|
| 229 |
+
|
| 230 |
+
Supported formats/encodings/bit depth/compression are;
|
| 231 |
+
|
| 232 |
+
``"wav"``, ``"amb"``
|
| 233 |
+
- 32-bit floating-point PCM
|
| 234 |
+
- 32-bit signed integer PCM
|
| 235 |
+
- 24-bit signed integer PCM
|
| 236 |
+
- 16-bit signed integer PCM
|
| 237 |
+
- 8-bit unsigned integer PCM
|
| 238 |
+
- 8-bit mu-law
|
| 239 |
+
- 8-bit a-law
|
| 240 |
+
|
| 241 |
+
Note: Default encoding/bit depth is determined by the dtype of the input Tensor.
|
| 242 |
+
|
| 243 |
+
``"mp3"``
|
| 244 |
+
Fixed bit rate (such as 128kHz) and variable bit rate compression.
|
| 245 |
+
Default: VBR with high quality.
|
| 246 |
+
|
| 247 |
+
``"flac"``
|
| 248 |
+
- 8-bit
|
| 249 |
+
- 16-bit
|
| 250 |
+
- 24-bit (default)
|
| 251 |
+
|
| 252 |
+
``"ogg"``, ``"vorbis"``
|
| 253 |
+
- Different quality level. Default: approx. 112kbps
|
| 254 |
+
|
| 255 |
+
``"sph"``
|
| 256 |
+
- 8-bit signed integer PCM
|
| 257 |
+
- 16-bit signed integer PCM
|
| 258 |
+
- 24-bit signed integer PCM
|
| 259 |
+
- 32-bit signed integer PCM (default)
|
| 260 |
+
- 8-bit mu-law
|
| 261 |
+
- 8-bit a-law
|
| 262 |
+
- 16-bit a-law
|
| 263 |
+
- 24-bit a-law
|
| 264 |
+
- 32-bit a-law
|
| 265 |
+
|
| 266 |
+
``"amr-nb"``
|
| 267 |
+
Bitrate ranging from 4.75 kbit/s to 12.2 kbit/s. Default: 4.75 kbit/s
|
| 268 |
+
|
| 269 |
+
``"gsm"``
|
| 270 |
+
Lossy Speech Compression, CPU intensive.
|
| 271 |
+
|
| 272 |
+
``"htk"``
|
| 273 |
+
Uses a default single-channel 16-bit PCM format.
|
| 274 |
+
|
| 275 |
+
Note:
|
| 276 |
+
To save into formats that ``libsox`` does not handle natively, (such as ``"mp3"``,
|
| 277 |
+
``"flac"``, ``"ogg"`` and ``"vorbis"``), your installation of ``torchaudio`` has
|
| 278 |
+
to be linked to ``libsox`` and corresponding codec libraries such as ``libmad``
|
| 279 |
+
or ``libmp3lame`` etc.
|
| 280 |
+
"""
|
| 281 |
+
if not torch.jit.is_scripting():
|
| 282 |
+
if hasattr(filepath, "write"):
|
| 283 |
+
raise RuntimeError("sox_io backend does not handle file-like object.")
|
| 284 |
+
filepath = os.fspath(filepath)
|
| 285 |
+
sox_ext.save_audio_file(
|
| 286 |
+
filepath,
|
| 287 |
+
src,
|
| 288 |
+
sample_rate,
|
| 289 |
+
channels_first,
|
| 290 |
+
compression,
|
| 291 |
+
format,
|
| 292 |
+
encoding,
|
| 293 |
+
bits_per_sample,
|
| 294 |
+
)
|
lib/python3.12/site-packages/torchaudio/backend/common.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def __getattr__(name: str):
|
| 2 |
+
if name == "AudioMetaData":
|
| 3 |
+
import warnings
|
| 4 |
+
|
| 5 |
+
warnings.warn(
|
| 6 |
+
"`torchaudio.backend.common.AudioMetaData` has been moved to "
|
| 7 |
+
"`torchaudio.AudioMetaData`. Please update the import path.",
|
| 8 |
+
stacklevel=2,
|
| 9 |
+
)
|
| 10 |
+
from torchaudio import AudioMetaData
|
| 11 |
+
|
| 12 |
+
return AudioMetaData
|
| 13 |
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
lib/python3.12/site-packages/torchaudio/backend/soundfile_backend.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def __getattr__(name: str):
|
| 2 |
+
import warnings
|
| 3 |
+
|
| 4 |
+
warnings.warn(
|
| 5 |
+
"Torchaudio's I/O functions now support par-call bakcend dispatch. "
|
| 6 |
+
"Importing backend implementation directly is no longer guaranteed to work. "
|
| 7 |
+
"Please use `backend` keyword with load/save/info function, instead of "
|
| 8 |
+
"calling the udnerlying implementation directly.",
|
| 9 |
+
stacklevel=2,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
from torchaudio._backend import soundfile_backend
|
| 13 |
+
|
| 14 |
+
return getattr(soundfile_backend, name)
|
lib/python3.12/site-packages/torchaudio/backend/sox_io_backend.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def __getattr__(name: str):
|
| 2 |
+
import warnings
|
| 3 |
+
|
| 4 |
+
warnings.warn(
|
| 5 |
+
"Torchaudio's I/O functions now support par-call bakcend dispatch. "
|
| 6 |
+
"Importing backend implementation directly is no longer guaranteed to work. "
|
| 7 |
+
"Please use `backend` keyword with load/save/info function, instead of "
|
| 8 |
+
"calling the udnerlying implementation directly.",
|
| 9 |
+
stacklevel=2,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
from . import _sox_io_backend
|
| 13 |
+
|
| 14 |
+
return getattr(_sox_io_backend, name)
|
lib/python3.12/site-packages/torchaudio/datasets/__pycache__/cmudict.cpython-312.pyc
ADDED
|
Binary file (6.83 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/datasets/__pycache__/gtzan.cpython-312.pyc
ADDED
|
Binary file (19.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/datasets/__pycache__/ljspeech.cpython-312.pyc
ADDED
|
Binary file (4.77 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/datasets/__pycache__/musdb_hq.cpython-312.pyc
ADDED
|
Binary file (7.11 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/functional/__init__.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ._alignment import forced_align, merge_tokens, TokenSpan
|
| 2 |
+
from .filtering import (
|
| 3 |
+
allpass_biquad,
|
| 4 |
+
band_biquad,
|
| 5 |
+
bandpass_biquad,
|
| 6 |
+
bandreject_biquad,
|
| 7 |
+
bass_biquad,
|
| 8 |
+
biquad,
|
| 9 |
+
contrast,
|
| 10 |
+
dcshift,
|
| 11 |
+
deemph_biquad,
|
| 12 |
+
dither,
|
| 13 |
+
equalizer_biquad,
|
| 14 |
+
filtfilt,
|
| 15 |
+
flanger,
|
| 16 |
+
gain,
|
| 17 |
+
highpass_biquad,
|
| 18 |
+
lfilter,
|
| 19 |
+
lowpass_biquad,
|
| 20 |
+
overdrive,
|
| 21 |
+
phaser,
|
| 22 |
+
riaa_biquad,
|
| 23 |
+
treble_biquad,
|
| 24 |
+
vad,
|
| 25 |
+
)
|
| 26 |
+
from .functional import (
|
| 27 |
+
add_noise,
|
| 28 |
+
amplitude_to_DB,
|
| 29 |
+
apply_beamforming,
|
| 30 |
+
apply_codec,
|
| 31 |
+
compute_deltas,
|
| 32 |
+
convolve,
|
| 33 |
+
create_dct,
|
| 34 |
+
DB_to_amplitude,
|
| 35 |
+
deemphasis,
|
| 36 |
+
detect_pitch_frequency,
|
| 37 |
+
edit_distance,
|
| 38 |
+
fftconvolve,
|
| 39 |
+
frechet_distance,
|
| 40 |
+
griffinlim,
|
| 41 |
+
inverse_spectrogram,
|
| 42 |
+
linear_fbanks,
|
| 43 |
+
loudness,
|
| 44 |
+
mask_along_axis,
|
| 45 |
+
mask_along_axis_iid,
|
| 46 |
+
melscale_fbanks,
|
| 47 |
+
mu_law_decoding,
|
| 48 |
+
mu_law_encoding,
|
| 49 |
+
mvdr_weights_rtf,
|
| 50 |
+
mvdr_weights_souden,
|
| 51 |
+
phase_vocoder,
|
| 52 |
+
pitch_shift,
|
| 53 |
+
preemphasis,
|
| 54 |
+
psd,
|
| 55 |
+
resample,
|
| 56 |
+
rnnt_loss,
|
| 57 |
+
rtf_evd,
|
| 58 |
+
rtf_power,
|
| 59 |
+
sliding_window_cmn,
|
| 60 |
+
spectral_centroid,
|
| 61 |
+
spectrogram,
|
| 62 |
+
speed,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
__all__ = [
|
| 66 |
+
"amplitude_to_DB",
|
| 67 |
+
"compute_deltas",
|
| 68 |
+
"create_dct",
|
| 69 |
+
"melscale_fbanks",
|
| 70 |
+
"linear_fbanks",
|
| 71 |
+
"DB_to_amplitude",
|
| 72 |
+
"loudness",
|
| 73 |
+
"detect_pitch_frequency",
|
| 74 |
+
"griffinlim",
|
| 75 |
+
"mask_along_axis",
|
| 76 |
+
"mask_along_axis_iid",
|
| 77 |
+
"mu_law_encoding",
|
| 78 |
+
"mu_law_decoding",
|
| 79 |
+
"phase_vocoder",
|
| 80 |
+
"sliding_window_cmn",
|
| 81 |
+
"spectrogram",
|
| 82 |
+
"inverse_spectrogram",
|
| 83 |
+
"spectral_centroid",
|
| 84 |
+
"allpass_biquad",
|
| 85 |
+
"band_biquad",
|
| 86 |
+
"bandpass_biquad",
|
| 87 |
+
"bandreject_biquad",
|
| 88 |
+
"bass_biquad",
|
| 89 |
+
"biquad",
|
| 90 |
+
"contrast",
|
| 91 |
+
"dither",
|
| 92 |
+
"dcshift",
|
| 93 |
+
"deemph_biquad",
|
| 94 |
+
"equalizer_biquad",
|
| 95 |
+
"filtfilt",
|
| 96 |
+
"flanger",
|
| 97 |
+
"forced_align",
|
| 98 |
+
"merge_tokens",
|
| 99 |
+
"TokenSpan",
|
| 100 |
+
"gain",
|
| 101 |
+
"highpass_biquad",
|
| 102 |
+
"lfilter",
|
| 103 |
+
"lowpass_biquad",
|
| 104 |
+
"overdrive",
|
| 105 |
+
"phaser",
|
| 106 |
+
"riaa_biquad",
|
| 107 |
+
"treble_biquad",
|
| 108 |
+
"vad",
|
| 109 |
+
"apply_codec",
|
| 110 |
+
"resample",
|
| 111 |
+
"edit_distance",
|
| 112 |
+
"pitch_shift",
|
| 113 |
+
"rnnt_loss",
|
| 114 |
+
"psd",
|
| 115 |
+
"mvdr_weights_souden",
|
| 116 |
+
"mvdr_weights_rtf",
|
| 117 |
+
"rtf_evd",
|
| 118 |
+
"rtf_power",
|
| 119 |
+
"apply_beamforming",
|
| 120 |
+
"fftconvolve",
|
| 121 |
+
"convolve",
|
| 122 |
+
"add_noise",
|
| 123 |
+
"speed",
|
| 124 |
+
"preemphasis",
|
| 125 |
+
"deemphasis",
|
| 126 |
+
"frechet_distance",
|
| 127 |
+
]
|
lib/python3.12/site-packages/torchaudio/functional/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (2.07 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/functional/__pycache__/_alignment.cpython-312.pyc
ADDED
|
Binary file (6.2 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/functional/__pycache__/filtering.cpython-312.pyc
ADDED
|
Binary file (70.8 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/functional/_alignment.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from typing import List, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from torch import Tensor
|
| 6 |
+
from torchaudio._extension import fail_if_no_align
|
| 7 |
+
|
| 8 |
+
__all__ = []
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@fail_if_no_align
|
| 12 |
+
def forced_align(
|
| 13 |
+
log_probs: Tensor,
|
| 14 |
+
targets: Tensor,
|
| 15 |
+
input_lengths: Optional[Tensor] = None,
|
| 16 |
+
target_lengths: Optional[Tensor] = None,
|
| 17 |
+
blank: int = 0,
|
| 18 |
+
) -> Tuple[Tensor, Tensor]:
|
| 19 |
+
r"""Align a CTC label sequence to an emission.
|
| 20 |
+
|
| 21 |
+
.. devices:: CPU CUDA
|
| 22 |
+
|
| 23 |
+
.. properties:: TorchScript
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
log_probs (Tensor): log probability of CTC emission output.
|
| 27 |
+
Tensor of shape `(B, T, C)`. where `B` is the batch size, `T` is the input length,
|
| 28 |
+
`C` is the number of characters in alphabet including blank.
|
| 29 |
+
targets (Tensor): Target sequence. Tensor of shape `(B, L)`,
|
| 30 |
+
where `L` is the target length.
|
| 31 |
+
input_lengths (Tensor or None, optional):
|
| 32 |
+
Lengths of the inputs (max value must each be <= `T`). 1-D Tensor of shape `(B,)`.
|
| 33 |
+
target_lengths (Tensor or None, optional):
|
| 34 |
+
Lengths of the targets. 1-D Tensor of shape `(B,)`.
|
| 35 |
+
blank_id (int, optional): The index of blank symbol in CTC emission. (Default: 0)
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
Tuple(Tensor, Tensor):
|
| 39 |
+
Tensor: Label for each time step in the alignment path computed using forced alignment.
|
| 40 |
+
|
| 41 |
+
Tensor: Log probability scores of the labels for each time step.
|
| 42 |
+
|
| 43 |
+
Note:
|
| 44 |
+
The sequence length of `log_probs` must satisfy:
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
.. math::
|
| 48 |
+
L_{\text{log\_probs}} \ge L_{\text{label}} + N_{\text{repeat}}
|
| 49 |
+
|
| 50 |
+
where :math:`N_{\text{repeat}}` is the number of consecutively repeated tokens.
|
| 51 |
+
For example, in str `"aabbc"`, the number of repeats are `2`.
|
| 52 |
+
|
| 53 |
+
Note:
|
| 54 |
+
The current version only supports ``batch_size==1``.
|
| 55 |
+
"""
|
| 56 |
+
if blank in targets:
|
| 57 |
+
raise ValueError(f"targets Tensor shouldn't contain blank index. Found {targets}.")
|
| 58 |
+
if torch.max(targets) >= log_probs.shape[-1]:
|
| 59 |
+
raise ValueError("targets values must be less than the CTC dimension")
|
| 60 |
+
|
| 61 |
+
if input_lengths is None:
|
| 62 |
+
batch_size, length = log_probs.size(0), log_probs.size(1)
|
| 63 |
+
input_lengths = torch.full((batch_size,), length, dtype=torch.int64, device=log_probs.device)
|
| 64 |
+
if target_lengths is None:
|
| 65 |
+
batch_size, length = targets.size(0), targets.size(1)
|
| 66 |
+
target_lengths = torch.full((batch_size,), length, dtype=torch.int64, device=targets.device)
|
| 67 |
+
|
| 68 |
+
# For TorchScript compatibility
|
| 69 |
+
assert input_lengths is not None
|
| 70 |
+
assert target_lengths is not None
|
| 71 |
+
|
| 72 |
+
paths, scores = torch.ops.torchaudio.forced_align(log_probs, targets, input_lengths, target_lengths, blank)
|
| 73 |
+
return paths, scores
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@dataclass
|
| 77 |
+
class TokenSpan:
|
| 78 |
+
"""TokenSpan()
|
| 79 |
+
Token with time stamps and score. Returned by :py:func:`merge_tokens`.
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
token: int
|
| 83 |
+
"""The token"""
|
| 84 |
+
start: int
|
| 85 |
+
"""The start time (inclusive) in emission time axis."""
|
| 86 |
+
end: int
|
| 87 |
+
"""The end time (exclusive) in emission time axis."""
|
| 88 |
+
score: float
|
| 89 |
+
"""The score of the this token."""
|
| 90 |
+
|
| 91 |
+
def __len__(self) -> int:
|
| 92 |
+
"""Returns the time span"""
|
| 93 |
+
return self.end - self.start
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def merge_tokens(tokens: Tensor, scores: Tensor, blank: int = 0) -> List[TokenSpan]:
|
| 97 |
+
"""Removes repeated tokens and blank tokens from the given CTC token sequence.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
tokens (Tensor): Alignment tokens (unbatched) returned from :py:func:`forced_align`.
|
| 101 |
+
Shape: `(time, )`.
|
| 102 |
+
scores (Tensor): Alignment scores (unbatched) returned from :py:func:`forced_align`.
|
| 103 |
+
Shape: `(time, )`. When computing the token-size score, the given score is averaged
|
| 104 |
+
across the corresponding time span.
|
| 105 |
+
|
| 106 |
+
Returns:
|
| 107 |
+
list of TokenSpan
|
| 108 |
+
|
| 109 |
+
Example:
|
| 110 |
+
>>> aligned_tokens, scores = forced_align(emission, targets, input_lengths, target_lengths)
|
| 111 |
+
>>> token_spans = merge_tokens(aligned_tokens[0], scores[0])
|
| 112 |
+
"""
|
| 113 |
+
if tokens.ndim != 1 or scores.ndim != 1:
|
| 114 |
+
raise ValueError("`tokens` and `scores` must be 1D Tensor.")
|
| 115 |
+
if len(tokens) != len(scores):
|
| 116 |
+
raise ValueError("`tokens` and `scores` must be the same length.")
|
| 117 |
+
|
| 118 |
+
diff = torch.diff(
|
| 119 |
+
tokens, prepend=torch.tensor([-1], device=tokens.device), append=torch.tensor([-1], device=tokens.device)
|
| 120 |
+
)
|
| 121 |
+
changes_wo_blank = torch.nonzero((diff != 0)).squeeze().tolist()
|
| 122 |
+
tokens = tokens.tolist()
|
| 123 |
+
spans = [
|
| 124 |
+
TokenSpan(token=token, start=start, end=end, score=scores[start:end].mean().item())
|
| 125 |
+
for start, end in zip(changes_wo_blank[:-1], changes_wo_blank[1:])
|
| 126 |
+
if (token := tokens[start]) != blank
|
| 127 |
+
]
|
| 128 |
+
return spans
|
lib/python3.12/site-packages/torchaudio/functional/filtering.py
ADDED
|
@@ -0,0 +1,1670 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import warnings
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch import Tensor
|
| 7 |
+
|
| 8 |
+
from torchaudio._extension import _IS_TORCHAUDIO_EXT_AVAILABLE
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def _dB2Linear(x: float) -> float:
|
| 12 |
+
return math.exp(x * math.log(10) / 20.0)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _generate_wave_table(
|
| 16 |
+
wave_type: str,
|
| 17 |
+
data_type: str,
|
| 18 |
+
table_size: int,
|
| 19 |
+
min: float,
|
| 20 |
+
max: float,
|
| 21 |
+
phase: float,
|
| 22 |
+
device: torch.device,
|
| 23 |
+
) -> Tensor:
|
| 24 |
+
r"""A helper function for phaser. Generates a table with given parameters.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
wave_type (str): SINE or TRIANGULAR
|
| 28 |
+
data_type (str): desired data_type ( `INT` or `FLOAT` )
|
| 29 |
+
table_size (int): desired table size
|
| 30 |
+
min (float): desired min value
|
| 31 |
+
max (float): desired max value
|
| 32 |
+
phase (float): desired phase
|
| 33 |
+
device (torch.device): Torch device on which table must be generated
|
| 34 |
+
Returns:
|
| 35 |
+
Tensor: A 1D tensor with wave table values
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
phase_offset = int(phase / math.pi / 2 * table_size + 0.5)
|
| 39 |
+
|
| 40 |
+
t = torch.arange(table_size, device=device, dtype=torch.int32)
|
| 41 |
+
|
| 42 |
+
point = (t + phase_offset) % table_size
|
| 43 |
+
|
| 44 |
+
d = torch.zeros_like(point, device=device, dtype=torch.float64)
|
| 45 |
+
|
| 46 |
+
if wave_type == "SINE":
|
| 47 |
+
d = (torch.sin(point.to(torch.float64) / table_size * 2 * math.pi) + 1) / 2
|
| 48 |
+
elif wave_type == "TRIANGLE":
|
| 49 |
+
d = point.to(torch.float64) * 2 / table_size
|
| 50 |
+
value = torch.div(4 * point, table_size, rounding_mode="floor")
|
| 51 |
+
d[value == 0] = d[value == 0] + 0.5
|
| 52 |
+
d[value == 1] = 1.5 - d[value == 1]
|
| 53 |
+
d[value == 2] = 1.5 - d[value == 2]
|
| 54 |
+
d[value == 3] = d[value == 3] - 1.5
|
| 55 |
+
|
| 56 |
+
d = d * (max - min) + min
|
| 57 |
+
|
| 58 |
+
if data_type == "INT":
|
| 59 |
+
mask = d < 0
|
| 60 |
+
d[mask] = d[mask] - 0.5
|
| 61 |
+
d[~mask] = d[~mask] + 0.5
|
| 62 |
+
d = d.to(torch.int32)
|
| 63 |
+
elif data_type == "FLOAT":
|
| 64 |
+
d = d.to(torch.float32)
|
| 65 |
+
|
| 66 |
+
return d
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def allpass_biquad(waveform: Tensor, sample_rate: int, central_freq: float, Q: float = 0.707) -> Tensor:
|
| 70 |
+
r"""Design two-pole all-pass filter. Similar to SoX implementation.
|
| 71 |
+
|
| 72 |
+
.. devices:: CPU CUDA
|
| 73 |
+
|
| 74 |
+
.. properties:: Autograd TorchScript
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
waveform(torch.Tensor): audio waveform of dimension of `(..., time)`
|
| 78 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 79 |
+
central_freq (float or torch.Tensor): central frequency (in Hz)
|
| 80 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``)
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 84 |
+
|
| 85 |
+
Reference:
|
| 86 |
+
- http://sox.sourceforge.net/sox.html
|
| 87 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 88 |
+
"""
|
| 89 |
+
dtype = waveform.dtype
|
| 90 |
+
device = waveform.device
|
| 91 |
+
central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device)
|
| 92 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 93 |
+
|
| 94 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 95 |
+
|
| 96 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 97 |
+
|
| 98 |
+
b0 = 1 - alpha
|
| 99 |
+
b1 = -2 * torch.cos(w0)
|
| 100 |
+
b2 = 1 + alpha
|
| 101 |
+
a0 = 1 + alpha
|
| 102 |
+
a1 = -2 * torch.cos(w0)
|
| 103 |
+
a2 = 1 - alpha
|
| 104 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def band_biquad(
|
| 108 |
+
waveform: Tensor,
|
| 109 |
+
sample_rate: int,
|
| 110 |
+
central_freq: float,
|
| 111 |
+
Q: float = 0.707,
|
| 112 |
+
noise: bool = False,
|
| 113 |
+
) -> Tensor:
|
| 114 |
+
r"""Design two-pole band filter. Similar to SoX implementation.
|
| 115 |
+
|
| 116 |
+
.. devices:: CPU CUDA
|
| 117 |
+
|
| 118 |
+
.. properties:: Autograd TorchScript
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 122 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 123 |
+
central_freq (float or torch.Tensor): central frequency (in Hz)
|
| 124 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``).
|
| 125 |
+
noise (bool, optional) : If ``True``, uses the alternate mode for un-pitched audio (e.g. percussion).
|
| 126 |
+
If ``False``, uses mode oriented to pitched audio, i.e. voice, singing,
|
| 127 |
+
or instrumental music (Default: ``False``).
|
| 128 |
+
|
| 129 |
+
Returns:
|
| 130 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 131 |
+
|
| 132 |
+
Reference:
|
| 133 |
+
- http://sox.sourceforge.net/sox.html
|
| 134 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 135 |
+
"""
|
| 136 |
+
dtype = waveform.dtype
|
| 137 |
+
device = waveform.device
|
| 138 |
+
central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device)
|
| 139 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 140 |
+
|
| 141 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 142 |
+
bw_Hz = central_freq / Q
|
| 143 |
+
|
| 144 |
+
a0 = 1.0
|
| 145 |
+
a2 = torch.exp(-2 * math.pi * bw_Hz / sample_rate)
|
| 146 |
+
a1 = -4 * a2 / (1 + a2) * torch.cos(w0)
|
| 147 |
+
|
| 148 |
+
b0 = torch.sqrt(1 - a1 * a1 / (4 * a2)) * (1 - a2)
|
| 149 |
+
|
| 150 |
+
if noise:
|
| 151 |
+
mult = torch.sqrt(((1 + a2) * (1 + a2) - a1 * a1) * (1 - a2) / (1 + a2)) / b0
|
| 152 |
+
b0 = mult * b0
|
| 153 |
+
|
| 154 |
+
b1 = 0.0
|
| 155 |
+
b2 = 0.0
|
| 156 |
+
|
| 157 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def bandpass_biquad(
|
| 161 |
+
waveform: Tensor,
|
| 162 |
+
sample_rate: int,
|
| 163 |
+
central_freq: float,
|
| 164 |
+
Q: float = 0.707,
|
| 165 |
+
const_skirt_gain: bool = False,
|
| 166 |
+
) -> Tensor:
|
| 167 |
+
r"""Design two-pole band-pass filter. Similar to SoX implementation.
|
| 168 |
+
|
| 169 |
+
.. devices:: CPU CUDA
|
| 170 |
+
|
| 171 |
+
.. properties:: Autograd TorchScript
|
| 172 |
+
|
| 173 |
+
Args:
|
| 174 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 175 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 176 |
+
central_freq (float or torch.Tensor): central frequency (in Hz)
|
| 177 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``)
|
| 178 |
+
const_skirt_gain (bool, optional) : If ``True``, uses a constant skirt gain (peak gain = Q).
|
| 179 |
+
If ``False``, uses a constant 0dB peak gain. (Default: ``False``)
|
| 180 |
+
|
| 181 |
+
Returns:
|
| 182 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 183 |
+
|
| 184 |
+
Reference:
|
| 185 |
+
- http://sox.sourceforge.net/sox.html
|
| 186 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 187 |
+
"""
|
| 188 |
+
dtype = waveform.dtype
|
| 189 |
+
device = waveform.device
|
| 190 |
+
central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device)
|
| 191 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 192 |
+
|
| 193 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 194 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 195 |
+
|
| 196 |
+
temp = torch.sin(w0) / 2 if const_skirt_gain else alpha
|
| 197 |
+
b0 = temp
|
| 198 |
+
b1 = 0.0
|
| 199 |
+
b2 = -temp
|
| 200 |
+
a0 = 1 + alpha
|
| 201 |
+
a1 = -2 * torch.cos(w0)
|
| 202 |
+
a2 = 1 - alpha
|
| 203 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def bandreject_biquad(waveform: Tensor, sample_rate: int, central_freq: float, Q: float = 0.707) -> Tensor:
|
| 207 |
+
r"""Design two-pole band-reject filter. Similar to SoX implementation.
|
| 208 |
+
|
| 209 |
+
.. devices:: CPU CUDA
|
| 210 |
+
|
| 211 |
+
.. properties:: Autograd TorchScript
|
| 212 |
+
|
| 213 |
+
Args:
|
| 214 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 215 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 216 |
+
central_freq (float or torch.Tensor): central frequency (in Hz)
|
| 217 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``)
|
| 218 |
+
|
| 219 |
+
Returns:
|
| 220 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 221 |
+
|
| 222 |
+
Reference:
|
| 223 |
+
- http://sox.sourceforge.net/sox.html
|
| 224 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 225 |
+
"""
|
| 226 |
+
dtype = waveform.dtype
|
| 227 |
+
device = waveform.device
|
| 228 |
+
central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device)
|
| 229 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 230 |
+
|
| 231 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 232 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 233 |
+
|
| 234 |
+
b0 = 1.0
|
| 235 |
+
b1 = -2 * torch.cos(w0)
|
| 236 |
+
b2 = 1.0
|
| 237 |
+
a0 = 1 + alpha
|
| 238 |
+
a1 = -2 * torch.cos(w0)
|
| 239 |
+
a2 = 1 - alpha
|
| 240 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def bass_biquad(
|
| 244 |
+
waveform: Tensor,
|
| 245 |
+
sample_rate: int,
|
| 246 |
+
gain: float,
|
| 247 |
+
central_freq: float = 100,
|
| 248 |
+
Q: float = 0.707,
|
| 249 |
+
) -> Tensor:
|
| 250 |
+
r"""Design a bass tone-control effect. Similar to SoX implementation.
|
| 251 |
+
|
| 252 |
+
.. devices:: CPU CUDA
|
| 253 |
+
|
| 254 |
+
.. properties:: Autograd TorchScript
|
| 255 |
+
|
| 256 |
+
Args:
|
| 257 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 258 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 259 |
+
gain (float or torch.Tensor): desired gain at the boost (or attenuation) in dB.
|
| 260 |
+
central_freq (float or torch.Tensor, optional): central frequency (in Hz). (Default: ``100``)
|
| 261 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``).
|
| 262 |
+
|
| 263 |
+
Returns:
|
| 264 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 265 |
+
|
| 266 |
+
Reference:
|
| 267 |
+
- http://sox.sourceforge.net/sox.html
|
| 268 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 269 |
+
"""
|
| 270 |
+
dtype = waveform.dtype
|
| 271 |
+
device = waveform.device
|
| 272 |
+
central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device)
|
| 273 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 274 |
+
gain = torch.as_tensor(gain, dtype=dtype, device=device)
|
| 275 |
+
|
| 276 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 277 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 278 |
+
A = torch.exp(gain / 40 * math.log(10))
|
| 279 |
+
|
| 280 |
+
temp1 = 2 * torch.sqrt(A) * alpha
|
| 281 |
+
temp2 = (A - 1) * torch.cos(w0)
|
| 282 |
+
temp3 = (A + 1) * torch.cos(w0)
|
| 283 |
+
|
| 284 |
+
b0 = A * ((A + 1) - temp2 + temp1)
|
| 285 |
+
b1 = 2 * A * ((A - 1) - temp3)
|
| 286 |
+
b2 = A * ((A + 1) - temp2 - temp1)
|
| 287 |
+
a0 = (A + 1) + temp2 + temp1
|
| 288 |
+
a1 = -2 * ((A - 1) + temp3)
|
| 289 |
+
a2 = (A + 1) + temp2 - temp1
|
| 290 |
+
|
| 291 |
+
return biquad(waveform, b0 / a0, b1 / a0, b2 / a0, a0 / a0, a1 / a0, a2 / a0)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def biquad(waveform: Tensor, b0: float, b1: float, b2: float, a0: float, a1: float, a2: float) -> Tensor:
|
| 295 |
+
r"""Perform a biquad filter of input tensor. Initial conditions set to 0.
|
| 296 |
+
|
| 297 |
+
.. devices:: CPU CUDA
|
| 298 |
+
|
| 299 |
+
.. properties:: Autograd TorchScript
|
| 300 |
+
|
| 301 |
+
Args:
|
| 302 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 303 |
+
b0 (float or torch.Tensor): numerator coefficient of current input, x[n]
|
| 304 |
+
b1 (float or torch.Tensor): numerator coefficient of input one time step ago x[n-1]
|
| 305 |
+
b2 (float or torch.Tensor): numerator coefficient of input two time steps ago x[n-2]
|
| 306 |
+
a0 (float or torch.Tensor): denominator coefficient of current output y[n], typically 1
|
| 307 |
+
a1 (float or torch.Tensor): denominator coefficient of current output y[n-1]
|
| 308 |
+
a2 (float or torch.Tensor): denominator coefficient of current output y[n-2]
|
| 309 |
+
|
| 310 |
+
Returns:
|
| 311 |
+
Tensor: Waveform with dimension of `(..., time)`
|
| 312 |
+
|
| 313 |
+
Reference:
|
| 314 |
+
- https://en.wikipedia.org/wiki/Digital_biquad_filter
|
| 315 |
+
"""
|
| 316 |
+
|
| 317 |
+
device = waveform.device
|
| 318 |
+
dtype = waveform.dtype
|
| 319 |
+
|
| 320 |
+
b0 = torch.as_tensor(b0, dtype=dtype, device=device).view(1)
|
| 321 |
+
b1 = torch.as_tensor(b1, dtype=dtype, device=device).view(1)
|
| 322 |
+
b2 = torch.as_tensor(b2, dtype=dtype, device=device).view(1)
|
| 323 |
+
a0 = torch.as_tensor(a0, dtype=dtype, device=device).view(1)
|
| 324 |
+
a1 = torch.as_tensor(a1, dtype=dtype, device=device).view(1)
|
| 325 |
+
a2 = torch.as_tensor(a2, dtype=dtype, device=device).view(1)
|
| 326 |
+
|
| 327 |
+
output_waveform = lfilter(
|
| 328 |
+
waveform,
|
| 329 |
+
torch.cat([a0, a1, a2]),
|
| 330 |
+
torch.cat([b0, b1, b2]),
|
| 331 |
+
)
|
| 332 |
+
return output_waveform
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def contrast(waveform: Tensor, enhancement_amount: float = 75.0) -> Tensor:
|
| 336 |
+
r"""Apply contrast effect. Similar to SoX implementation.
|
| 337 |
+
|
| 338 |
+
.. devices:: CPU CUDA
|
| 339 |
+
|
| 340 |
+
.. properties:: Autograd TorchScript
|
| 341 |
+
|
| 342 |
+
Comparable with compression, this effect modifies an audio signal to make it sound louder
|
| 343 |
+
|
| 344 |
+
Args:
|
| 345 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 346 |
+
enhancement_amount (float, optional): controls the amount of the enhancement
|
| 347 |
+
Allowed range of values for enhancement_amount : 0-100
|
| 348 |
+
Note that enhancement_amount = 0 still gives a significant contrast enhancement
|
| 349 |
+
|
| 350 |
+
Returns:
|
| 351 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 352 |
+
|
| 353 |
+
Reference:
|
| 354 |
+
- http://sox.sourceforge.net/sox.html
|
| 355 |
+
"""
|
| 356 |
+
|
| 357 |
+
if not 0 <= enhancement_amount <= 100:
|
| 358 |
+
raise ValueError("Allowed range of values for enhancement_amount : 0-100")
|
| 359 |
+
|
| 360 |
+
contrast = enhancement_amount / 750.0
|
| 361 |
+
|
| 362 |
+
temp1 = waveform * (math.pi / 2)
|
| 363 |
+
temp2 = contrast * torch.sin(temp1 * 4)
|
| 364 |
+
output_waveform = torch.sin(temp1 + temp2)
|
| 365 |
+
|
| 366 |
+
return output_waveform
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def dcshift(waveform: Tensor, shift: float, limiter_gain: Optional[float] = None) -> Tensor:
|
| 370 |
+
r"""Apply a DC shift to the audio. Similar to SoX implementation.
|
| 371 |
+
|
| 372 |
+
.. devices:: CPU CUDA
|
| 373 |
+
|
| 374 |
+
.. properties:: TorchScript
|
| 375 |
+
|
| 376 |
+
This can be useful to remove a DC offset
|
| 377 |
+
(caused perhaps by a hardware problem in the recording chain) from the audio
|
| 378 |
+
|
| 379 |
+
Args:
|
| 380 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 381 |
+
shift (float): indicates the amount to shift the audio
|
| 382 |
+
Allowed range of values for shift : -2.0 to +2.0
|
| 383 |
+
limiter_gain (float of None, optional): It is used only on peaks to prevent clipping
|
| 384 |
+
It should have a value much less than 1 (e.g. 0.05 or 0.02)
|
| 385 |
+
|
| 386 |
+
Returns:
|
| 387 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 388 |
+
|
| 389 |
+
Reference:
|
| 390 |
+
- http://sox.sourceforge.net/sox.html
|
| 391 |
+
"""
|
| 392 |
+
output_waveform = waveform
|
| 393 |
+
limiter_threshold = 0.0
|
| 394 |
+
|
| 395 |
+
if limiter_gain is not None:
|
| 396 |
+
limiter_threshold = 1.0 - (abs(shift) - limiter_gain)
|
| 397 |
+
|
| 398 |
+
# Note:
|
| 399 |
+
# the following index-based update breaks auto-grad support
|
| 400 |
+
if limiter_gain is not None and shift > 0:
|
| 401 |
+
mask = waveform > limiter_threshold
|
| 402 |
+
temp = (waveform[mask] - limiter_threshold) * limiter_gain / (1 - limiter_threshold)
|
| 403 |
+
output_waveform[mask] = (temp + limiter_threshold + shift).clamp(max=limiter_threshold)
|
| 404 |
+
output_waveform[~mask] = (waveform[~mask] + shift).clamp(min=-1, max=1)
|
| 405 |
+
elif limiter_gain is not None and shift < 0:
|
| 406 |
+
mask = waveform < -limiter_threshold
|
| 407 |
+
temp = (waveform[mask] + limiter_threshold) * limiter_gain / (1 - limiter_threshold)
|
| 408 |
+
output_waveform[mask] = (temp - limiter_threshold + shift).clamp(min=-limiter_threshold)
|
| 409 |
+
output_waveform[~mask] = (waveform[~mask] + shift).clamp(min=-1, max=1)
|
| 410 |
+
else:
|
| 411 |
+
output_waveform = (waveform + shift).clamp(min=-1, max=1)
|
| 412 |
+
|
| 413 |
+
return output_waveform
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def deemph_biquad(waveform: Tensor, sample_rate: int) -> Tensor:
|
| 417 |
+
r"""Apply ISO 908 CD de-emphasis (shelving) IIR filter. Similar to SoX implementation.
|
| 418 |
+
|
| 419 |
+
.. devices:: CPU CUDA
|
| 420 |
+
|
| 421 |
+
.. properties:: Autograd TorchScript
|
| 422 |
+
|
| 423 |
+
Args:
|
| 424 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 425 |
+
sample_rate (int): sampling rate of the waveform, Allowed sample rate ``44100`` or ``48000``
|
| 426 |
+
|
| 427 |
+
Returns:
|
| 428 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 429 |
+
|
| 430 |
+
Reference:
|
| 431 |
+
- http://sox.sourceforge.net/sox.html
|
| 432 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 433 |
+
"""
|
| 434 |
+
|
| 435 |
+
if sample_rate == 44100:
|
| 436 |
+
central_freq = 5283
|
| 437 |
+
width_slope = 0.4845
|
| 438 |
+
gain = -9.477
|
| 439 |
+
elif sample_rate == 48000:
|
| 440 |
+
central_freq = 5356
|
| 441 |
+
width_slope = 0.479
|
| 442 |
+
gain = -9.62
|
| 443 |
+
else:
|
| 444 |
+
raise ValueError("Sample rate must be 44100 (audio-CD) or 48000 (DAT)")
|
| 445 |
+
|
| 446 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 447 |
+
A = math.exp(gain / 40.0 * math.log(10))
|
| 448 |
+
alpha = math.sin(w0) / 2 * math.sqrt((A + 1 / A) * (1 / width_slope - 1) + 2)
|
| 449 |
+
|
| 450 |
+
temp1 = 2 * math.sqrt(A) * alpha
|
| 451 |
+
temp2 = (A - 1) * math.cos(w0)
|
| 452 |
+
temp3 = (A + 1) * math.cos(w0)
|
| 453 |
+
|
| 454 |
+
b0 = A * ((A + 1) + temp2 + temp1)
|
| 455 |
+
b1 = -2 * A * ((A - 1) + temp3)
|
| 456 |
+
b2 = A * ((A + 1) + temp2 - temp1)
|
| 457 |
+
a0 = (A + 1) - temp2 + temp1
|
| 458 |
+
a1 = 2 * ((A - 1) - temp3)
|
| 459 |
+
a2 = (A + 1) - temp2 - temp1
|
| 460 |
+
|
| 461 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
def _add_noise_shaping(dithered_waveform: Tensor, waveform: Tensor) -> Tensor:
|
| 465 |
+
r"""Noise shaping is calculated by error:
|
| 466 |
+
error[n] = dithered[n] - original[n]
|
| 467 |
+
noise_shaped_waveform[n] = dithered[n] + error[n-1]
|
| 468 |
+
"""
|
| 469 |
+
wf_shape = waveform.size()
|
| 470 |
+
waveform = waveform.reshape(-1, wf_shape[-1])
|
| 471 |
+
|
| 472 |
+
dithered_shape = dithered_waveform.size()
|
| 473 |
+
dithered_waveform = dithered_waveform.reshape(-1, dithered_shape[-1])
|
| 474 |
+
|
| 475 |
+
error = dithered_waveform - waveform
|
| 476 |
+
|
| 477 |
+
# add error[n-1] to dithered_waveform[n], so offset the error by 1 index
|
| 478 |
+
zeros = torch.zeros(1, dtype=error.dtype, device=error.device)
|
| 479 |
+
for index in range(error.size()[0]):
|
| 480 |
+
err = error[index]
|
| 481 |
+
error_offset = torch.cat((zeros, err))
|
| 482 |
+
error[index] = error_offset[: waveform.size()[1]]
|
| 483 |
+
|
| 484 |
+
noise_shaped = dithered_waveform + error
|
| 485 |
+
return noise_shaped.reshape(dithered_shape[:-1] + noise_shaped.shape[-1:])
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def _apply_probability_distribution(waveform: Tensor, density_function: str = "TPDF") -> Tensor:
|
| 489 |
+
r"""Apply a probability distribution function on a waveform.
|
| 490 |
+
|
| 491 |
+
Triangular probability density function (TPDF) dither noise has a
|
| 492 |
+
triangular distribution; values in the center of the range have a higher
|
| 493 |
+
probability of occurring.
|
| 494 |
+
|
| 495 |
+
Rectangular probability density function (RPDF) dither noise has a
|
| 496 |
+
uniform distribution; any value in the specified range has the same
|
| 497 |
+
probability of occurring.
|
| 498 |
+
|
| 499 |
+
Gaussian probability density function (GPDF) has a normal distribution.
|
| 500 |
+
The relationship of probabilities of results follows a bell-shaped,
|
| 501 |
+
or Gaussian curve, typical of dither generated by analog sources.
|
| 502 |
+
Args:
|
| 503 |
+
waveform (Tensor): Tensor of audio of dimension (..., time)
|
| 504 |
+
density_function (str, optional): The density function of a
|
| 505 |
+
continuous random variable (Default: ``"TPDF"``)
|
| 506 |
+
Options: Triangular Probability Density Function - `TPDF`
|
| 507 |
+
Rectangular Probability Density Function - `RPDF`
|
| 508 |
+
Gaussian Probability Density Function - `GPDF`
|
| 509 |
+
Returns:
|
| 510 |
+
Tensor: waveform dithered with TPDF
|
| 511 |
+
"""
|
| 512 |
+
|
| 513 |
+
# pack batch
|
| 514 |
+
shape = waveform.size()
|
| 515 |
+
waveform = waveform.reshape(-1, shape[-1])
|
| 516 |
+
|
| 517 |
+
channel_size = waveform.size()[0] - 1
|
| 518 |
+
time_size = waveform.size()[-1] - 1
|
| 519 |
+
|
| 520 |
+
random_channel = (
|
| 521 |
+
int(
|
| 522 |
+
torch.randint(
|
| 523 |
+
channel_size,
|
| 524 |
+
[
|
| 525 |
+
1,
|
| 526 |
+
],
|
| 527 |
+
).item()
|
| 528 |
+
)
|
| 529 |
+
if channel_size > 0
|
| 530 |
+
else 0
|
| 531 |
+
)
|
| 532 |
+
random_time = (
|
| 533 |
+
int(
|
| 534 |
+
torch.randint(
|
| 535 |
+
time_size,
|
| 536 |
+
[
|
| 537 |
+
1,
|
| 538 |
+
],
|
| 539 |
+
).item()
|
| 540 |
+
)
|
| 541 |
+
if time_size > 0
|
| 542 |
+
else 0
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
number_of_bits = 16
|
| 546 |
+
up_scaling = 2 ** (number_of_bits - 1) - 2
|
| 547 |
+
signal_scaled = waveform * up_scaling
|
| 548 |
+
down_scaling = 2 ** (number_of_bits - 1)
|
| 549 |
+
|
| 550 |
+
signal_scaled_dis = waveform
|
| 551 |
+
if density_function == "RPDF":
|
| 552 |
+
RPDF = waveform[random_channel][random_time] - 0.5
|
| 553 |
+
|
| 554 |
+
signal_scaled_dis = signal_scaled + RPDF
|
| 555 |
+
elif density_function == "GPDF":
|
| 556 |
+
# TODO Replace by distribution code once
|
| 557 |
+
# https://github.com/pytorch/pytorch/issues/29843 is resolved
|
| 558 |
+
# gaussian = torch.distributions.normal.Normal(torch.mean(waveform, -1), 1).sample()
|
| 559 |
+
|
| 560 |
+
num_rand_variables = 6
|
| 561 |
+
|
| 562 |
+
gaussian = waveform[random_channel][random_time]
|
| 563 |
+
for ws in num_rand_variables * [time_size]:
|
| 564 |
+
rand_chan = int(
|
| 565 |
+
torch.randint(
|
| 566 |
+
channel_size,
|
| 567 |
+
[
|
| 568 |
+
1,
|
| 569 |
+
],
|
| 570 |
+
).item()
|
| 571 |
+
)
|
| 572 |
+
gaussian += waveform[rand_chan][
|
| 573 |
+
int(
|
| 574 |
+
torch.randint(
|
| 575 |
+
ws,
|
| 576 |
+
[
|
| 577 |
+
1,
|
| 578 |
+
],
|
| 579 |
+
).item()
|
| 580 |
+
)
|
| 581 |
+
]
|
| 582 |
+
|
| 583 |
+
signal_scaled_dis = signal_scaled + gaussian
|
| 584 |
+
else:
|
| 585 |
+
# dtype needed for https://github.com/pytorch/pytorch/issues/32358
|
| 586 |
+
TPDF = torch.bartlett_window(time_size + 1, dtype=signal_scaled.dtype, device=signal_scaled.device)
|
| 587 |
+
TPDF = TPDF.repeat((channel_size + 1), 1)
|
| 588 |
+
signal_scaled_dis = signal_scaled + TPDF
|
| 589 |
+
|
| 590 |
+
quantised_signal_scaled = torch.round(signal_scaled_dis)
|
| 591 |
+
quantised_signal = quantised_signal_scaled / down_scaling
|
| 592 |
+
|
| 593 |
+
# unpack batch
|
| 594 |
+
return quantised_signal.reshape(shape[:-1] + quantised_signal.shape[-1:])
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
def dither(waveform: Tensor, density_function: str = "TPDF", noise_shaping: bool = False) -> Tensor:
|
| 598 |
+
r"""Apply dither
|
| 599 |
+
|
| 600 |
+
.. devices:: CPU CUDA
|
| 601 |
+
|
| 602 |
+
.. properties:: TorchScript
|
| 603 |
+
|
| 604 |
+
Dither increases the perceived dynamic range of audio stored at a
|
| 605 |
+
particular bit-depth by eliminating nonlinear truncation distortion
|
| 606 |
+
(i.e. adding minimally perceived noise to mask distortion caused by quantization).
|
| 607 |
+
|
| 608 |
+
Args:
|
| 609 |
+
waveform (Tensor): Tensor of audio of dimension (..., time)
|
| 610 |
+
density_function (str, optional):
|
| 611 |
+
The density function of a continuous random variable. One of
|
| 612 |
+
``"TPDF"`` (Triangular Probability Density Function),
|
| 613 |
+
``"RPDF"`` (Rectangular Probability Density Function) or
|
| 614 |
+
``"GPDF"`` (Gaussian Probability Density Function) (Default: ``"TPDF"``).
|
| 615 |
+
noise_shaping (bool, optional): a filtering process that shapes the spectral
|
| 616 |
+
energy of quantisation error (Default: ``False``)
|
| 617 |
+
|
| 618 |
+
Returns:
|
| 619 |
+
Tensor: waveform dithered
|
| 620 |
+
"""
|
| 621 |
+
dithered = _apply_probability_distribution(waveform, density_function=density_function)
|
| 622 |
+
|
| 623 |
+
if noise_shaping:
|
| 624 |
+
return _add_noise_shaping(dithered, waveform)
|
| 625 |
+
else:
|
| 626 |
+
return dithered
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
def equalizer_biquad(
|
| 630 |
+
waveform: Tensor,
|
| 631 |
+
sample_rate: int,
|
| 632 |
+
center_freq: float,
|
| 633 |
+
gain: float,
|
| 634 |
+
Q: float = 0.707,
|
| 635 |
+
) -> Tensor:
|
| 636 |
+
r"""Design biquad peaking equalizer filter and perform filtering. Similar to SoX implementation.
|
| 637 |
+
|
| 638 |
+
.. devices:: CPU CUDA
|
| 639 |
+
|
| 640 |
+
.. properties:: Autograd TorchScript
|
| 641 |
+
|
| 642 |
+
Args:
|
| 643 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 644 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 645 |
+
center_freq (float): filter's central frequency
|
| 646 |
+
gain (float or torch.Tensor): desired gain at the boost (or attenuation) in dB
|
| 647 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``)
|
| 648 |
+
|
| 649 |
+
Returns:
|
| 650 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 651 |
+
"""
|
| 652 |
+
dtype = waveform.dtype
|
| 653 |
+
device = waveform.device
|
| 654 |
+
center_freq = torch.as_tensor(center_freq, dtype=dtype, device=device)
|
| 655 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 656 |
+
gain = torch.as_tensor(gain, dtype=dtype, device=device)
|
| 657 |
+
|
| 658 |
+
w0 = 2 * math.pi * center_freq / sample_rate
|
| 659 |
+
A = torch.exp(gain / 40.0 * math.log(10))
|
| 660 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 661 |
+
|
| 662 |
+
b0 = 1 + alpha * A
|
| 663 |
+
b1 = -2 * torch.cos(w0)
|
| 664 |
+
b2 = 1 - alpha * A
|
| 665 |
+
a0 = 1 + alpha / A
|
| 666 |
+
a1 = -2 * torch.cos(w0)
|
| 667 |
+
a2 = 1 - alpha / A
|
| 668 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
def filtfilt(
|
| 672 |
+
waveform: Tensor,
|
| 673 |
+
a_coeffs: Tensor,
|
| 674 |
+
b_coeffs: Tensor,
|
| 675 |
+
clamp: bool = True,
|
| 676 |
+
) -> Tensor:
|
| 677 |
+
r"""Apply an IIR filter forward and backward to a waveform.
|
| 678 |
+
|
| 679 |
+
.. devices:: CPU CUDA
|
| 680 |
+
|
| 681 |
+
.. properties:: Autograd TorchScript
|
| 682 |
+
|
| 683 |
+
Inspired by https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.filtfilt.html
|
| 684 |
+
|
| 685 |
+
Args:
|
| 686 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`. Must be normalized to -1 to 1.
|
| 687 |
+
a_coeffs (Tensor): denominator coefficients of difference equation of dimension of either
|
| 688 |
+
1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`.
|
| 689 |
+
Lower delay coefficients are first, e.g. ``[a0, a1, a2, ...]``.
|
| 690 |
+
Must be same size as b_coeffs (pad with 0's as necessary).
|
| 691 |
+
b_coeffs (Tensor): numerator coefficients of difference equation of dimension of either
|
| 692 |
+
1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`.
|
| 693 |
+
Lower delay coefficients are first, e.g. ``[b0, b1, b2, ...]``.
|
| 694 |
+
Must be same size as a_coeffs (pad with 0's as necessary).
|
| 695 |
+
clamp (bool, optional): If ``True``, clamp the output signal to be in the range [-1, 1] (Default: ``True``)
|
| 696 |
+
|
| 697 |
+
Returns:
|
| 698 |
+
Tensor: Waveform with dimension of either `(..., num_filters, time)` if ``a_coeffs`` and ``b_coeffs``
|
| 699 |
+
are 2D Tensors, or `(..., time)` otherwise.
|
| 700 |
+
"""
|
| 701 |
+
forward_filtered = lfilter(waveform, a_coeffs, b_coeffs, clamp=False, batching=True)
|
| 702 |
+
backward_filtered = lfilter(
|
| 703 |
+
forward_filtered.flip(-1),
|
| 704 |
+
a_coeffs,
|
| 705 |
+
b_coeffs,
|
| 706 |
+
clamp=clamp,
|
| 707 |
+
batching=True,
|
| 708 |
+
).flip(-1)
|
| 709 |
+
return backward_filtered
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
def flanger(
|
| 713 |
+
waveform: Tensor,
|
| 714 |
+
sample_rate: int,
|
| 715 |
+
delay: float = 0.0,
|
| 716 |
+
depth: float = 2.0,
|
| 717 |
+
regen: float = 0.0,
|
| 718 |
+
width: float = 71.0,
|
| 719 |
+
speed: float = 0.5,
|
| 720 |
+
phase: float = 25.0,
|
| 721 |
+
modulation: str = "sinusoidal",
|
| 722 |
+
interpolation: str = "linear",
|
| 723 |
+
) -> Tensor:
|
| 724 |
+
r"""Apply a flanger effect to the audio. Similar to SoX implementation.
|
| 725 |
+
|
| 726 |
+
.. devices:: CPU CUDA
|
| 727 |
+
|
| 728 |
+
.. properties:: Autograd TorchScript
|
| 729 |
+
|
| 730 |
+
Args:
|
| 731 |
+
waveform (Tensor): audio waveform of dimension of `(..., channel, time)` .
|
| 732 |
+
Max 4 channels allowed
|
| 733 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 734 |
+
delay (float, optional): desired delay in milliseconds(ms)
|
| 735 |
+
Allowed range of values are 0 to 30
|
| 736 |
+
depth (float, optional): desired delay depth in milliseconds(ms)
|
| 737 |
+
Allowed range of values are 0 to 10
|
| 738 |
+
regen (float, optional): desired regen(feedback gain) in dB
|
| 739 |
+
Allowed range of values are -95 to 95
|
| 740 |
+
width (float, optional): desired width(delay gain) in dB
|
| 741 |
+
Allowed range of values are 0 to 100
|
| 742 |
+
speed (float, optional): modulation speed in Hz
|
| 743 |
+
Allowed range of values are 0.1 to 10
|
| 744 |
+
phase (float, optional): percentage phase-shift for multi-channel
|
| 745 |
+
Allowed range of values are 0 to 100
|
| 746 |
+
modulation (str, optional): Use either "sinusoidal" or "triangular" modulation. (Default: ``sinusoidal``)
|
| 747 |
+
interpolation (str, optional): Use either "linear" or "quadratic" for delay-line interpolation.
|
| 748 |
+
(Default: ``linear``)
|
| 749 |
+
|
| 750 |
+
Returns:
|
| 751 |
+
Tensor: Waveform of dimension of `(..., channel, time)`
|
| 752 |
+
|
| 753 |
+
Reference:
|
| 754 |
+
- http://sox.sourceforge.net/sox.html
|
| 755 |
+
|
| 756 |
+
- Scott Lehman, `Effects Explained`_,
|
| 757 |
+
|
| 758 |
+
.. _Effects Explained:
|
| 759 |
+
https://web.archive.org/web/20051125072557/http://www.harmony-central.com/Effects/effects-explained.html
|
| 760 |
+
"""
|
| 761 |
+
|
| 762 |
+
if modulation not in ("sinusoidal", "triangular"):
|
| 763 |
+
raise ValueError('Only "sinusoidal" or "triangular" modulation allowed')
|
| 764 |
+
|
| 765 |
+
if interpolation not in ("linear", "quadratic"):
|
| 766 |
+
raise ValueError('Only "linear" or "quadratic" interpolation allowed')
|
| 767 |
+
|
| 768 |
+
actual_shape = waveform.shape
|
| 769 |
+
device, dtype = waveform.device, waveform.dtype
|
| 770 |
+
|
| 771 |
+
if actual_shape[-2] > 4:
|
| 772 |
+
raise ValueError("Max 4 channels allowed")
|
| 773 |
+
|
| 774 |
+
# convert to 3D (batch, channels, time)
|
| 775 |
+
waveform = waveform.view(-1, actual_shape[-2], actual_shape[-1])
|
| 776 |
+
|
| 777 |
+
# Scaling
|
| 778 |
+
feedback_gain = regen / 100
|
| 779 |
+
delay_gain = width / 100
|
| 780 |
+
channel_phase = phase / 100
|
| 781 |
+
delay_min = delay / 1000
|
| 782 |
+
delay_depth = depth / 1000
|
| 783 |
+
|
| 784 |
+
n_channels = waveform.shape[-2]
|
| 785 |
+
|
| 786 |
+
if modulation == "sinusoidal":
|
| 787 |
+
wave_type = "SINE"
|
| 788 |
+
else:
|
| 789 |
+
wave_type = "TRIANGLE"
|
| 790 |
+
|
| 791 |
+
# Balance output:
|
| 792 |
+
in_gain = 1.0 / (1 + delay_gain)
|
| 793 |
+
delay_gain = delay_gain / (1 + delay_gain)
|
| 794 |
+
|
| 795 |
+
# Balance feedback loop:
|
| 796 |
+
delay_gain = delay_gain * (1 - abs(feedback_gain))
|
| 797 |
+
|
| 798 |
+
delay_buf_length = int((delay_min + delay_depth) * sample_rate + 0.5)
|
| 799 |
+
delay_buf_length = delay_buf_length + 2
|
| 800 |
+
|
| 801 |
+
delay_bufs = torch.zeros(waveform.shape[0], n_channels, delay_buf_length, dtype=dtype, device=device)
|
| 802 |
+
delay_last = torch.zeros(waveform.shape[0], n_channels, dtype=dtype, device=device)
|
| 803 |
+
|
| 804 |
+
lfo_length = int(sample_rate / speed)
|
| 805 |
+
|
| 806 |
+
table_min = math.floor(delay_min * sample_rate + 0.5)
|
| 807 |
+
table_max = delay_buf_length - 2.0
|
| 808 |
+
|
| 809 |
+
lfo = _generate_wave_table(
|
| 810 |
+
wave_type=wave_type,
|
| 811 |
+
data_type="FLOAT",
|
| 812 |
+
table_size=lfo_length,
|
| 813 |
+
min=float(table_min),
|
| 814 |
+
max=float(table_max),
|
| 815 |
+
phase=3 * math.pi / 2,
|
| 816 |
+
device=device,
|
| 817 |
+
)
|
| 818 |
+
|
| 819 |
+
output_waveform = torch.zeros_like(waveform, dtype=dtype, device=device)
|
| 820 |
+
|
| 821 |
+
delay_buf_pos = 0
|
| 822 |
+
lfo_pos = 0
|
| 823 |
+
channel_idxs = torch.arange(0, n_channels, device=device)
|
| 824 |
+
|
| 825 |
+
for i in range(waveform.shape[-1]):
|
| 826 |
+
|
| 827 |
+
delay_buf_pos = (delay_buf_pos + delay_buf_length - 1) % delay_buf_length
|
| 828 |
+
|
| 829 |
+
cur_channel_phase = (channel_idxs * lfo_length * channel_phase + 0.5).to(torch.int64)
|
| 830 |
+
delay_tensor = lfo[(lfo_pos + cur_channel_phase) % lfo_length]
|
| 831 |
+
frac_delay = torch.frac(delay_tensor)
|
| 832 |
+
delay_tensor = torch.floor(delay_tensor)
|
| 833 |
+
|
| 834 |
+
int_delay = delay_tensor.to(torch.int64)
|
| 835 |
+
|
| 836 |
+
temp = waveform[:, :, i]
|
| 837 |
+
|
| 838 |
+
delay_bufs[:, :, delay_buf_pos] = temp + delay_last * feedback_gain
|
| 839 |
+
|
| 840 |
+
delayed_0 = delay_bufs[:, channel_idxs, (delay_buf_pos + int_delay) % delay_buf_length]
|
| 841 |
+
|
| 842 |
+
int_delay = int_delay + 1
|
| 843 |
+
|
| 844 |
+
delayed_1 = delay_bufs[:, channel_idxs, (delay_buf_pos + int_delay) % delay_buf_length]
|
| 845 |
+
|
| 846 |
+
int_delay = int_delay + 1
|
| 847 |
+
|
| 848 |
+
if interpolation == "linear":
|
| 849 |
+
delayed = delayed_0 + (delayed_1 - delayed_0) * frac_delay
|
| 850 |
+
else:
|
| 851 |
+
delayed_2 = delay_bufs[:, channel_idxs, (delay_buf_pos + int_delay) % delay_buf_length]
|
| 852 |
+
|
| 853 |
+
int_delay = int_delay + 1
|
| 854 |
+
|
| 855 |
+
delayed_2 = delayed_2 - delayed_0
|
| 856 |
+
delayed_1 = delayed_1 - delayed_0
|
| 857 |
+
a = delayed_2 * 0.5 - delayed_1
|
| 858 |
+
b = delayed_1 * 2 - delayed_2 * 0.5
|
| 859 |
+
|
| 860 |
+
delayed = delayed_0 + (a * frac_delay + b) * frac_delay
|
| 861 |
+
|
| 862 |
+
delay_last = delayed
|
| 863 |
+
output_waveform[:, :, i] = waveform[:, :, i] * in_gain + delayed * delay_gain
|
| 864 |
+
|
| 865 |
+
lfo_pos = (lfo_pos + 1) % lfo_length
|
| 866 |
+
|
| 867 |
+
return output_waveform.clamp(min=-1, max=1).view(actual_shape)
|
| 868 |
+
|
| 869 |
+
|
| 870 |
+
def gain(waveform: Tensor, gain_db: float = 1.0) -> Tensor:
|
| 871 |
+
r"""Apply amplification or attenuation to the whole waveform.
|
| 872 |
+
|
| 873 |
+
.. devices:: CPU CUDA
|
| 874 |
+
|
| 875 |
+
.. properties:: Autograd TorchScript
|
| 876 |
+
|
| 877 |
+
Args:
|
| 878 |
+
waveform (Tensor): Tensor of audio of dimension (..., time).
|
| 879 |
+
gain_db (float, optional) Gain adjustment in decibels (dB) (Default: ``1.0``).
|
| 880 |
+
|
| 881 |
+
Returns:
|
| 882 |
+
Tensor: the whole waveform amplified by gain_db.
|
| 883 |
+
"""
|
| 884 |
+
if gain_db == 0:
|
| 885 |
+
return waveform
|
| 886 |
+
|
| 887 |
+
ratio = 10 ** (gain_db / 20)
|
| 888 |
+
|
| 889 |
+
return waveform * ratio
|
| 890 |
+
|
| 891 |
+
|
| 892 |
+
def highpass_biquad(waveform: Tensor, sample_rate: int, cutoff_freq: float, Q: float = 0.707) -> Tensor:
|
| 893 |
+
r"""Design biquad highpass filter and perform filtering. Similar to SoX implementation.
|
| 894 |
+
|
| 895 |
+
.. devices:: CPU CUDA
|
| 896 |
+
|
| 897 |
+
.. properties:: Autograd TorchScript
|
| 898 |
+
|
| 899 |
+
Args:
|
| 900 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 901 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 902 |
+
cutoff_freq (float or torch.Tensor): filter cutoff frequency
|
| 903 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``)
|
| 904 |
+
|
| 905 |
+
Returns:
|
| 906 |
+
Tensor: Waveform dimension of `(..., time)`
|
| 907 |
+
"""
|
| 908 |
+
dtype = waveform.dtype
|
| 909 |
+
device = waveform.device
|
| 910 |
+
cutoff_freq = torch.as_tensor(cutoff_freq, dtype=dtype, device=device)
|
| 911 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 912 |
+
|
| 913 |
+
w0 = 2 * math.pi * cutoff_freq / sample_rate
|
| 914 |
+
alpha = torch.sin(w0) / 2.0 / Q
|
| 915 |
+
|
| 916 |
+
b0 = (1 + torch.cos(w0)) / 2
|
| 917 |
+
b1 = -1 - torch.cos(w0)
|
| 918 |
+
b2 = b0
|
| 919 |
+
a0 = 1 + alpha
|
| 920 |
+
a1 = -2 * torch.cos(w0)
|
| 921 |
+
a2 = 1 - alpha
|
| 922 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 923 |
+
|
| 924 |
+
|
| 925 |
+
def _lfilter_core_generic_loop(input_signal_windows: Tensor, a_coeffs_flipped: Tensor, padded_output_waveform: Tensor):
|
| 926 |
+
n_order = a_coeffs_flipped.size(1)
|
| 927 |
+
a_coeffs_flipped = a_coeffs_flipped.unsqueeze(2)
|
| 928 |
+
for i_sample, o0 in enumerate(input_signal_windows.permute(2, 0, 1)):
|
| 929 |
+
windowed_output_signal = padded_output_waveform[:, :, i_sample : i_sample + n_order]
|
| 930 |
+
o0 -= (windowed_output_signal.transpose(0, 1) @ a_coeffs_flipped)[..., 0].t()
|
| 931 |
+
padded_output_waveform[:, :, i_sample + n_order - 1] = o0
|
| 932 |
+
|
| 933 |
+
|
| 934 |
+
if _IS_TORCHAUDIO_EXT_AVAILABLE:
|
| 935 |
+
_lfilter_core_cpu_loop = torch.ops.torchaudio._lfilter_core_loop
|
| 936 |
+
else:
|
| 937 |
+
_lfilter_core_cpu_loop = _lfilter_core_generic_loop
|
| 938 |
+
|
| 939 |
+
|
| 940 |
+
def _lfilter_core(
|
| 941 |
+
waveform: Tensor,
|
| 942 |
+
a_coeffs: Tensor,
|
| 943 |
+
b_coeffs: Tensor,
|
| 944 |
+
) -> Tensor:
|
| 945 |
+
|
| 946 |
+
if a_coeffs.size() != b_coeffs.size():
|
| 947 |
+
raise ValueError(
|
| 948 |
+
"Expected coeffs to be the same size."
|
| 949 |
+
f"Found a_coeffs size: {a_coeffs.size()}, b_coeffs size: {b_coeffs.size()}"
|
| 950 |
+
)
|
| 951 |
+
if waveform.ndim != 3:
|
| 952 |
+
raise ValueError(f"Expected waveform to be 3 dimensional. Found: {waveform.ndim}")
|
| 953 |
+
if not (waveform.device == a_coeffs.device == b_coeffs.device):
|
| 954 |
+
raise ValueError(
|
| 955 |
+
"Expected waveform and coeffs to be on the same device."
|
| 956 |
+
f"Found: waveform device:{waveform.device}, a_coeffs device: {a_coeffs.device}, "
|
| 957 |
+
f"b_coeffs device: {b_coeffs.device}"
|
| 958 |
+
)
|
| 959 |
+
|
| 960 |
+
n_batch, n_channel, n_sample = waveform.size()
|
| 961 |
+
n_order = a_coeffs.size(1)
|
| 962 |
+
if n_order <= 0:
|
| 963 |
+
raise ValueError(f"Expected n_order to be positive. Found: {n_order}")
|
| 964 |
+
|
| 965 |
+
# Pad the input and create output
|
| 966 |
+
|
| 967 |
+
padded_waveform = torch.nn.functional.pad(waveform, [n_order - 1, 0])
|
| 968 |
+
padded_output_waveform = torch.zeros_like(padded_waveform)
|
| 969 |
+
|
| 970 |
+
# Set up the coefficients matrix
|
| 971 |
+
# Flip coefficients' order
|
| 972 |
+
a_coeffs_flipped = a_coeffs.flip(1)
|
| 973 |
+
b_coeffs_flipped = b_coeffs.flip(1)
|
| 974 |
+
|
| 975 |
+
# calculate windowed_input_signal in parallel using convolution
|
| 976 |
+
input_signal_windows = torch.nn.functional.conv1d(padded_waveform, b_coeffs_flipped.unsqueeze(1), groups=n_channel)
|
| 977 |
+
|
| 978 |
+
input_signal_windows.div_(a_coeffs[:, :1])
|
| 979 |
+
a_coeffs_flipped.div_(a_coeffs[:, :1])
|
| 980 |
+
|
| 981 |
+
if (
|
| 982 |
+
input_signal_windows.device == torch.device("cpu")
|
| 983 |
+
and a_coeffs_flipped.device == torch.device("cpu")
|
| 984 |
+
and padded_output_waveform.device == torch.device("cpu")
|
| 985 |
+
):
|
| 986 |
+
_lfilter_core_cpu_loop(input_signal_windows, a_coeffs_flipped, padded_output_waveform)
|
| 987 |
+
else:
|
| 988 |
+
_lfilter_core_generic_loop(input_signal_windows, a_coeffs_flipped, padded_output_waveform)
|
| 989 |
+
|
| 990 |
+
output = padded_output_waveform[:, :, n_order - 1 :]
|
| 991 |
+
return output
|
| 992 |
+
|
| 993 |
+
|
| 994 |
+
if _IS_TORCHAUDIO_EXT_AVAILABLE:
|
| 995 |
+
_lfilter = torch.ops.torchaudio._lfilter
|
| 996 |
+
else:
|
| 997 |
+
_lfilter = _lfilter_core
|
| 998 |
+
|
| 999 |
+
|
| 1000 |
+
def lfilter(waveform: Tensor, a_coeffs: Tensor, b_coeffs: Tensor, clamp: bool = True, batching: bool = True) -> Tensor:
|
| 1001 |
+
r"""Perform an IIR filter by evaluating difference equation, using differentiable implementation
|
| 1002 |
+
developed separately by *Yu et al.* :cite:`ismir_YuF23` and *Forgione et al.* :cite:`forgione2021dynonet`.
|
| 1003 |
+
The gradients of ``a_coeffs`` are computed based on a faster algorithm from :cite:`ycy2024diffapf`.
|
| 1004 |
+
|
| 1005 |
+
.. devices:: CPU CUDA
|
| 1006 |
+
|
| 1007 |
+
.. properties:: Autograd TorchScript
|
| 1008 |
+
|
| 1009 |
+
Note:
|
| 1010 |
+
To avoid numerical problems, small filter order is preferred.
|
| 1011 |
+
Using double precision could also minimize numerical precision errors.
|
| 1012 |
+
|
| 1013 |
+
Args:
|
| 1014 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`. Must be normalized to -1 to 1.
|
| 1015 |
+
a_coeffs (Tensor): denominator coefficients of difference equation of dimension of either
|
| 1016 |
+
1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`.
|
| 1017 |
+
Lower delays coefficients are first, e.g. ``[a0, a1, a2, ...]``.
|
| 1018 |
+
Must be same size as b_coeffs (pad with 0's as necessary).
|
| 1019 |
+
b_coeffs (Tensor): numerator coefficients of difference equation of dimension of either
|
| 1020 |
+
1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`.
|
| 1021 |
+
Lower delays coefficients are first, e.g. ``[b0, b1, b2, ...]``.
|
| 1022 |
+
Must be same size as a_coeffs (pad with 0's as necessary).
|
| 1023 |
+
clamp (bool, optional): If ``True``, clamp the output signal to be in the range [-1, 1] (Default: ``True``)
|
| 1024 |
+
batching (bool, optional): Effective only when coefficients are 2D. If ``True``, then waveform should be at
|
| 1025 |
+
least 2D, and the size of second axis from last should equals to ``num_filters``.
|
| 1026 |
+
The output can be expressed as ``output[..., i, :] = lfilter(waveform[..., i, :],
|
| 1027 |
+
a_coeffs[i], b_coeffs[i], clamp=clamp, batching=False)``. (Default: ``True``)
|
| 1028 |
+
|
| 1029 |
+
Returns:
|
| 1030 |
+
Tensor: Waveform with dimension of either `(..., num_filters, time)` if ``a_coeffs`` and ``b_coeffs``
|
| 1031 |
+
are 2D Tensors, or `(..., time)` otherwise.
|
| 1032 |
+
"""
|
| 1033 |
+
if a_coeffs.size() != b_coeffs.size():
|
| 1034 |
+
raise ValueError(
|
| 1035 |
+
"Expected coeffs to be the same size."
|
| 1036 |
+
f"Found: a_coeffs size: {a_coeffs.size()}, b_coeffs size: {b_coeffs.size()}"
|
| 1037 |
+
)
|
| 1038 |
+
if a_coeffs.ndim > 2:
|
| 1039 |
+
raise ValueError(f"Expected coeffs to have greater than 1 dimension. Found: {a_coeffs.ndim}")
|
| 1040 |
+
|
| 1041 |
+
if a_coeffs.ndim > 1:
|
| 1042 |
+
if batching:
|
| 1043 |
+
if waveform.ndim <= 0:
|
| 1044 |
+
raise ValueError("Expected waveform to have a positive number of dimensions." f"Found: {waveform.ndim}")
|
| 1045 |
+
if waveform.shape[-2] != a_coeffs.shape[0]:
|
| 1046 |
+
raise ValueError(
|
| 1047 |
+
"Expected number of batches in waveform and coeffs to be the same."
|
| 1048 |
+
f"Found: coeffs batches: {a_coeffs.shape[0]}, waveform batches: {waveform.shape[-2]}"
|
| 1049 |
+
)
|
| 1050 |
+
else:
|
| 1051 |
+
waveform = torch.stack([waveform] * a_coeffs.shape[0], -2)
|
| 1052 |
+
else:
|
| 1053 |
+
a_coeffs = a_coeffs.unsqueeze(0)
|
| 1054 |
+
b_coeffs = b_coeffs.unsqueeze(0)
|
| 1055 |
+
|
| 1056 |
+
# pack batch
|
| 1057 |
+
shape = waveform.size()
|
| 1058 |
+
waveform = waveform.reshape(-1, a_coeffs.shape[0], shape[-1])
|
| 1059 |
+
output = _lfilter(waveform, a_coeffs, b_coeffs)
|
| 1060 |
+
|
| 1061 |
+
if clamp:
|
| 1062 |
+
output = torch.clamp(output, min=-1.0, max=1.0)
|
| 1063 |
+
|
| 1064 |
+
# unpack batch
|
| 1065 |
+
output = output.reshape(shape[:-1] + output.shape[-1:])
|
| 1066 |
+
|
| 1067 |
+
return output
|
| 1068 |
+
|
| 1069 |
+
|
| 1070 |
+
def lowpass_biquad(waveform: Tensor, sample_rate: int, cutoff_freq: float, Q: float = 0.707) -> Tensor:
|
| 1071 |
+
r"""Design biquad lowpass filter and perform filtering. Similar to SoX implementation.
|
| 1072 |
+
|
| 1073 |
+
.. devices:: CPU CUDA
|
| 1074 |
+
|
| 1075 |
+
.. properties:: Autograd TorchScript
|
| 1076 |
+
|
| 1077 |
+
Args:
|
| 1078 |
+
waveform (torch.Tensor): audio waveform of dimension of `(..., time)`
|
| 1079 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 1080 |
+
cutoff_freq (float or torch.Tensor): filter cutoff frequency
|
| 1081 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``)
|
| 1082 |
+
|
| 1083 |
+
Returns:
|
| 1084 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 1085 |
+
"""
|
| 1086 |
+
dtype = waveform.dtype
|
| 1087 |
+
device = waveform.device
|
| 1088 |
+
cutoff_freq = torch.as_tensor(cutoff_freq, dtype=dtype, device=device)
|
| 1089 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 1090 |
+
|
| 1091 |
+
w0 = 2 * math.pi * cutoff_freq / sample_rate
|
| 1092 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 1093 |
+
|
| 1094 |
+
b0 = (1 - torch.cos(w0)) / 2
|
| 1095 |
+
b1 = 1 - torch.cos(w0)
|
| 1096 |
+
b2 = b0
|
| 1097 |
+
a0 = 1 + alpha
|
| 1098 |
+
a1 = -2 * torch.cos(w0)
|
| 1099 |
+
a2 = 1 - alpha
|
| 1100 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 1101 |
+
|
| 1102 |
+
|
| 1103 |
+
def _overdrive_core_loop_generic(
|
| 1104 |
+
waveform: Tensor, temp: Tensor, last_in: Tensor, last_out: Tensor, output_waveform: Tensor
|
| 1105 |
+
):
|
| 1106 |
+
for i in range(waveform.shape[-1]):
|
| 1107 |
+
last_out = temp[:, i] - last_in + 0.995 * last_out
|
| 1108 |
+
last_in = temp[:, i]
|
| 1109 |
+
output_waveform[:, i] = waveform[:, i] * 0.5 + last_out * 0.75
|
| 1110 |
+
|
| 1111 |
+
|
| 1112 |
+
if _IS_TORCHAUDIO_EXT_AVAILABLE:
|
| 1113 |
+
_overdrive_core_loop_cpu = torch.ops.torchaudio._overdrive_core_loop
|
| 1114 |
+
else:
|
| 1115 |
+
_overdrive_core_loop_cpu = _overdrive_core_loop_generic
|
| 1116 |
+
|
| 1117 |
+
|
| 1118 |
+
def overdrive(waveform: Tensor, gain: float = 20, colour: float = 20) -> Tensor:
|
| 1119 |
+
r"""Apply a overdrive effect to the audio. Similar to SoX implementation.
|
| 1120 |
+
|
| 1121 |
+
.. devices:: CPU CUDA
|
| 1122 |
+
|
| 1123 |
+
.. properties:: Autograd TorchScript
|
| 1124 |
+
|
| 1125 |
+
This effect applies a non linear distortion to the audio signal.
|
| 1126 |
+
|
| 1127 |
+
Args:
|
| 1128 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 1129 |
+
gain (float, optional): desired gain at the boost (or attenuation) in dB
|
| 1130 |
+
Allowed range of values are 0 to 100
|
| 1131 |
+
colour (float, optional): controls the amount of even harmonic content in the over-driven output
|
| 1132 |
+
Allowed range of values are 0 to 100
|
| 1133 |
+
|
| 1134 |
+
Returns:
|
| 1135 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 1136 |
+
|
| 1137 |
+
Reference:
|
| 1138 |
+
- http://sox.sourceforge.net/sox.html
|
| 1139 |
+
"""
|
| 1140 |
+
actual_shape = waveform.shape
|
| 1141 |
+
device, dtype = waveform.device, waveform.dtype
|
| 1142 |
+
|
| 1143 |
+
# convert to 2D (..,time)
|
| 1144 |
+
waveform = waveform.view(-1, actual_shape[-1])
|
| 1145 |
+
|
| 1146 |
+
gain = _dB2Linear(gain)
|
| 1147 |
+
colour = colour / 200
|
| 1148 |
+
last_in = torch.zeros(waveform.shape[:-1], dtype=dtype, device=device)
|
| 1149 |
+
last_out = torch.zeros(waveform.shape[:-1], dtype=dtype, device=device)
|
| 1150 |
+
|
| 1151 |
+
temp = waveform * gain + colour
|
| 1152 |
+
|
| 1153 |
+
mask1 = temp < -1
|
| 1154 |
+
temp[mask1] = torch.tensor(-2.0 / 3.0, dtype=dtype, device=device)
|
| 1155 |
+
# Wrapping the constant with Tensor is required for Torchscript
|
| 1156 |
+
|
| 1157 |
+
mask2 = temp > 1
|
| 1158 |
+
temp[mask2] = torch.tensor(2.0 / 3.0, dtype=dtype, device=device)
|
| 1159 |
+
|
| 1160 |
+
mask3 = ~mask1 & ~mask2
|
| 1161 |
+
temp[mask3] = temp[mask3] - (temp[mask3] ** 3) * (1.0 / 3)
|
| 1162 |
+
|
| 1163 |
+
output_waveform = torch.zeros_like(waveform, dtype=dtype, device=device)
|
| 1164 |
+
|
| 1165 |
+
# Uses CPU optimized loop function if available for CPU device
|
| 1166 |
+
if device == torch.device("cpu"):
|
| 1167 |
+
_overdrive_core_loop_cpu(waveform, temp, last_in, last_out, output_waveform)
|
| 1168 |
+
else:
|
| 1169 |
+
_overdrive_core_loop_generic(waveform, temp, last_in, last_out, output_waveform)
|
| 1170 |
+
|
| 1171 |
+
return output_waveform.clamp(min=-1, max=1).view(actual_shape)
|
| 1172 |
+
|
| 1173 |
+
|
| 1174 |
+
def phaser(
|
| 1175 |
+
waveform: Tensor,
|
| 1176 |
+
sample_rate: int,
|
| 1177 |
+
gain_in: float = 0.4,
|
| 1178 |
+
gain_out: float = 0.74,
|
| 1179 |
+
delay_ms: float = 3.0,
|
| 1180 |
+
decay: float = 0.4,
|
| 1181 |
+
mod_speed: float = 0.5,
|
| 1182 |
+
sinusoidal: bool = True,
|
| 1183 |
+
) -> Tensor:
|
| 1184 |
+
r"""Apply a phasing effect to the audio. Similar to SoX implementation.
|
| 1185 |
+
|
| 1186 |
+
.. devices:: CPU CUDA
|
| 1187 |
+
|
| 1188 |
+
.. properties:: Autograd TorchScript
|
| 1189 |
+
|
| 1190 |
+
Args:
|
| 1191 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 1192 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 1193 |
+
gain_in (float, optional): desired input gain at the boost (or attenuation) in dB
|
| 1194 |
+
Allowed range of values are 0 to 1
|
| 1195 |
+
gain_out (float, optional): desired output gain at the boost (or attenuation) in dB
|
| 1196 |
+
Allowed range of values are 0 to 1e9
|
| 1197 |
+
delay_ms (float, optional): desired delay in milliseconds
|
| 1198 |
+
Allowed range of values are 0 to 5.0
|
| 1199 |
+
decay (float, optional): desired decay relative to gain-in
|
| 1200 |
+
Allowed range of values are 0 to 0.99
|
| 1201 |
+
mod_speed (float, optional): modulation speed in Hz
|
| 1202 |
+
Allowed range of values are 0.1 to 2
|
| 1203 |
+
sinusoidal (bool, optional): If ``True``, uses sinusoidal modulation (preferable for multiple instruments)
|
| 1204 |
+
If ``False``, uses triangular modulation (gives single instruments a sharper phasing effect)
|
| 1205 |
+
(Default: ``True``)
|
| 1206 |
+
|
| 1207 |
+
Returns:
|
| 1208 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 1209 |
+
|
| 1210 |
+
Reference:
|
| 1211 |
+
- http://sox.sourceforge.net/sox.html
|
| 1212 |
+
- Scott Lehman, `Effects Explained`_.
|
| 1213 |
+
|
| 1214 |
+
.. _Effects Explained:
|
| 1215 |
+
https://web.archive.org/web/20051125072557/http://www.harmony-central.com/Effects/effects-explained.html
|
| 1216 |
+
"""
|
| 1217 |
+
actual_shape = waveform.shape
|
| 1218 |
+
device, dtype = waveform.device, waveform.dtype
|
| 1219 |
+
|
| 1220 |
+
# convert to 2D (channels,time)
|
| 1221 |
+
waveform = waveform.view(-1, actual_shape[-1])
|
| 1222 |
+
|
| 1223 |
+
delay_buf_len = int((delay_ms * 0.001 * sample_rate) + 0.5)
|
| 1224 |
+
delay_buf = torch.zeros(waveform.shape[0], delay_buf_len, dtype=dtype, device=device)
|
| 1225 |
+
|
| 1226 |
+
mod_buf_len = int(sample_rate / mod_speed + 0.5)
|
| 1227 |
+
|
| 1228 |
+
if sinusoidal:
|
| 1229 |
+
wave_type = "SINE"
|
| 1230 |
+
else:
|
| 1231 |
+
wave_type = "TRIANGLE"
|
| 1232 |
+
|
| 1233 |
+
mod_buf = _generate_wave_table(
|
| 1234 |
+
wave_type=wave_type,
|
| 1235 |
+
data_type="INT",
|
| 1236 |
+
table_size=mod_buf_len,
|
| 1237 |
+
min=1.0,
|
| 1238 |
+
max=float(delay_buf_len),
|
| 1239 |
+
phase=math.pi / 2,
|
| 1240 |
+
device=device,
|
| 1241 |
+
)
|
| 1242 |
+
|
| 1243 |
+
delay_pos = 0
|
| 1244 |
+
mod_pos = 0
|
| 1245 |
+
|
| 1246 |
+
output_waveform_pre_gain_list = []
|
| 1247 |
+
waveform = waveform * gain_in
|
| 1248 |
+
delay_buf = delay_buf * decay
|
| 1249 |
+
waveform_list = [waveform[:, i] for i in range(waveform.size(1))]
|
| 1250 |
+
delay_buf_list = [delay_buf[:, i] for i in range(delay_buf.size(1))]
|
| 1251 |
+
mod_buf_list = [mod_buf[i] for i in range(mod_buf.size(0))]
|
| 1252 |
+
|
| 1253 |
+
for i in range(waveform.shape[-1]):
|
| 1254 |
+
idx = int((delay_pos + mod_buf_list[mod_pos]) % delay_buf_len)
|
| 1255 |
+
mod_pos = (mod_pos + 1) % mod_buf_len
|
| 1256 |
+
delay_pos = (delay_pos + 1) % delay_buf_len
|
| 1257 |
+
temp = (waveform_list[i]) + (delay_buf_list[idx])
|
| 1258 |
+
delay_buf_list[delay_pos] = temp * decay
|
| 1259 |
+
output_waveform_pre_gain_list.append(temp)
|
| 1260 |
+
|
| 1261 |
+
output_waveform = torch.stack(output_waveform_pre_gain_list, dim=1).to(dtype=dtype, device=device)
|
| 1262 |
+
output_waveform.mul_(gain_out)
|
| 1263 |
+
|
| 1264 |
+
return output_waveform.clamp(min=-1, max=1).view(actual_shape)
|
| 1265 |
+
|
| 1266 |
+
|
| 1267 |
+
def riaa_biquad(waveform: Tensor, sample_rate: int) -> Tensor:
|
| 1268 |
+
r"""Apply RIAA vinyl playback equalization. Similar to SoX implementation.
|
| 1269 |
+
|
| 1270 |
+
.. devices:: CPU CUDA
|
| 1271 |
+
|
| 1272 |
+
.. properties:: Autograd TorchScript
|
| 1273 |
+
|
| 1274 |
+
Args:
|
| 1275 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 1276 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz).
|
| 1277 |
+
Allowed sample rates in Hz : ``44100``,``48000``,``88200``,``96000``
|
| 1278 |
+
|
| 1279 |
+
Returns:
|
| 1280 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 1281 |
+
|
| 1282 |
+
Reference:
|
| 1283 |
+
- http://sox.sourceforge.net/sox.html
|
| 1284 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 1285 |
+
"""
|
| 1286 |
+
|
| 1287 |
+
if sample_rate == 44100:
|
| 1288 |
+
zeros = [-0.2014898, 0.9233820]
|
| 1289 |
+
poles = [0.7083149, 0.9924091]
|
| 1290 |
+
|
| 1291 |
+
elif sample_rate == 48000:
|
| 1292 |
+
zeros = [-0.1766069, 0.9321590]
|
| 1293 |
+
poles = [0.7396325, 0.9931330]
|
| 1294 |
+
|
| 1295 |
+
elif sample_rate == 88200:
|
| 1296 |
+
zeros = [-0.1168735, 0.9648312]
|
| 1297 |
+
poles = [0.8590646, 0.9964002]
|
| 1298 |
+
|
| 1299 |
+
elif sample_rate == 96000:
|
| 1300 |
+
zeros = [-0.1141486, 0.9676817]
|
| 1301 |
+
poles = [0.8699137, 0.9966946]
|
| 1302 |
+
|
| 1303 |
+
else:
|
| 1304 |
+
raise ValueError("Sample rate must be 44.1k, 48k, 88.2k, or 96k")
|
| 1305 |
+
|
| 1306 |
+
# polynomial coefficients with roots zeros[0] and zeros[1]
|
| 1307 |
+
b0 = 1.0
|
| 1308 |
+
b1 = -(zeros[0] + zeros[1])
|
| 1309 |
+
b2 = zeros[0] * zeros[1]
|
| 1310 |
+
|
| 1311 |
+
# polynomial coefficients with roots poles[0] and poles[1]
|
| 1312 |
+
a0 = 1.0
|
| 1313 |
+
a1 = -(poles[0] + poles[1])
|
| 1314 |
+
a2 = poles[0] * poles[1]
|
| 1315 |
+
|
| 1316 |
+
# Normalize to 0dB at 1kHz
|
| 1317 |
+
y = 2 * math.pi * 1000 / sample_rate
|
| 1318 |
+
b_re = b0 + b1 * math.cos(-y) + b2 * math.cos(-2 * y)
|
| 1319 |
+
a_re = a0 + a1 * math.cos(-y) + a2 * math.cos(-2 * y)
|
| 1320 |
+
b_im = b1 * math.sin(-y) + b2 * math.sin(-2 * y)
|
| 1321 |
+
a_im = a1 * math.sin(-y) + a2 * math.sin(-2 * y)
|
| 1322 |
+
g = 1 / math.sqrt((b_re**2 + b_im**2) / (a_re**2 + a_im**2))
|
| 1323 |
+
|
| 1324 |
+
b0 *= g
|
| 1325 |
+
b1 *= g
|
| 1326 |
+
b2 *= g
|
| 1327 |
+
|
| 1328 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 1329 |
+
|
| 1330 |
+
|
| 1331 |
+
def treble_biquad(
|
| 1332 |
+
waveform: Tensor,
|
| 1333 |
+
sample_rate: int,
|
| 1334 |
+
gain: float,
|
| 1335 |
+
central_freq: float = 3000,
|
| 1336 |
+
Q: float = 0.707,
|
| 1337 |
+
) -> Tensor:
|
| 1338 |
+
r"""Design a treble tone-control effect. Similar to SoX implementation.
|
| 1339 |
+
|
| 1340 |
+
.. devices:: CPU CUDA
|
| 1341 |
+
|
| 1342 |
+
.. properties:: Autograd TorchScript
|
| 1343 |
+
|
| 1344 |
+
Args:
|
| 1345 |
+
waveform (Tensor): audio waveform of dimension of `(..., time)`
|
| 1346 |
+
sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz)
|
| 1347 |
+
gain (float or torch.Tensor): desired gain at the boost (or attenuation) in dB.
|
| 1348 |
+
central_freq (float or torch.Tensor, optional): central frequency (in Hz). (Default: ``3000``)
|
| 1349 |
+
Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``).
|
| 1350 |
+
|
| 1351 |
+
Returns:
|
| 1352 |
+
Tensor: Waveform of dimension of `(..., time)`
|
| 1353 |
+
|
| 1354 |
+
Reference:
|
| 1355 |
+
- http://sox.sourceforge.net/sox.html
|
| 1356 |
+
- https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF
|
| 1357 |
+
"""
|
| 1358 |
+
dtype = waveform.dtype
|
| 1359 |
+
device = waveform.device
|
| 1360 |
+
central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device)
|
| 1361 |
+
Q = torch.as_tensor(Q, dtype=dtype, device=device)
|
| 1362 |
+
gain = torch.as_tensor(gain, dtype=dtype, device=device)
|
| 1363 |
+
|
| 1364 |
+
w0 = 2 * math.pi * central_freq / sample_rate
|
| 1365 |
+
alpha = torch.sin(w0) / 2 / Q
|
| 1366 |
+
A = torch.exp(gain / 40 * math.log(10))
|
| 1367 |
+
|
| 1368 |
+
temp1 = 2 * torch.sqrt(A) * alpha
|
| 1369 |
+
temp2 = (A - 1) * torch.cos(w0)
|
| 1370 |
+
temp3 = (A + 1) * torch.cos(w0)
|
| 1371 |
+
|
| 1372 |
+
b0 = A * ((A + 1) + temp2 + temp1)
|
| 1373 |
+
b1 = -2 * A * ((A - 1) + temp3)
|
| 1374 |
+
b2 = A * ((A + 1) + temp2 - temp1)
|
| 1375 |
+
a0 = (A + 1) - temp2 + temp1
|
| 1376 |
+
a1 = 2 * ((A - 1) - temp3)
|
| 1377 |
+
a2 = (A + 1) - temp2 - temp1
|
| 1378 |
+
|
| 1379 |
+
return biquad(waveform, b0, b1, b2, a0, a1, a2)
|
| 1380 |
+
|
| 1381 |
+
|
| 1382 |
+
def _measure(
|
| 1383 |
+
measure_len_ws: int,
|
| 1384 |
+
samples: Tensor,
|
| 1385 |
+
spectrum: Tensor,
|
| 1386 |
+
noise_spectrum: Tensor,
|
| 1387 |
+
spectrum_window: Tensor,
|
| 1388 |
+
spectrum_start: int,
|
| 1389 |
+
spectrum_end: int,
|
| 1390 |
+
cepstrum_window: Tensor,
|
| 1391 |
+
cepstrum_start: int,
|
| 1392 |
+
cepstrum_end: int,
|
| 1393 |
+
noise_reduction_amount: float,
|
| 1394 |
+
measure_smooth_time_mult: float,
|
| 1395 |
+
noise_up_time_mult: Tensor,
|
| 1396 |
+
noise_down_time_mult: Tensor,
|
| 1397 |
+
boot_count: int,
|
| 1398 |
+
) -> float:
|
| 1399 |
+
device = samples.device
|
| 1400 |
+
|
| 1401 |
+
if spectrum.size(-1) != noise_spectrum.size(-1):
|
| 1402 |
+
raise ValueError(
|
| 1403 |
+
"Expected spectrum size to match noise spectrum size in final dimension."
|
| 1404 |
+
f"Found: spectrum size: {spectrum.size()}, noise_spectrum size: {noise_spectrum.size()}"
|
| 1405 |
+
)
|
| 1406 |
+
|
| 1407 |
+
dft_len_ws = spectrum.size()[-1]
|
| 1408 |
+
|
| 1409 |
+
dftBuf = torch.zeros(dft_len_ws, device=device)
|
| 1410 |
+
|
| 1411 |
+
dftBuf[:measure_len_ws] = samples * spectrum_window[:measure_len_ws]
|
| 1412 |
+
|
| 1413 |
+
# lsx_safe_rdft((int)p->dft_len_ws, 1, c->dftBuf);
|
| 1414 |
+
_dftBuf = torch.fft.rfft(dftBuf)
|
| 1415 |
+
|
| 1416 |
+
mult: float = boot_count / (1.0 + boot_count) if boot_count >= 0 else measure_smooth_time_mult
|
| 1417 |
+
|
| 1418 |
+
_d = _dftBuf[spectrum_start:spectrum_end].abs()
|
| 1419 |
+
spectrum[spectrum_start:spectrum_end].mul_(mult).add_(_d * (1 - mult))
|
| 1420 |
+
_d = spectrum[spectrum_start:spectrum_end] ** 2
|
| 1421 |
+
|
| 1422 |
+
_zeros = torch.zeros(spectrum_end - spectrum_start, device=device)
|
| 1423 |
+
_mult = (
|
| 1424 |
+
_zeros
|
| 1425 |
+
if boot_count >= 0
|
| 1426 |
+
else torch.where(
|
| 1427 |
+
_d > noise_spectrum[spectrum_start:spectrum_end],
|
| 1428 |
+
noise_up_time_mult, # if
|
| 1429 |
+
noise_down_time_mult, # else,
|
| 1430 |
+
)
|
| 1431 |
+
)
|
| 1432 |
+
|
| 1433 |
+
noise_spectrum[spectrum_start:spectrum_end].mul_(_mult).add_(_d * (1 - _mult))
|
| 1434 |
+
_d = torch.sqrt(
|
| 1435 |
+
torch.max(
|
| 1436 |
+
_zeros,
|
| 1437 |
+
_d - noise_reduction_amount * noise_spectrum[spectrum_start:spectrum_end],
|
| 1438 |
+
),
|
| 1439 |
+
)
|
| 1440 |
+
|
| 1441 |
+
_cepstrum_Buf: Tensor = torch.zeros(dft_len_ws >> 1, device=device)
|
| 1442 |
+
_cepstrum_Buf[spectrum_start:spectrum_end] = _d * cepstrum_window
|
| 1443 |
+
_cepstrum_Buf[spectrum_end : dft_len_ws >> 1].zero_()
|
| 1444 |
+
|
| 1445 |
+
# lsx_safe_rdft((int)p->dft_len_ws >> 1, 1, c->dftBuf);
|
| 1446 |
+
_cepstrum_Buf = torch.fft.rfft(_cepstrum_Buf)
|
| 1447 |
+
|
| 1448 |
+
result: float = float(torch.sum(_cepstrum_Buf[cepstrum_start:cepstrum_end].abs().pow(2)))
|
| 1449 |
+
result = math.log(result / (cepstrum_end - cepstrum_start)) if result > 0 else -math.inf
|
| 1450 |
+
return max(0, 21 + result)
|
| 1451 |
+
|
| 1452 |
+
|
| 1453 |
+
def vad(
|
| 1454 |
+
waveform: Tensor,
|
| 1455 |
+
sample_rate: int,
|
| 1456 |
+
trigger_level: float = 7.0,
|
| 1457 |
+
trigger_time: float = 0.25,
|
| 1458 |
+
search_time: float = 1.0,
|
| 1459 |
+
allowed_gap: float = 0.25,
|
| 1460 |
+
pre_trigger_time: float = 0.0,
|
| 1461 |
+
# Fine-tuning parameters
|
| 1462 |
+
boot_time: float = 0.35,
|
| 1463 |
+
noise_up_time: float = 0.1,
|
| 1464 |
+
noise_down_time: float = 0.01,
|
| 1465 |
+
noise_reduction_amount: float = 1.35,
|
| 1466 |
+
measure_freq: float = 20.0,
|
| 1467 |
+
measure_duration: Optional[float] = None,
|
| 1468 |
+
measure_smooth_time: float = 0.4,
|
| 1469 |
+
hp_filter_freq: float = 50.0,
|
| 1470 |
+
lp_filter_freq: float = 6000.0,
|
| 1471 |
+
hp_lifter_freq: float = 150.0,
|
| 1472 |
+
lp_lifter_freq: float = 2000.0,
|
| 1473 |
+
) -> Tensor:
|
| 1474 |
+
r"""Voice Activity Detector. Similar to SoX implementation.
|
| 1475 |
+
|
| 1476 |
+
.. devices:: CPU CUDA
|
| 1477 |
+
|
| 1478 |
+
.. properties:: TorchScript
|
| 1479 |
+
|
| 1480 |
+
Attempts to trim silence and quiet background sounds from the ends of recordings of speech.
|
| 1481 |
+
The algorithm currently uses a simple cepstral power measurement to detect voice,
|
| 1482 |
+
so may be fooled by other things, especially music.
|
| 1483 |
+
|
| 1484 |
+
The effect can trim only from the front of the audio,
|
| 1485 |
+
so in order to trim from the back, the reverse effect must also be used.
|
| 1486 |
+
|
| 1487 |
+
Args:
|
| 1488 |
+
waveform (Tensor): Tensor of audio of dimension `(channels, time)` or `(time)`
|
| 1489 |
+
Tensor of shape `(channels, time)` is treated as a multi-channel recording
|
| 1490 |
+
of the same event and the resulting output will be trimmed to the earliest
|
| 1491 |
+
voice activity in any channel.
|
| 1492 |
+
sample_rate (int): Sample rate of audio signal.
|
| 1493 |
+
trigger_level (float, optional): The measurement level used to trigger activity detection.
|
| 1494 |
+
This may need to be cahnged depending on the noise level, signal level,
|
| 1495 |
+
and other characteristics of the input audio. (Default: 7.0)
|
| 1496 |
+
trigger_time (float, optional): The time constant (in seconds)
|
| 1497 |
+
used to help ignore short bursts of sound. (Default: 0.25)
|
| 1498 |
+
search_time (float, optional): The amount of audio (in seconds)
|
| 1499 |
+
to search for quieter/shorter bursts of audio to include prior
|
| 1500 |
+
to the detected trigger point. (Default: 1.0)
|
| 1501 |
+
allowed_gap (float, optional): The allowed gap (in seconds) between
|
| 1502 |
+
quieter/shorter bursts of audio to include prior
|
| 1503 |
+
to the detected trigger point. (Default: 0.25)
|
| 1504 |
+
pre_trigger_time (float, optional): The amount of audio (in seconds) to preserve
|
| 1505 |
+
before the trigger point and any found quieter/shorter bursts. (Default: 0.0)
|
| 1506 |
+
boot_time (float, optional) The algorithm (internally) uses adaptive noise
|
| 1507 |
+
estimation/reduction in order to detect the start of the wanted audio.
|
| 1508 |
+
This option sets the time for the initial noise estimate. (Default: 0.35)
|
| 1509 |
+
noise_up_time (float, optional) Time constant used by the adaptive noise estimator
|
| 1510 |
+
for when the noise level is increasing. (Default: 0.1)
|
| 1511 |
+
noise_down_time (float, optional) Time constant used by the adaptive noise estimator
|
| 1512 |
+
for when the noise level is decreasing. (Default: 0.01)
|
| 1513 |
+
noise_reduction_amount (float, optional) Amount of noise reduction to use in
|
| 1514 |
+
the detection algorithm (e.g. 0, 0.5, ...). (Default: 1.35)
|
| 1515 |
+
measure_freq (float, optional) Frequency of the algorithm's
|
| 1516 |
+
processing/measurements. (Default: 20.0)
|
| 1517 |
+
measure_duration: (float, optional) Measurement duration.
|
| 1518 |
+
(Default: Twice the measurement period; i.e. with overlap.)
|
| 1519 |
+
measure_smooth_time (float, optional) Time constant used to smooth
|
| 1520 |
+
spectral measurements. (Default: 0.4)
|
| 1521 |
+
hp_filter_freq (float, optional) "Brick-wall" frequency of high-pass filter applied
|
| 1522 |
+
at the input to the detector algorithm. (Default: 50.0)
|
| 1523 |
+
lp_filter_freq (float, optional) "Brick-wall" frequency of low-pass filter applied
|
| 1524 |
+
at the input to the detector algorithm. (Default: 6000.0)
|
| 1525 |
+
hp_lifter_freq (float, optional) "Brick-wall" frequency of high-pass lifter used
|
| 1526 |
+
in the detector algorithm. (Default: 150.0)
|
| 1527 |
+
lp_lifter_freq (float, optional) "Brick-wall" frequency of low-pass lifter used
|
| 1528 |
+
in the detector algorithm. (Default: 2000.0)
|
| 1529 |
+
|
| 1530 |
+
Returns:
|
| 1531 |
+
Tensor: Tensor of audio of dimension `(..., time)`.
|
| 1532 |
+
|
| 1533 |
+
Reference:
|
| 1534 |
+
- http://sox.sourceforge.net/sox.html
|
| 1535 |
+
"""
|
| 1536 |
+
device = waveform.device
|
| 1537 |
+
|
| 1538 |
+
if waveform.ndim > 2:
|
| 1539 |
+
warnings.warn(
|
| 1540 |
+
"Expected input tensor dimension of 1 for single channel"
|
| 1541 |
+
f" or 2 for multi-channel. Got {waveform.ndim} instead. "
|
| 1542 |
+
"Batch semantics is not supported. "
|
| 1543 |
+
"Please refer to https://github.com/pytorch/audio/issues/1348"
|
| 1544 |
+
" and https://github.com/pytorch/audio/issues/1468."
|
| 1545 |
+
)
|
| 1546 |
+
|
| 1547 |
+
measure_duration: float = 2.0 / measure_freq if measure_duration is None else measure_duration
|
| 1548 |
+
|
| 1549 |
+
measure_len_ws = int(sample_rate * measure_duration + 0.5)
|
| 1550 |
+
measure_len_ns = measure_len_ws
|
| 1551 |
+
# for (dft_len_ws = 16; dft_len_ws < measure_len_ws; dft_len_ws <<= 1);
|
| 1552 |
+
dft_len_ws = 16
|
| 1553 |
+
while dft_len_ws < measure_len_ws:
|
| 1554 |
+
dft_len_ws *= 2
|
| 1555 |
+
|
| 1556 |
+
measure_period_ns = int(sample_rate / measure_freq + 0.5)
|
| 1557 |
+
measures_len = math.ceil(search_time * measure_freq)
|
| 1558 |
+
search_pre_trigger_len_ns = measures_len * measure_period_ns
|
| 1559 |
+
gap_len = int(allowed_gap * measure_freq + 0.5)
|
| 1560 |
+
|
| 1561 |
+
fixed_pre_trigger_len_ns = int(pre_trigger_time * sample_rate + 0.5)
|
| 1562 |
+
samplesLen_ns = fixed_pre_trigger_len_ns + search_pre_trigger_len_ns + measure_len_ns
|
| 1563 |
+
|
| 1564 |
+
spectrum_window = torch.zeros(measure_len_ws, device=device)
|
| 1565 |
+
for i in range(measure_len_ws):
|
| 1566 |
+
# sox.h:741 define SOX_SAMPLE_MIN (sox_sample_t)SOX_INT_MIN(32)
|
| 1567 |
+
spectrum_window[i] = 2.0 / math.sqrt(float(measure_len_ws))
|
| 1568 |
+
# lsx_apply_hann(spectrum_window, (int)measure_len_ws);
|
| 1569 |
+
spectrum_window *= torch.hann_window(measure_len_ws, device=device, dtype=torch.float)
|
| 1570 |
+
|
| 1571 |
+
spectrum_start: int = int(hp_filter_freq / sample_rate * dft_len_ws + 0.5)
|
| 1572 |
+
spectrum_start: int = max(spectrum_start, 1)
|
| 1573 |
+
spectrum_end: int = int(lp_filter_freq / sample_rate * dft_len_ws + 0.5)
|
| 1574 |
+
spectrum_end: int = min(spectrum_end, dft_len_ws // 2)
|
| 1575 |
+
|
| 1576 |
+
cepstrum_window = torch.zeros(spectrum_end - spectrum_start, device=device)
|
| 1577 |
+
for i in range(spectrum_end - spectrum_start):
|
| 1578 |
+
cepstrum_window[i] = 2.0 / math.sqrt(float(spectrum_end) - spectrum_start)
|
| 1579 |
+
# lsx_apply_hann(cepstrum_window,(int)(spectrum_end - spectrum_start));
|
| 1580 |
+
cepstrum_window *= torch.hann_window(spectrum_end - spectrum_start, device=device, dtype=torch.float)
|
| 1581 |
+
|
| 1582 |
+
cepstrum_start = math.ceil(sample_rate * 0.5 / lp_lifter_freq)
|
| 1583 |
+
cepstrum_end = math.floor(sample_rate * 0.5 / hp_lifter_freq)
|
| 1584 |
+
cepstrum_end = min(cepstrum_end, dft_len_ws // 4)
|
| 1585 |
+
|
| 1586 |
+
if cepstrum_end <= cepstrum_start:
|
| 1587 |
+
raise ValueError(
|
| 1588 |
+
"Expected cepstrum_start to be smaller than cepstrum_end."
|
| 1589 |
+
f"Found: cepstrum_start: {cepstrum_start}, cepstrum_end: {cepstrum_end}."
|
| 1590 |
+
)
|
| 1591 |
+
|
| 1592 |
+
noise_up_time_mult = torch.tensor(math.exp(-1.0 / (noise_up_time * measure_freq)), device=device)
|
| 1593 |
+
noise_down_time_mult = torch.tensor(math.exp(-1.0 / (noise_down_time * measure_freq)), device=device)
|
| 1594 |
+
measure_smooth_time_mult = math.exp(-1.0 / (measure_smooth_time * measure_freq))
|
| 1595 |
+
trigger_meas_time_mult = math.exp(-1.0 / (trigger_time * measure_freq))
|
| 1596 |
+
|
| 1597 |
+
boot_count_max = int(boot_time * measure_freq - 0.5)
|
| 1598 |
+
boot_count = measures_index = flushedLen_ns = 0
|
| 1599 |
+
|
| 1600 |
+
# pack batch
|
| 1601 |
+
shape = waveform.size()
|
| 1602 |
+
waveform = waveform.view(-1, shape[-1])
|
| 1603 |
+
|
| 1604 |
+
n_channels, ilen = waveform.size()
|
| 1605 |
+
|
| 1606 |
+
mean_meas = torch.zeros(n_channels, device=device)
|
| 1607 |
+
spectrum = torch.zeros(n_channels, dft_len_ws, device=device)
|
| 1608 |
+
noise_spectrum = torch.zeros(n_channels, dft_len_ws, device=device)
|
| 1609 |
+
measures = torch.zeros(n_channels, measures_len, device=device)
|
| 1610 |
+
|
| 1611 |
+
has_triggered: bool = False
|
| 1612 |
+
num_measures_to_flush: int = 0
|
| 1613 |
+
|
| 1614 |
+
pos = 0
|
| 1615 |
+
for pos in range(measure_len_ns, ilen, measure_period_ns):
|
| 1616 |
+
for i in range(n_channels):
|
| 1617 |
+
meas: float = _measure(
|
| 1618 |
+
measure_len_ws=measure_len_ws,
|
| 1619 |
+
samples=waveform[i, pos - measure_len_ws : pos],
|
| 1620 |
+
spectrum=spectrum[i],
|
| 1621 |
+
noise_spectrum=noise_spectrum[i],
|
| 1622 |
+
spectrum_window=spectrum_window,
|
| 1623 |
+
spectrum_start=spectrum_start,
|
| 1624 |
+
spectrum_end=spectrum_end,
|
| 1625 |
+
cepstrum_window=cepstrum_window,
|
| 1626 |
+
cepstrum_start=cepstrum_start,
|
| 1627 |
+
cepstrum_end=cepstrum_end,
|
| 1628 |
+
noise_reduction_amount=noise_reduction_amount,
|
| 1629 |
+
measure_smooth_time_mult=measure_smooth_time_mult,
|
| 1630 |
+
noise_up_time_mult=noise_up_time_mult,
|
| 1631 |
+
noise_down_time_mult=noise_down_time_mult,
|
| 1632 |
+
boot_count=boot_count,
|
| 1633 |
+
)
|
| 1634 |
+
measures[i, measures_index] = meas
|
| 1635 |
+
mean_meas[i] = mean_meas[i] * trigger_meas_time_mult + meas * (1.0 - trigger_meas_time_mult)
|
| 1636 |
+
|
| 1637 |
+
has_triggered = has_triggered or (mean_meas[i] >= trigger_level)
|
| 1638 |
+
if has_triggered:
|
| 1639 |
+
n: int = measures_len
|
| 1640 |
+
k: int = measures_index
|
| 1641 |
+
jTrigger: int = n
|
| 1642 |
+
jZero: int = n
|
| 1643 |
+
j: int = 0
|
| 1644 |
+
|
| 1645 |
+
for j in range(n):
|
| 1646 |
+
if (measures[i, k] >= trigger_level) and (j <= jTrigger + gap_len):
|
| 1647 |
+
jZero = jTrigger = j
|
| 1648 |
+
elif (measures[i, k] == 0) and (jTrigger >= jZero):
|
| 1649 |
+
jZero = j
|
| 1650 |
+
k = (k + n - 1) % n
|
| 1651 |
+
j = min(j, jZero)
|
| 1652 |
+
# num_measures_to_flush = range_limit(j, num_measures_to_flush, n);
|
| 1653 |
+
num_measures_to_flush = min(max(num_measures_to_flush, j), n)
|
| 1654 |
+
# end if has_triggered
|
| 1655 |
+
# end for channel
|
| 1656 |
+
measures_index += 1
|
| 1657 |
+
measures_index = measures_index % measures_len
|
| 1658 |
+
if boot_count >= 0:
|
| 1659 |
+
boot_count = -1 if boot_count == boot_count_max else boot_count + 1
|
| 1660 |
+
|
| 1661 |
+
if has_triggered:
|
| 1662 |
+
flushedLen_ns = (measures_len - num_measures_to_flush) * measure_period_ns
|
| 1663 |
+
break
|
| 1664 |
+
# end for window
|
| 1665 |
+
if not has_triggered:
|
| 1666 |
+
return waveform[..., :0].view(shape[:-1] + torch.Size([0]))
|
| 1667 |
+
|
| 1668 |
+
res = waveform[:, max(pos - samplesLen_ns + flushedLen_ns, 0) :]
|
| 1669 |
+
# unpack batch
|
| 1670 |
+
return res.view(shape[:-1] + res.shape[-1:])
|
lib/python3.12/site-packages/torchaudio/functional/functional.py
ADDED
|
@@ -0,0 +1,2535 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import tempfile
|
| 5 |
+
import warnings
|
| 6 |
+
from collections.abc import Sequence
|
| 7 |
+
from typing import List, Optional, Tuple, Union
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torchaudio
|
| 11 |
+
from torch import Tensor
|
| 12 |
+
from torchaudio._internal.module_utils import deprecated
|
| 13 |
+
|
| 14 |
+
from .filtering import highpass_biquad, treble_biquad
|
| 15 |
+
|
| 16 |
+
__all__ = [
|
| 17 |
+
"spectrogram",
|
| 18 |
+
"inverse_spectrogram",
|
| 19 |
+
"griffinlim",
|
| 20 |
+
"amplitude_to_DB",
|
| 21 |
+
"DB_to_amplitude",
|
| 22 |
+
"compute_deltas",
|
| 23 |
+
"melscale_fbanks",
|
| 24 |
+
"linear_fbanks",
|
| 25 |
+
"create_dct",
|
| 26 |
+
"compute_deltas",
|
| 27 |
+
"detect_pitch_frequency",
|
| 28 |
+
"DB_to_amplitude",
|
| 29 |
+
"mu_law_encoding",
|
| 30 |
+
"mu_law_decoding",
|
| 31 |
+
"phase_vocoder",
|
| 32 |
+
"mask_along_axis",
|
| 33 |
+
"mask_along_axis_iid",
|
| 34 |
+
"sliding_window_cmn",
|
| 35 |
+
"spectral_centroid",
|
| 36 |
+
"apply_codec",
|
| 37 |
+
"resample",
|
| 38 |
+
"edit_distance",
|
| 39 |
+
"loudness",
|
| 40 |
+
"pitch_shift",
|
| 41 |
+
"rnnt_loss",
|
| 42 |
+
"psd",
|
| 43 |
+
"mvdr_weights_souden",
|
| 44 |
+
"mvdr_weights_rtf",
|
| 45 |
+
"rtf_evd",
|
| 46 |
+
"rtf_power",
|
| 47 |
+
"apply_beamforming",
|
| 48 |
+
"fftconvolve",
|
| 49 |
+
"convolve",
|
| 50 |
+
"add_noise",
|
| 51 |
+
"speed",
|
| 52 |
+
"preemphasis",
|
| 53 |
+
"deemphasis",
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def spectrogram(
|
| 58 |
+
waveform: Tensor,
|
| 59 |
+
pad: int,
|
| 60 |
+
window: Tensor,
|
| 61 |
+
n_fft: int,
|
| 62 |
+
hop_length: int,
|
| 63 |
+
win_length: int,
|
| 64 |
+
power: Optional[float],
|
| 65 |
+
normalized: Union[bool, str],
|
| 66 |
+
center: bool = True,
|
| 67 |
+
pad_mode: str = "reflect",
|
| 68 |
+
onesided: bool = True,
|
| 69 |
+
return_complex: Optional[bool] = None,
|
| 70 |
+
) -> Tensor:
|
| 71 |
+
r"""Create a spectrogram or a batch of spectrograms from a raw audio signal.
|
| 72 |
+
The spectrogram can be either magnitude-only or complex.
|
| 73 |
+
|
| 74 |
+
.. devices:: CPU CUDA
|
| 75 |
+
|
| 76 |
+
.. properties:: Autograd TorchScript
|
| 77 |
+
|
| 78 |
+
Args:
|
| 79 |
+
waveform (Tensor): Tensor of audio of dimension `(..., time)`
|
| 80 |
+
pad (int): Two sided padding of signal
|
| 81 |
+
window (Tensor): Window tensor that is applied/multiplied to each frame/window
|
| 82 |
+
n_fft (int): Size of FFT
|
| 83 |
+
hop_length (int): Length of hop between STFT windows
|
| 84 |
+
win_length (int): Window size
|
| 85 |
+
power (float or None): Exponent for the magnitude spectrogram,
|
| 86 |
+
(must be > 0) e.g., 1 for magnitude, 2 for power, etc.
|
| 87 |
+
If None, then the complex spectrum is returned instead.
|
| 88 |
+
normalized (bool or str): Whether to normalize by magnitude after stft. If input is str, choices are
|
| 89 |
+
``"window"`` and ``"frame_length"``, if specific normalization type is desirable. ``True`` maps to
|
| 90 |
+
``"window"``. When normalized on ``"window"``, waveform is normalized upon the window's L2 energy. If
|
| 91 |
+
normalized on ``"frame_length"``, waveform is normalized by dividing by
|
| 92 |
+
:math:`(\text{frame\_length})^{0.5}`.
|
| 93 |
+
center (bool, optional): whether to pad :attr:`waveform` on both sides so
|
| 94 |
+
that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`.
|
| 95 |
+
Default: ``True``
|
| 96 |
+
pad_mode (string, optional): controls the padding method used when
|
| 97 |
+
:attr:`center` is ``True``. Default: ``"reflect"``
|
| 98 |
+
onesided (bool, optional): controls whether to return half of results to
|
| 99 |
+
avoid redundancy. Default: ``True``
|
| 100 |
+
return_complex (bool, optional):
|
| 101 |
+
Deprecated and not used.
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
Tensor: Dimension `(..., freq, time)`, freq is
|
| 105 |
+
``n_fft // 2 + 1`` and ``n_fft`` is the number of
|
| 106 |
+
Fourier bins, and time is the number of window hops (n_frame).
|
| 107 |
+
"""
|
| 108 |
+
if return_complex is not None:
|
| 109 |
+
warnings.warn(
|
| 110 |
+
"`return_complex` argument is now deprecated and is not effective."
|
| 111 |
+
"`torchaudio.functional.spectrogram(power=None)` always returns a tensor with "
|
| 112 |
+
"complex dtype. Please remove the argument in the function call."
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
if pad > 0:
|
| 116 |
+
# TODO add "with torch.no_grad():" back when JIT supports it
|
| 117 |
+
waveform = torch.nn.functional.pad(waveform, (pad, pad), "constant")
|
| 118 |
+
|
| 119 |
+
frame_length_norm, window_norm = _get_spec_norms(normalized)
|
| 120 |
+
|
| 121 |
+
# pack batch
|
| 122 |
+
shape = waveform.size()
|
| 123 |
+
waveform = waveform.reshape(-1, shape[-1])
|
| 124 |
+
|
| 125 |
+
# default values are consistent with librosa.core.spectrum._spectrogram
|
| 126 |
+
spec_f = torch.stft(
|
| 127 |
+
input=waveform,
|
| 128 |
+
n_fft=n_fft,
|
| 129 |
+
hop_length=hop_length,
|
| 130 |
+
win_length=win_length,
|
| 131 |
+
window=window,
|
| 132 |
+
center=center,
|
| 133 |
+
pad_mode=pad_mode,
|
| 134 |
+
normalized=frame_length_norm,
|
| 135 |
+
onesided=onesided,
|
| 136 |
+
return_complex=True,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# unpack batch
|
| 140 |
+
spec_f = spec_f.reshape(shape[:-1] + spec_f.shape[-2:])
|
| 141 |
+
|
| 142 |
+
if window_norm:
|
| 143 |
+
spec_f /= window.pow(2.0).sum().sqrt()
|
| 144 |
+
if power is not None:
|
| 145 |
+
if power == 1.0:
|
| 146 |
+
return spec_f.abs()
|
| 147 |
+
return spec_f.abs().pow(power)
|
| 148 |
+
return spec_f
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def inverse_spectrogram(
|
| 152 |
+
spectrogram: Tensor,
|
| 153 |
+
length: Optional[int],
|
| 154 |
+
pad: int,
|
| 155 |
+
window: Tensor,
|
| 156 |
+
n_fft: int,
|
| 157 |
+
hop_length: int,
|
| 158 |
+
win_length: int,
|
| 159 |
+
normalized: Union[bool, str],
|
| 160 |
+
center: bool = True,
|
| 161 |
+
pad_mode: str = "reflect",
|
| 162 |
+
onesided: bool = True,
|
| 163 |
+
) -> Tensor:
|
| 164 |
+
r"""Create an inverse spectrogram or a batch of inverse spectrograms from the provided
|
| 165 |
+
complex-valued spectrogram.
|
| 166 |
+
|
| 167 |
+
.. devices:: CPU CUDA
|
| 168 |
+
|
| 169 |
+
.. properties:: Autograd TorchScript
|
| 170 |
+
|
| 171 |
+
Args:
|
| 172 |
+
spectrogram (Tensor): Complex tensor of audio of dimension (..., freq, time).
|
| 173 |
+
length (int or None): The output length of the waveform.
|
| 174 |
+
pad (int): Two sided padding of signal. It is only effective when ``length`` is provided.
|
| 175 |
+
window (Tensor): Window tensor that is applied/multiplied to each frame/window
|
| 176 |
+
n_fft (int): Size of FFT
|
| 177 |
+
hop_length (int): Length of hop between STFT windows
|
| 178 |
+
win_length (int): Window size
|
| 179 |
+
normalized (bool or str): Whether the stft output was normalized by magnitude. If input is str, choices are
|
| 180 |
+
``"window"`` and ``"frame_length"``, dependent on normalization mode. ``True`` maps to
|
| 181 |
+
``"window"``.
|
| 182 |
+
center (bool, optional): whether the waveform was padded on both sides so
|
| 183 |
+
that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`.
|
| 184 |
+
Default: ``True``
|
| 185 |
+
pad_mode (string, optional): controls the padding method used when
|
| 186 |
+
:attr:`center` is ``True``. This parameter is provided for compatibility with the
|
| 187 |
+
spectrogram function and is not used. Default: ``"reflect"``
|
| 188 |
+
onesided (bool, optional): controls whether spectrogram was done in onesided mode.
|
| 189 |
+
Default: ``True``
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
Tensor: Dimension `(..., time)`. Least squares estimation of the original signal.
|
| 193 |
+
"""
|
| 194 |
+
|
| 195 |
+
frame_length_norm, window_norm = _get_spec_norms(normalized)
|
| 196 |
+
|
| 197 |
+
if not spectrogram.is_complex():
|
| 198 |
+
raise ValueError("Expected `spectrogram` to be complex dtype.")
|
| 199 |
+
|
| 200 |
+
if window_norm:
|
| 201 |
+
spectrogram = spectrogram * window.pow(2.0).sum().sqrt()
|
| 202 |
+
|
| 203 |
+
# pack batch
|
| 204 |
+
shape = spectrogram.size()
|
| 205 |
+
spectrogram = spectrogram.reshape(-1, shape[-2], shape[-1])
|
| 206 |
+
|
| 207 |
+
# default values are consistent with librosa.core.spectrum._spectrogram
|
| 208 |
+
waveform = torch.istft(
|
| 209 |
+
input=spectrogram,
|
| 210 |
+
n_fft=n_fft,
|
| 211 |
+
hop_length=hop_length,
|
| 212 |
+
win_length=win_length,
|
| 213 |
+
window=window,
|
| 214 |
+
center=center,
|
| 215 |
+
normalized=frame_length_norm,
|
| 216 |
+
onesided=onesided,
|
| 217 |
+
length=length + 2 * pad if length is not None else None,
|
| 218 |
+
return_complex=False,
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
if length is not None and pad > 0:
|
| 222 |
+
# remove padding from front and back
|
| 223 |
+
waveform = waveform[:, pad:-pad]
|
| 224 |
+
|
| 225 |
+
# unpack batch
|
| 226 |
+
waveform = waveform.reshape(shape[:-2] + waveform.shape[-1:])
|
| 227 |
+
|
| 228 |
+
return waveform
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def _get_spec_norms(normalized: Union[str, bool]):
|
| 232 |
+
frame_length_norm, window_norm = False, False
|
| 233 |
+
if torch.jit.isinstance(normalized, str):
|
| 234 |
+
if normalized not in ["frame_length", "window"]:
|
| 235 |
+
raise ValueError("Invalid normalized parameter: {}".format(normalized))
|
| 236 |
+
if normalized == "frame_length":
|
| 237 |
+
frame_length_norm = True
|
| 238 |
+
elif normalized == "window":
|
| 239 |
+
window_norm = True
|
| 240 |
+
elif torch.jit.isinstance(normalized, bool):
|
| 241 |
+
if normalized:
|
| 242 |
+
window_norm = True
|
| 243 |
+
else:
|
| 244 |
+
raise TypeError("Input type not supported")
|
| 245 |
+
return frame_length_norm, window_norm
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def _get_complex_dtype(real_dtype: torch.dtype):
|
| 249 |
+
if real_dtype == torch.double:
|
| 250 |
+
return torch.cdouble
|
| 251 |
+
if real_dtype == torch.float:
|
| 252 |
+
return torch.cfloat
|
| 253 |
+
if real_dtype == torch.half:
|
| 254 |
+
return torch.complex32
|
| 255 |
+
raise ValueError(f"Unexpected dtype {real_dtype}")
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def griffinlim(
|
| 259 |
+
specgram: Tensor,
|
| 260 |
+
window: Tensor,
|
| 261 |
+
n_fft: int,
|
| 262 |
+
hop_length: int,
|
| 263 |
+
win_length: int,
|
| 264 |
+
power: float,
|
| 265 |
+
n_iter: int,
|
| 266 |
+
momentum: float,
|
| 267 |
+
length: Optional[int],
|
| 268 |
+
rand_init: bool,
|
| 269 |
+
) -> Tensor:
|
| 270 |
+
r"""Compute waveform from a linear scale magnitude spectrogram using the Griffin-Lim transformation.
|
| 271 |
+
|
| 272 |
+
.. devices:: CPU CUDA
|
| 273 |
+
|
| 274 |
+
.. properties:: Autograd TorchScript
|
| 275 |
+
|
| 276 |
+
Implementation ported from
|
| 277 |
+
*librosa* :cite:`brian_mcfee-proc-scipy-2015`, *A fast Griffin-Lim algorithm* :cite:`6701851`
|
| 278 |
+
and *Signal estimation from modified short-time Fourier transform* :cite:`1172092`.
|
| 279 |
+
|
| 280 |
+
Args:
|
| 281 |
+
specgram (Tensor): A magnitude-only STFT spectrogram of dimension `(..., freq, frames)`
|
| 282 |
+
where freq is ``n_fft // 2 + 1``.
|
| 283 |
+
window (Tensor): Window tensor that is applied/multiplied to each frame/window
|
| 284 |
+
n_fft (int): Size of FFT, creates ``n_fft // 2 + 1`` bins
|
| 285 |
+
hop_length (int): Length of hop between STFT windows. (
|
| 286 |
+
Default: ``win_length // 2``)
|
| 287 |
+
win_length (int): Window size. (Default: ``n_fft``)
|
| 288 |
+
power (float): Exponent for the magnitude spectrogram,
|
| 289 |
+
(must be > 0) e.g., 1 for magnitude, 2 for power, etc.
|
| 290 |
+
n_iter (int): Number of iteration for phase recovery process.
|
| 291 |
+
momentum (float): The momentum parameter for fast Griffin-Lim.
|
| 292 |
+
Setting this to 0 recovers the original Griffin-Lim method.
|
| 293 |
+
Values near 1 can lead to faster convergence, but above 1 may not converge.
|
| 294 |
+
length (int or None): Array length of the expected output.
|
| 295 |
+
rand_init (bool): Initializes phase randomly if True, to zero otherwise.
|
| 296 |
+
|
| 297 |
+
Returns:
|
| 298 |
+
Tensor: waveform of `(..., time)`, where time equals the ``length`` parameter if given.
|
| 299 |
+
"""
|
| 300 |
+
if not 0 <= momentum < 1:
|
| 301 |
+
raise ValueError("momentum must be in range [0, 1). Found: {}".format(momentum))
|
| 302 |
+
|
| 303 |
+
momentum = momentum / (1 + momentum)
|
| 304 |
+
|
| 305 |
+
# pack batch
|
| 306 |
+
shape = specgram.size()
|
| 307 |
+
specgram = specgram.reshape([-1] + list(shape[-2:]))
|
| 308 |
+
|
| 309 |
+
specgram = specgram.pow(1 / power)
|
| 310 |
+
|
| 311 |
+
# initialize the phase
|
| 312 |
+
if rand_init:
|
| 313 |
+
angles = torch.rand(specgram.size(), dtype=_get_complex_dtype(specgram.dtype), device=specgram.device)
|
| 314 |
+
else:
|
| 315 |
+
angles = torch.full(specgram.size(), 1, dtype=_get_complex_dtype(specgram.dtype), device=specgram.device)
|
| 316 |
+
|
| 317 |
+
# And initialize the previous iterate to 0
|
| 318 |
+
tprev = torch.tensor(0.0, dtype=specgram.dtype, device=specgram.device)
|
| 319 |
+
for _ in range(n_iter):
|
| 320 |
+
# Invert with our current estimate of the phases
|
| 321 |
+
inverse = torch.istft(
|
| 322 |
+
specgram * angles, n_fft=n_fft, hop_length=hop_length, win_length=win_length, window=window, length=length
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
# Rebuild the spectrogram
|
| 326 |
+
rebuilt = torch.stft(
|
| 327 |
+
input=inverse,
|
| 328 |
+
n_fft=n_fft,
|
| 329 |
+
hop_length=hop_length,
|
| 330 |
+
win_length=win_length,
|
| 331 |
+
window=window,
|
| 332 |
+
center=True,
|
| 333 |
+
pad_mode="reflect",
|
| 334 |
+
normalized=False,
|
| 335 |
+
onesided=True,
|
| 336 |
+
return_complex=True,
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
# Update our phase estimates
|
| 340 |
+
angles = rebuilt
|
| 341 |
+
if momentum:
|
| 342 |
+
angles = angles - tprev.mul_(momentum)
|
| 343 |
+
angles = angles.div(angles.abs().add(1e-16))
|
| 344 |
+
|
| 345 |
+
# Store the previous iterate
|
| 346 |
+
tprev = rebuilt
|
| 347 |
+
|
| 348 |
+
# Return the final phase estimates
|
| 349 |
+
waveform = torch.istft(
|
| 350 |
+
specgram * angles, n_fft=n_fft, hop_length=hop_length, win_length=win_length, window=window, length=length
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
# unpack batch
|
| 354 |
+
waveform = waveform.reshape(shape[:-2] + waveform.shape[-1:])
|
| 355 |
+
|
| 356 |
+
return waveform
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def amplitude_to_DB(
|
| 360 |
+
x: Tensor, multiplier: float, amin: float, db_multiplier: float, top_db: Optional[float] = None
|
| 361 |
+
) -> Tensor:
|
| 362 |
+
r"""Turn a spectrogram from the power/amplitude scale to the decibel scale.
|
| 363 |
+
|
| 364 |
+
.. devices:: CPU CUDA
|
| 365 |
+
|
| 366 |
+
.. properties:: Autograd TorchScript
|
| 367 |
+
|
| 368 |
+
The output of each tensor in a batch depends on the maximum value of that tensor,
|
| 369 |
+
and so may return different values for an audio clip split into snippets vs. a full clip.
|
| 370 |
+
|
| 371 |
+
Args:
|
| 372 |
+
|
| 373 |
+
x (Tensor): Input spectrogram(s) before being converted to decibel scale.
|
| 374 |
+
The expected shapes are ``(freq, time)``, ``(channel, freq, time)`` or
|
| 375 |
+
``(..., batch, channel, freq, time)``.
|
| 376 |
+
|
| 377 |
+
.. note::
|
| 378 |
+
|
| 379 |
+
When ``top_db`` is specified, cut-off values are computed for each audio
|
| 380 |
+
in the batch. Therefore if the input shape is 4D (or larger), different
|
| 381 |
+
cut-off values are used for audio data in the batch.
|
| 382 |
+
If the input shape is 2D or 3D, a single cutoff value is used.
|
| 383 |
+
|
| 384 |
+
multiplier (float): Use 10. for power and 20. for amplitude
|
| 385 |
+
amin (float): Number to clamp ``x``
|
| 386 |
+
db_multiplier (float): Log10(max(reference value and amin))
|
| 387 |
+
top_db (float or None, optional): Minimum negative cut-off in decibels. A reasonable number
|
| 388 |
+
is 80. (Default: ``None``)
|
| 389 |
+
|
| 390 |
+
Returns:
|
| 391 |
+
Tensor: Output tensor in decibel scale
|
| 392 |
+
"""
|
| 393 |
+
x_db = multiplier * torch.log10(torch.clamp(x, min=amin))
|
| 394 |
+
x_db -= multiplier * db_multiplier
|
| 395 |
+
|
| 396 |
+
if top_db is not None:
|
| 397 |
+
# Expand batch
|
| 398 |
+
shape = x_db.size()
|
| 399 |
+
packed_channels = shape[-3] if x_db.dim() > 2 else 1
|
| 400 |
+
x_db = x_db.reshape(-1, packed_channels, shape[-2], shape[-1])
|
| 401 |
+
|
| 402 |
+
x_db = torch.max(x_db, (x_db.amax(dim=(-3, -2, -1)) - top_db).view(-1, 1, 1, 1))
|
| 403 |
+
|
| 404 |
+
# Repack batch
|
| 405 |
+
x_db = x_db.reshape(shape)
|
| 406 |
+
|
| 407 |
+
return x_db
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
def DB_to_amplitude(x: Tensor, ref: float, power: float) -> Tensor:
|
| 411 |
+
r"""Turn a tensor from the decibel scale to the power/amplitude scale.
|
| 412 |
+
|
| 413 |
+
.. devices:: CPU CUDA
|
| 414 |
+
|
| 415 |
+
.. properties:: TorchScript
|
| 416 |
+
|
| 417 |
+
Args:
|
| 418 |
+
x (Tensor): Input tensor before being converted to power/amplitude scale.
|
| 419 |
+
ref (float): Reference which the output will be scaled by.
|
| 420 |
+
power (float): If power equals 1, will compute DB to power. If 0.5, will compute DB to amplitude.
|
| 421 |
+
|
| 422 |
+
Returns:
|
| 423 |
+
Tensor: Output tensor in power/amplitude scale.
|
| 424 |
+
"""
|
| 425 |
+
return ref * torch.pow(torch.pow(10.0, 0.1 * x), power)
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def _hz_to_mel(freq: float, mel_scale: str = "htk") -> float:
|
| 429 |
+
r"""Convert Hz to Mels.
|
| 430 |
+
|
| 431 |
+
Args:
|
| 432 |
+
freqs (float): Frequencies in Hz
|
| 433 |
+
mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``)
|
| 434 |
+
|
| 435 |
+
Returns:
|
| 436 |
+
mels (float): Frequency in Mels
|
| 437 |
+
"""
|
| 438 |
+
|
| 439 |
+
if mel_scale not in ["slaney", "htk"]:
|
| 440 |
+
raise ValueError('mel_scale should be one of "htk" or "slaney".')
|
| 441 |
+
|
| 442 |
+
if mel_scale == "htk":
|
| 443 |
+
return 2595.0 * math.log10(1.0 + (freq / 700.0))
|
| 444 |
+
|
| 445 |
+
# Fill in the linear part
|
| 446 |
+
f_min = 0.0
|
| 447 |
+
f_sp = 200.0 / 3
|
| 448 |
+
|
| 449 |
+
mels = (freq - f_min) / f_sp
|
| 450 |
+
|
| 451 |
+
# Fill in the log-scale part
|
| 452 |
+
min_log_hz = 1000.0
|
| 453 |
+
min_log_mel = (min_log_hz - f_min) / f_sp
|
| 454 |
+
logstep = math.log(6.4) / 27.0
|
| 455 |
+
|
| 456 |
+
if freq >= min_log_hz:
|
| 457 |
+
mels = min_log_mel + math.log(freq / min_log_hz) / logstep
|
| 458 |
+
|
| 459 |
+
return mels
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def _mel_to_hz(mels: Tensor, mel_scale: str = "htk") -> Tensor:
|
| 463 |
+
"""Convert mel bin numbers to frequencies.
|
| 464 |
+
|
| 465 |
+
Args:
|
| 466 |
+
mels (Tensor): Mel frequencies
|
| 467 |
+
mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``)
|
| 468 |
+
|
| 469 |
+
Returns:
|
| 470 |
+
freqs (Tensor): Mels converted in Hz
|
| 471 |
+
"""
|
| 472 |
+
|
| 473 |
+
if mel_scale not in ["slaney", "htk"]:
|
| 474 |
+
raise ValueError('mel_scale should be one of "htk" or "slaney".')
|
| 475 |
+
|
| 476 |
+
if mel_scale == "htk":
|
| 477 |
+
return 700.0 * (10.0 ** (mels / 2595.0) - 1.0)
|
| 478 |
+
|
| 479 |
+
# Fill in the linear scale
|
| 480 |
+
f_min = 0.0
|
| 481 |
+
f_sp = 200.0 / 3
|
| 482 |
+
freqs = f_min + f_sp * mels
|
| 483 |
+
|
| 484 |
+
# And now the nonlinear scale
|
| 485 |
+
min_log_hz = 1000.0
|
| 486 |
+
min_log_mel = (min_log_hz - f_min) / f_sp
|
| 487 |
+
logstep = math.log(6.4) / 27.0
|
| 488 |
+
|
| 489 |
+
log_t = mels >= min_log_mel
|
| 490 |
+
freqs[log_t] = min_log_hz * torch.exp(logstep * (mels[log_t] - min_log_mel))
|
| 491 |
+
|
| 492 |
+
return freqs
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
def _create_triangular_filterbank(
|
| 496 |
+
all_freqs: Tensor,
|
| 497 |
+
f_pts: Tensor,
|
| 498 |
+
) -> Tensor:
|
| 499 |
+
"""Create a triangular filter bank.
|
| 500 |
+
|
| 501 |
+
Args:
|
| 502 |
+
all_freqs (Tensor): STFT freq points of size (`n_freqs`).
|
| 503 |
+
f_pts (Tensor): Filter mid points of size (`n_filter`).
|
| 504 |
+
|
| 505 |
+
Returns:
|
| 506 |
+
fb (Tensor): The filter bank of size (`n_freqs`, `n_filter`).
|
| 507 |
+
"""
|
| 508 |
+
# Adopted from Librosa
|
| 509 |
+
# calculate the difference between each filter mid point and each stft freq point in hertz
|
| 510 |
+
f_diff = f_pts[1:] - f_pts[:-1] # (n_filter + 1)
|
| 511 |
+
slopes = f_pts.unsqueeze(0) - all_freqs.unsqueeze(1) # (n_freqs, n_filter + 2)
|
| 512 |
+
# create overlapping triangles
|
| 513 |
+
zero = torch.zeros(1)
|
| 514 |
+
down_slopes = (-1.0 * slopes[:, :-2]) / f_diff[:-1] # (n_freqs, n_filter)
|
| 515 |
+
up_slopes = slopes[:, 2:] / f_diff[1:] # (n_freqs, n_filter)
|
| 516 |
+
fb = torch.max(zero, torch.min(down_slopes, up_slopes))
|
| 517 |
+
|
| 518 |
+
return fb
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def melscale_fbanks(
|
| 522 |
+
n_freqs: int,
|
| 523 |
+
f_min: float,
|
| 524 |
+
f_max: float,
|
| 525 |
+
n_mels: int,
|
| 526 |
+
sample_rate: int,
|
| 527 |
+
norm: Optional[str] = None,
|
| 528 |
+
mel_scale: str = "htk",
|
| 529 |
+
) -> Tensor:
|
| 530 |
+
r"""Create a frequency bin conversion matrix.
|
| 531 |
+
|
| 532 |
+
.. devices:: CPU
|
| 533 |
+
|
| 534 |
+
.. properties:: TorchScript
|
| 535 |
+
|
| 536 |
+
Note:
|
| 537 |
+
For the sake of the numerical compatibility with librosa, not all the coefficients
|
| 538 |
+
in the resulting filter bank has magnitude of 1.
|
| 539 |
+
|
| 540 |
+
.. image:: https://download.pytorch.org/torchaudio/doc-assets/mel_fbanks.png
|
| 541 |
+
:alt: Visualization of generated filter bank
|
| 542 |
+
|
| 543 |
+
Args:
|
| 544 |
+
n_freqs (int): Number of frequencies to highlight/apply
|
| 545 |
+
f_min (float): Minimum frequency (Hz)
|
| 546 |
+
f_max (float): Maximum frequency (Hz)
|
| 547 |
+
n_mels (int): Number of mel filterbanks
|
| 548 |
+
sample_rate (int): Sample rate of the audio waveform
|
| 549 |
+
norm (str or None, optional): If "slaney", divide the triangular mel weights by the width of the mel band
|
| 550 |
+
(area normalization). (Default: ``None``)
|
| 551 |
+
mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``)
|
| 552 |
+
|
| 553 |
+
Returns:
|
| 554 |
+
Tensor: Triangular filter banks (fb matrix) of size (``n_freqs``, ``n_mels``)
|
| 555 |
+
meaning number of frequencies to highlight/apply to x the number of filterbanks.
|
| 556 |
+
Each column is a filterbank so that assuming there is a matrix A of
|
| 557 |
+
size (..., ``n_freqs``), the applied result would be
|
| 558 |
+
``A @ melscale_fbanks(A.size(-1), ...)``.
|
| 559 |
+
|
| 560 |
+
"""
|
| 561 |
+
|
| 562 |
+
if norm is not None and norm != "slaney":
|
| 563 |
+
raise ValueError('norm must be one of None or "slaney"')
|
| 564 |
+
|
| 565 |
+
# freq bins
|
| 566 |
+
all_freqs = torch.linspace(0, sample_rate // 2, n_freqs)
|
| 567 |
+
|
| 568 |
+
# calculate mel freq bins
|
| 569 |
+
m_min = _hz_to_mel(f_min, mel_scale=mel_scale)
|
| 570 |
+
m_max = _hz_to_mel(f_max, mel_scale=mel_scale)
|
| 571 |
+
|
| 572 |
+
m_pts = torch.linspace(m_min, m_max, n_mels + 2)
|
| 573 |
+
f_pts = _mel_to_hz(m_pts, mel_scale=mel_scale)
|
| 574 |
+
|
| 575 |
+
# create filterbank
|
| 576 |
+
fb = _create_triangular_filterbank(all_freqs, f_pts)
|
| 577 |
+
|
| 578 |
+
if norm is not None and norm == "slaney":
|
| 579 |
+
# Slaney-style mel is scaled to be approx constant energy per channel
|
| 580 |
+
enorm = 2.0 / (f_pts[2 : n_mels + 2] - f_pts[:n_mels])
|
| 581 |
+
fb *= enorm.unsqueeze(0)
|
| 582 |
+
|
| 583 |
+
if (fb.max(dim=0).values == 0.0).any():
|
| 584 |
+
warnings.warn(
|
| 585 |
+
"At least one mel filterbank has all zero values. "
|
| 586 |
+
f"The value for `n_mels` ({n_mels}) may be set too high. "
|
| 587 |
+
f"Or, the value for `n_freqs` ({n_freqs}) may be set too low."
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
return fb
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
def linear_fbanks(
|
| 594 |
+
n_freqs: int,
|
| 595 |
+
f_min: float,
|
| 596 |
+
f_max: float,
|
| 597 |
+
n_filter: int,
|
| 598 |
+
sample_rate: int,
|
| 599 |
+
) -> Tensor:
|
| 600 |
+
r"""Creates a linear triangular filterbank.
|
| 601 |
+
|
| 602 |
+
.. devices:: CPU
|
| 603 |
+
|
| 604 |
+
.. properties:: TorchScript
|
| 605 |
+
|
| 606 |
+
Note:
|
| 607 |
+
For the sake of the numerical compatibility with librosa, not all the coefficients
|
| 608 |
+
in the resulting filter bank has magnitude of 1.
|
| 609 |
+
|
| 610 |
+
.. image:: https://download.pytorch.org/torchaudio/doc-assets/lin_fbanks.png
|
| 611 |
+
:alt: Visualization of generated filter bank
|
| 612 |
+
|
| 613 |
+
Args:
|
| 614 |
+
n_freqs (int): Number of frequencies to highlight/apply
|
| 615 |
+
f_min (float): Minimum frequency (Hz)
|
| 616 |
+
f_max (float): Maximum frequency (Hz)
|
| 617 |
+
n_filter (int): Number of (linear) triangular filter
|
| 618 |
+
sample_rate (int): Sample rate of the audio waveform
|
| 619 |
+
|
| 620 |
+
Returns:
|
| 621 |
+
Tensor: Triangular filter banks (fb matrix) of size (``n_freqs``, ``n_filter``)
|
| 622 |
+
meaning number of frequencies to highlight/apply to x the number of filterbanks.
|
| 623 |
+
Each column is a filterbank so that assuming there is a matrix A of
|
| 624 |
+
size (..., ``n_freqs``), the applied result would be
|
| 625 |
+
``A * linear_fbanks(A.size(-1), ...)``.
|
| 626 |
+
"""
|
| 627 |
+
# freq bins
|
| 628 |
+
all_freqs = torch.linspace(0, sample_rate // 2, n_freqs)
|
| 629 |
+
|
| 630 |
+
# filter mid-points
|
| 631 |
+
f_pts = torch.linspace(f_min, f_max, n_filter + 2)
|
| 632 |
+
|
| 633 |
+
# create filterbank
|
| 634 |
+
fb = _create_triangular_filterbank(all_freqs, f_pts)
|
| 635 |
+
|
| 636 |
+
return fb
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
def create_dct(n_mfcc: int, n_mels: int, norm: Optional[str]) -> Tensor:
|
| 640 |
+
r"""Create a DCT transformation matrix with shape (``n_mels``, ``n_mfcc``),
|
| 641 |
+
normalized depending on norm.
|
| 642 |
+
|
| 643 |
+
.. devices:: CPU
|
| 644 |
+
|
| 645 |
+
.. properties:: TorchScript
|
| 646 |
+
|
| 647 |
+
Args:
|
| 648 |
+
n_mfcc (int): Number of mfc coefficients to retain
|
| 649 |
+
n_mels (int): Number of mel filterbanks
|
| 650 |
+
norm (str or None): Norm to use (either "ortho" or None)
|
| 651 |
+
|
| 652 |
+
Returns:
|
| 653 |
+
Tensor: The transformation matrix, to be right-multiplied to
|
| 654 |
+
row-wise data of size (``n_mels``, ``n_mfcc``).
|
| 655 |
+
"""
|
| 656 |
+
|
| 657 |
+
if norm is not None and norm != "ortho":
|
| 658 |
+
raise ValueError('norm must be either "ortho" or None')
|
| 659 |
+
|
| 660 |
+
# http://en.wikipedia.org/wiki/Discrete_cosine_transform#DCT-II
|
| 661 |
+
n = torch.arange(float(n_mels))
|
| 662 |
+
k = torch.arange(float(n_mfcc)).unsqueeze(1)
|
| 663 |
+
dct = torch.cos(math.pi / float(n_mels) * (n + 0.5) * k) # size (n_mfcc, n_mels)
|
| 664 |
+
|
| 665 |
+
if norm is None:
|
| 666 |
+
dct *= 2.0
|
| 667 |
+
else:
|
| 668 |
+
dct[0] *= 1.0 / math.sqrt(2.0)
|
| 669 |
+
dct *= math.sqrt(2.0 / float(n_mels))
|
| 670 |
+
return dct.t()
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def mu_law_encoding(x: Tensor, quantization_channels: int) -> Tensor:
|
| 674 |
+
r"""Encode signal based on mu-law companding.
|
| 675 |
+
|
| 676 |
+
.. devices:: CPU CUDA
|
| 677 |
+
|
| 678 |
+
.. properties:: TorchScript
|
| 679 |
+
|
| 680 |
+
For more info see the
|
| 681 |
+
`Wikipedia Entry <https://en.wikipedia.org/wiki/%CE%9C-law_algorithm>`_
|
| 682 |
+
|
| 683 |
+
This algorithm expects the signal has been scaled to between -1 and 1 and
|
| 684 |
+
returns a signal encoded with values from 0 to quantization_channels - 1.
|
| 685 |
+
|
| 686 |
+
Args:
|
| 687 |
+
x (Tensor): Input tensor
|
| 688 |
+
quantization_channels (int): Number of channels
|
| 689 |
+
|
| 690 |
+
Returns:
|
| 691 |
+
Tensor: Input after mu-law encoding
|
| 692 |
+
"""
|
| 693 |
+
mu = quantization_channels - 1.0
|
| 694 |
+
if not x.is_floating_point():
|
| 695 |
+
warnings.warn(
|
| 696 |
+
"The input Tensor must be of floating type. \
|
| 697 |
+
This will be an error in the v0.12 release."
|
| 698 |
+
)
|
| 699 |
+
x = x.to(torch.float)
|
| 700 |
+
mu = torch.tensor(mu, dtype=x.dtype)
|
| 701 |
+
x_mu = torch.sign(x) * torch.log1p(mu * torch.abs(x)) / torch.log1p(mu)
|
| 702 |
+
x_mu = ((x_mu + 1) / 2 * mu + 0.5).to(torch.int64)
|
| 703 |
+
return x_mu
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
def mu_law_decoding(x_mu: Tensor, quantization_channels: int) -> Tensor:
|
| 707 |
+
r"""Decode mu-law encoded signal.
|
| 708 |
+
|
| 709 |
+
.. devices:: CPU CUDA
|
| 710 |
+
|
| 711 |
+
.. properties:: TorchScript
|
| 712 |
+
|
| 713 |
+
For more info see the
|
| 714 |
+
`Wikipedia Entry <https://en.wikipedia.org/wiki/%CE%9C-law_algorithm>`_
|
| 715 |
+
|
| 716 |
+
This expects an input with values between 0 and quantization_channels - 1
|
| 717 |
+
and returns a signal scaled between -1 and 1.
|
| 718 |
+
|
| 719 |
+
Args:
|
| 720 |
+
x_mu (Tensor): Input tensor
|
| 721 |
+
quantization_channels (int): Number of channels
|
| 722 |
+
|
| 723 |
+
Returns:
|
| 724 |
+
Tensor: Input after mu-law decoding
|
| 725 |
+
"""
|
| 726 |
+
mu = quantization_channels - 1.0
|
| 727 |
+
if not x_mu.is_floating_point():
|
| 728 |
+
x_mu = x_mu.to(torch.float)
|
| 729 |
+
mu = torch.tensor(mu, dtype=x_mu.dtype)
|
| 730 |
+
x = ((x_mu) / mu) * 2 - 1.0
|
| 731 |
+
x = torch.sign(x) * (torch.exp(torch.abs(x) * torch.log1p(mu)) - 1.0) / mu
|
| 732 |
+
return x
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
def phase_vocoder(complex_specgrams: Tensor, rate: float, phase_advance: Tensor) -> Tensor:
|
| 736 |
+
r"""Given a STFT tensor, speed up in time without modifying pitch by a factor of ``rate``.
|
| 737 |
+
|
| 738 |
+
.. devices:: CPU CUDA
|
| 739 |
+
|
| 740 |
+
.. properties:: Autograd TorchScript
|
| 741 |
+
|
| 742 |
+
Args:
|
| 743 |
+
complex_specgrams (Tensor):
|
| 744 |
+
A tensor of dimension `(..., freq, num_frame)` with complex dtype.
|
| 745 |
+
rate (float): Speed-up factor
|
| 746 |
+
phase_advance (Tensor): Expected phase advance in each bin. Dimension of `(freq, 1)`
|
| 747 |
+
|
| 748 |
+
Returns:
|
| 749 |
+
Tensor:
|
| 750 |
+
Stretched spectrogram. The resulting tensor is of the same dtype as the input
|
| 751 |
+
spectrogram, but the number of frames is changed to ``ceil(num_frame / rate)``.
|
| 752 |
+
|
| 753 |
+
Example
|
| 754 |
+
>>> freq, hop_length = 1025, 512
|
| 755 |
+
>>> # (channel, freq, time)
|
| 756 |
+
>>> complex_specgrams = torch.randn(2, freq, 300, dtype=torch.cfloat)
|
| 757 |
+
>>> rate = 1.3 # Speed up by 30%
|
| 758 |
+
>>> phase_advance = torch.linspace(
|
| 759 |
+
>>> 0, math.pi * hop_length, freq)[..., None]
|
| 760 |
+
>>> x = phase_vocoder(complex_specgrams, rate, phase_advance)
|
| 761 |
+
>>> x.shape # with 231 == ceil(300 / 1.3)
|
| 762 |
+
torch.Size([2, 1025, 231])
|
| 763 |
+
"""
|
| 764 |
+
if rate == 1.0:
|
| 765 |
+
return complex_specgrams
|
| 766 |
+
|
| 767 |
+
# pack batch
|
| 768 |
+
shape = complex_specgrams.size()
|
| 769 |
+
complex_specgrams = complex_specgrams.reshape([-1] + list(shape[-2:]))
|
| 770 |
+
|
| 771 |
+
# Figures out the corresponding real dtype, i.e. complex128 -> float64, complex64 -> float32
|
| 772 |
+
# Note torch.real is a view so it does not incur any memory copy.
|
| 773 |
+
real_dtype = torch.real(complex_specgrams).dtype
|
| 774 |
+
time_steps = torch.arange(0, complex_specgrams.size(-1), rate, device=complex_specgrams.device, dtype=real_dtype)
|
| 775 |
+
|
| 776 |
+
alphas = time_steps % 1.0
|
| 777 |
+
phase_0 = complex_specgrams[..., :1].angle()
|
| 778 |
+
|
| 779 |
+
# Time Padding
|
| 780 |
+
complex_specgrams = torch.nn.functional.pad(complex_specgrams, [0, 2])
|
| 781 |
+
|
| 782 |
+
# (new_bins, freq, 2)
|
| 783 |
+
complex_specgrams_0 = complex_specgrams.index_select(-1, time_steps.long())
|
| 784 |
+
complex_specgrams_1 = complex_specgrams.index_select(-1, (time_steps + 1).long())
|
| 785 |
+
|
| 786 |
+
angle_0 = complex_specgrams_0.angle()
|
| 787 |
+
angle_1 = complex_specgrams_1.angle()
|
| 788 |
+
|
| 789 |
+
norm_0 = complex_specgrams_0.abs()
|
| 790 |
+
norm_1 = complex_specgrams_1.abs()
|
| 791 |
+
|
| 792 |
+
phase = angle_1 - angle_0 - phase_advance
|
| 793 |
+
phase = phase - 2 * math.pi * torch.round(phase / (2 * math.pi))
|
| 794 |
+
|
| 795 |
+
# Compute Phase Accum
|
| 796 |
+
phase = phase + phase_advance
|
| 797 |
+
phase = torch.cat([phase_0, phase[..., :-1]], dim=-1)
|
| 798 |
+
phase_acc = torch.cumsum(phase, -1)
|
| 799 |
+
|
| 800 |
+
mag = alphas * norm_1 + (1 - alphas) * norm_0
|
| 801 |
+
|
| 802 |
+
complex_specgrams_stretch = torch.polar(mag, phase_acc)
|
| 803 |
+
|
| 804 |
+
# unpack batch
|
| 805 |
+
complex_specgrams_stretch = complex_specgrams_stretch.reshape(shape[:-2] + complex_specgrams_stretch.shape[1:])
|
| 806 |
+
return complex_specgrams_stretch
|
| 807 |
+
|
| 808 |
+
|
| 809 |
+
def _get_mask_param(mask_param: int, p: float, axis_length: int) -> int:
|
| 810 |
+
if p == 1.0:
|
| 811 |
+
return mask_param
|
| 812 |
+
else:
|
| 813 |
+
return min(mask_param, int(axis_length * p))
|
| 814 |
+
|
| 815 |
+
|
| 816 |
+
def mask_along_axis_iid(
|
| 817 |
+
specgrams: Tensor,
|
| 818 |
+
mask_param: int,
|
| 819 |
+
mask_value: float,
|
| 820 |
+
axis: int,
|
| 821 |
+
p: float = 1.0,
|
| 822 |
+
) -> Tensor:
|
| 823 |
+
r"""Apply a mask along ``axis``.
|
| 824 |
+
|
| 825 |
+
.. devices:: CPU CUDA
|
| 826 |
+
|
| 827 |
+
.. properties:: Autograd TorchScript
|
| 828 |
+
|
| 829 |
+
Mask will be applied from indices ``[v_0, v_0 + v)``,
|
| 830 |
+
where ``v`` is sampled from ``uniform(0, max_v)`` and
|
| 831 |
+
``v_0`` from ``uniform(0, specgrams.size(axis) - v)``,
|
| 832 |
+
with ``max_v = mask_param`` when ``p = 1.0`` and
|
| 833 |
+
``max_v = min(mask_param, floor(specgrams.size(axis) * p))`` otherwise.
|
| 834 |
+
|
| 835 |
+
Args:
|
| 836 |
+
specgrams (Tensor): Real spectrograms `(..., freq, time)`, with at least 3 dimensions.
|
| 837 |
+
mask_param (int): Number of columns to be masked will be uniformly sampled from [0, mask_param]
|
| 838 |
+
mask_value (float): Value to assign to the masked columns
|
| 839 |
+
axis (int): Axis to apply masking on, which should be the one of the last two dimensions.
|
| 840 |
+
p (float, optional): maximum proportion of columns that can be masked. (Default: 1.0)
|
| 841 |
+
|
| 842 |
+
Returns:
|
| 843 |
+
Tensor: Masked spectrograms with the same dimensions as input specgrams Tensor`
|
| 844 |
+
"""
|
| 845 |
+
|
| 846 |
+
dim = specgrams.dim()
|
| 847 |
+
|
| 848 |
+
if dim < 3:
|
| 849 |
+
raise ValueError(f"Spectrogram must have at least three dimensions ({dim} given).")
|
| 850 |
+
|
| 851 |
+
if axis not in [dim - 2, dim - 1]:
|
| 852 |
+
raise ValueError(
|
| 853 |
+
f"Only Frequency and Time masking are supported (axis {dim-2} and axis {dim-1} supported; {axis} given)."
|
| 854 |
+
)
|
| 855 |
+
|
| 856 |
+
if not 0.0 <= p <= 1.0:
|
| 857 |
+
raise ValueError(f"The value of p must be between 0.0 and 1.0 ({p} given).")
|
| 858 |
+
|
| 859 |
+
mask_param = _get_mask_param(mask_param, p, specgrams.shape[axis])
|
| 860 |
+
if mask_param < 1:
|
| 861 |
+
return specgrams
|
| 862 |
+
|
| 863 |
+
device = specgrams.device
|
| 864 |
+
dtype = specgrams.dtype
|
| 865 |
+
|
| 866 |
+
value = torch.rand(specgrams.shape[: (dim - 2)], device=device, dtype=dtype) * mask_param
|
| 867 |
+
min_value = torch.rand(specgrams.shape[: (dim - 2)], device=device, dtype=dtype) * (specgrams.size(axis) - value)
|
| 868 |
+
|
| 869 |
+
# Create broadcastable mask
|
| 870 |
+
mask_start = min_value.long()[..., None, None]
|
| 871 |
+
mask_end = (min_value.long() + value.long())[..., None, None]
|
| 872 |
+
mask = torch.arange(0, specgrams.size(axis), device=device, dtype=dtype)
|
| 873 |
+
|
| 874 |
+
# Per batch example masking
|
| 875 |
+
specgrams = specgrams.transpose(axis, -1)
|
| 876 |
+
specgrams = specgrams.masked_fill((mask >= mask_start) & (mask < mask_end), mask_value)
|
| 877 |
+
specgrams = specgrams.transpose(axis, -1)
|
| 878 |
+
|
| 879 |
+
return specgrams
|
| 880 |
+
|
| 881 |
+
|
| 882 |
+
def mask_along_axis(
|
| 883 |
+
specgram: Tensor,
|
| 884 |
+
mask_param: int,
|
| 885 |
+
mask_value: float,
|
| 886 |
+
axis: int,
|
| 887 |
+
p: float = 1.0,
|
| 888 |
+
) -> Tensor:
|
| 889 |
+
r"""Apply a mask along ``axis``.
|
| 890 |
+
|
| 891 |
+
.. devices:: CPU CUDA
|
| 892 |
+
|
| 893 |
+
.. properties:: Autograd TorchScript
|
| 894 |
+
|
| 895 |
+
Mask will be applied from indices ``[v_0, v_0 + v)``,
|
| 896 |
+
where ``v`` is sampled from ``uniform(0, max_v)`` and
|
| 897 |
+
``v_0`` from ``uniform(0, specgram.size(axis) - v)``, with
|
| 898 |
+
``max_v = mask_param`` when ``p = 1.0`` and
|
| 899 |
+
``max_v = min(mask_param, floor(specgram.size(axis) * p))``
|
| 900 |
+
otherwise.
|
| 901 |
+
All examples will have the same mask interval.
|
| 902 |
+
|
| 903 |
+
Args:
|
| 904 |
+
specgram (Tensor): Real spectrograms `(..., freq, time)`, with at least 2 dimensions.
|
| 905 |
+
mask_param (int): Number of columns to be masked will be uniformly sampled from [0, mask_param]
|
| 906 |
+
mask_value (float): Value to assign to the masked columns
|
| 907 |
+
axis (int): Axis to apply masking on, which should be the one of the last two dimensions.
|
| 908 |
+
p (float, optional): maximum proportion of columns that can be masked. (Default: 1.0)
|
| 909 |
+
|
| 910 |
+
Returns:
|
| 911 |
+
Tensor: Masked spectrograms with the same dimensions as input specgram Tensor
|
| 912 |
+
"""
|
| 913 |
+
dim = specgram.dim()
|
| 914 |
+
|
| 915 |
+
if dim < 2:
|
| 916 |
+
raise ValueError(f"Spectrogram must have at least two dimensions (time and frequency) ({dim} given).")
|
| 917 |
+
|
| 918 |
+
if axis not in [dim - 2, dim - 1]:
|
| 919 |
+
raise ValueError(
|
| 920 |
+
f"Only Frequency and Time masking are supported (axis {dim-2} and axis {dim-1} supported; {axis} given)."
|
| 921 |
+
)
|
| 922 |
+
|
| 923 |
+
if not 0.0 <= p <= 1.0:
|
| 924 |
+
raise ValueError(f"The value of p must be between 0.0 and 1.0 ({p} given).")
|
| 925 |
+
|
| 926 |
+
mask_param = _get_mask_param(mask_param, p, specgram.shape[axis])
|
| 927 |
+
if mask_param < 1:
|
| 928 |
+
return specgram
|
| 929 |
+
|
| 930 |
+
# pack batch
|
| 931 |
+
shape = specgram.size()
|
| 932 |
+
specgram = specgram.reshape([-1] + list(shape[-2:]))
|
| 933 |
+
# After packing, specgram is a 3D tensor, and the axis corresponding to the to-be-masked dimension
|
| 934 |
+
# is now (axis - dim + 3), e.g. a tensor of shape (10, 2, 50, 10, 2) becomes a tensor of shape (1000, 10, 2).
|
| 935 |
+
value = torch.rand(1) * mask_param
|
| 936 |
+
min_value = torch.rand(1) * (specgram.size(axis - dim + 3) - value)
|
| 937 |
+
|
| 938 |
+
mask_start = (min_value.long()).squeeze()
|
| 939 |
+
mask_end = (min_value.long() + value.long()).squeeze()
|
| 940 |
+
mask = torch.arange(0, specgram.shape[axis - dim + 3], device=specgram.device, dtype=specgram.dtype)
|
| 941 |
+
mask = (mask >= mask_start) & (mask < mask_end)
|
| 942 |
+
# unsqueeze the mask if the axis is frequency
|
| 943 |
+
if axis == dim - 2:
|
| 944 |
+
mask = mask.unsqueeze(-1)
|
| 945 |
+
|
| 946 |
+
if mask_end - mask_start >= mask_param:
|
| 947 |
+
raise ValueError("Number of columns to be masked should be less than mask_param")
|
| 948 |
+
|
| 949 |
+
specgram = specgram.masked_fill(mask, mask_value)
|
| 950 |
+
|
| 951 |
+
# unpack batch
|
| 952 |
+
specgram = specgram.reshape(shape[:-2] + specgram.shape[-2:])
|
| 953 |
+
|
| 954 |
+
return specgram
|
| 955 |
+
|
| 956 |
+
|
| 957 |
+
def compute_deltas(specgram: Tensor, win_length: int = 5, mode: str = "replicate") -> Tensor:
|
| 958 |
+
r"""Compute delta coefficients of a tensor, usually a spectrogram:
|
| 959 |
+
|
| 960 |
+
.. devices:: CPU CUDA
|
| 961 |
+
|
| 962 |
+
.. properties:: TorchScript
|
| 963 |
+
|
| 964 |
+
.. math::
|
| 965 |
+
d_t = \frac{\sum_{n=1}^{\text{N}} n (c_{t+n} - c_{t-n})}{2 \sum_{n=1}^{\text{N}} n^2}
|
| 966 |
+
|
| 967 |
+
where :math:`d_t` is the deltas at time :math:`t`,
|
| 968 |
+
:math:`c_t` is the spectrogram coeffcients at time :math:`t`,
|
| 969 |
+
:math:`N` is ``(win_length-1)//2``.
|
| 970 |
+
|
| 971 |
+
Args:
|
| 972 |
+
specgram (Tensor): Tensor of audio of dimension `(..., freq, time)`
|
| 973 |
+
win_length (int, optional): The window length used for computing delta (Default: ``5``)
|
| 974 |
+
mode (str, optional): Mode parameter passed to padding (Default: ``"replicate"``)
|
| 975 |
+
|
| 976 |
+
Returns:
|
| 977 |
+
Tensor: Tensor of deltas of dimension `(..., freq, time)`
|
| 978 |
+
|
| 979 |
+
Example
|
| 980 |
+
>>> specgram = torch.randn(1, 40, 1000)
|
| 981 |
+
>>> delta = compute_deltas(specgram)
|
| 982 |
+
>>> delta2 = compute_deltas(delta)
|
| 983 |
+
"""
|
| 984 |
+
device = specgram.device
|
| 985 |
+
dtype = specgram.dtype
|
| 986 |
+
|
| 987 |
+
# pack batch
|
| 988 |
+
shape = specgram.size()
|
| 989 |
+
specgram = specgram.reshape(1, -1, shape[-1])
|
| 990 |
+
|
| 991 |
+
if win_length < 3:
|
| 992 |
+
raise ValueError(f"Window length should be greater than or equal to 3. Found win_length {win_length}")
|
| 993 |
+
|
| 994 |
+
n = (win_length - 1) // 2
|
| 995 |
+
|
| 996 |
+
# twice sum of integer squared
|
| 997 |
+
denom = n * (n + 1) * (2 * n + 1) / 3
|
| 998 |
+
|
| 999 |
+
specgram = torch.nn.functional.pad(specgram, (n, n), mode=mode)
|
| 1000 |
+
|
| 1001 |
+
kernel = torch.arange(-n, n + 1, 1, device=device, dtype=dtype).repeat(specgram.shape[1], 1, 1)
|
| 1002 |
+
|
| 1003 |
+
output = torch.nn.functional.conv1d(specgram, kernel, groups=specgram.shape[1]) / denom
|
| 1004 |
+
|
| 1005 |
+
# unpack batch
|
| 1006 |
+
output = output.reshape(shape)
|
| 1007 |
+
|
| 1008 |
+
return output
|
| 1009 |
+
|
| 1010 |
+
|
| 1011 |
+
def _compute_nccf(waveform: Tensor, sample_rate: int, frame_time: float, freq_low: int) -> Tensor:
|
| 1012 |
+
r"""
|
| 1013 |
+
Compute Normalized Cross-Correlation Function (NCCF).
|
| 1014 |
+
|
| 1015 |
+
.. math::
|
| 1016 |
+
\phi_i(m) = \frac{\sum_{n=b_i}^{b_i + N-1} w(n) w(m+n)}{\sqrt{E(b_i) E(m+b_i)}},
|
| 1017 |
+
|
| 1018 |
+
where
|
| 1019 |
+
:math:`\phi_i(m)` is the NCCF at frame :math:`i` with lag :math:`m`,
|
| 1020 |
+
:math:`w` is the waveform,
|
| 1021 |
+
:math:`N` is the length of a frame,
|
| 1022 |
+
:math:`b_i` is the beginning of frame :math:`i`,
|
| 1023 |
+
:math:`E(j)` is the energy :math:`\sum_{n=j}^{j+N-1} w^2(n)`.
|
| 1024 |
+
"""
|
| 1025 |
+
|
| 1026 |
+
EPSILON = 10 ** (-9)
|
| 1027 |
+
|
| 1028 |
+
# Number of lags to check
|
| 1029 |
+
lags = int(math.ceil(sample_rate / freq_low))
|
| 1030 |
+
|
| 1031 |
+
frame_size = int(math.ceil(sample_rate * frame_time))
|
| 1032 |
+
|
| 1033 |
+
waveform_length = waveform.size()[-1]
|
| 1034 |
+
num_of_frames = int(math.ceil(waveform_length / frame_size))
|
| 1035 |
+
|
| 1036 |
+
p = lags + num_of_frames * frame_size - waveform_length
|
| 1037 |
+
waveform = torch.nn.functional.pad(waveform, (0, p))
|
| 1038 |
+
|
| 1039 |
+
# Compute lags
|
| 1040 |
+
output_lag = []
|
| 1041 |
+
for lag in range(1, lags + 1):
|
| 1042 |
+
s1 = waveform[..., :-lag].unfold(-1, frame_size, frame_size)[..., :num_of_frames, :]
|
| 1043 |
+
s2 = waveform[..., lag:].unfold(-1, frame_size, frame_size)[..., :num_of_frames, :]
|
| 1044 |
+
|
| 1045 |
+
output_frames = (
|
| 1046 |
+
(s1 * s2).sum(-1)
|
| 1047 |
+
/ (EPSILON + torch.linalg.vector_norm(s1, ord=2, dim=-1)).pow(2)
|
| 1048 |
+
/ (EPSILON + torch.linalg.vector_norm(s2, ord=2, dim=-1)).pow(2)
|
| 1049 |
+
)
|
| 1050 |
+
|
| 1051 |
+
output_lag.append(output_frames.unsqueeze(-1))
|
| 1052 |
+
|
| 1053 |
+
nccf = torch.cat(output_lag, -1)
|
| 1054 |
+
|
| 1055 |
+
return nccf
|
| 1056 |
+
|
| 1057 |
+
|
| 1058 |
+
def _combine_max(a: Tuple[Tensor, Tensor], b: Tuple[Tensor, Tensor], thresh: float = 0.99) -> Tuple[Tensor, Tensor]:
|
| 1059 |
+
"""
|
| 1060 |
+
Take value from first if bigger than a multiplicative factor of the second, elementwise.
|
| 1061 |
+
"""
|
| 1062 |
+
mask = a[0] > thresh * b[0]
|
| 1063 |
+
values = mask * a[0] + ~mask * b[0]
|
| 1064 |
+
indices = mask * a[1] + ~mask * b[1]
|
| 1065 |
+
return values, indices
|
| 1066 |
+
|
| 1067 |
+
|
| 1068 |
+
def _find_max_per_frame(nccf: Tensor, sample_rate: int, freq_high: int) -> Tensor:
|
| 1069 |
+
r"""
|
| 1070 |
+
For each frame, take the highest value of NCCF,
|
| 1071 |
+
apply centered median smoothing, and convert to frequency.
|
| 1072 |
+
|
| 1073 |
+
Note: If the max among all the lags is very close
|
| 1074 |
+
to the first half of lags, then the latter is taken.
|
| 1075 |
+
"""
|
| 1076 |
+
|
| 1077 |
+
lag_min = int(math.ceil(sample_rate / freq_high))
|
| 1078 |
+
|
| 1079 |
+
# Find near enough max that is smallest
|
| 1080 |
+
|
| 1081 |
+
best = torch.max(nccf[..., lag_min:], -1)
|
| 1082 |
+
|
| 1083 |
+
half_size = nccf.shape[-1] // 2
|
| 1084 |
+
half = torch.max(nccf[..., lag_min:half_size], -1)
|
| 1085 |
+
|
| 1086 |
+
best = _combine_max(half, best)
|
| 1087 |
+
indices = best[1]
|
| 1088 |
+
|
| 1089 |
+
# Add back minimal lag
|
| 1090 |
+
indices += lag_min
|
| 1091 |
+
# Add 1 empirical calibration offset
|
| 1092 |
+
indices += 1
|
| 1093 |
+
|
| 1094 |
+
return indices
|
| 1095 |
+
|
| 1096 |
+
|
| 1097 |
+
def _median_smoothing(indices: Tensor, win_length: int) -> Tensor:
|
| 1098 |
+
r"""
|
| 1099 |
+
Apply median smoothing to the 1D tensor over the given window.
|
| 1100 |
+
"""
|
| 1101 |
+
|
| 1102 |
+
# Centered windowed
|
| 1103 |
+
pad_length = (win_length - 1) // 2
|
| 1104 |
+
|
| 1105 |
+
# "replicate" padding in any dimension
|
| 1106 |
+
indices = torch.nn.functional.pad(indices, (pad_length, 0), mode="constant", value=0.0)
|
| 1107 |
+
|
| 1108 |
+
indices[..., :pad_length] = torch.cat(pad_length * [indices[..., pad_length].unsqueeze(-1)], dim=-1)
|
| 1109 |
+
roll = indices.unfold(-1, win_length, 1)
|
| 1110 |
+
|
| 1111 |
+
values, _ = torch.median(roll, -1)
|
| 1112 |
+
return values
|
| 1113 |
+
|
| 1114 |
+
|
| 1115 |
+
def detect_pitch_frequency(
|
| 1116 |
+
waveform: Tensor,
|
| 1117 |
+
sample_rate: int,
|
| 1118 |
+
frame_time: float = 10 ** (-2),
|
| 1119 |
+
win_length: int = 30,
|
| 1120 |
+
freq_low: int = 85,
|
| 1121 |
+
freq_high: int = 3400,
|
| 1122 |
+
) -> Tensor:
|
| 1123 |
+
r"""Detect pitch frequency.
|
| 1124 |
+
|
| 1125 |
+
.. devices:: CPU CUDA
|
| 1126 |
+
|
| 1127 |
+
.. properties:: TorchScript
|
| 1128 |
+
|
| 1129 |
+
It is implemented using normalized cross-correlation function and median smoothing.
|
| 1130 |
+
|
| 1131 |
+
Args:
|
| 1132 |
+
waveform (Tensor): Tensor of audio of dimension `(..., freq, time)`
|
| 1133 |
+
sample_rate (int): The sample rate of the waveform (Hz)
|
| 1134 |
+
frame_time (float, optional): Duration of a frame (Default: ``10 ** (-2)``).
|
| 1135 |
+
win_length (int, optional): The window length for median smoothing (in number of frames) (Default: ``30``).
|
| 1136 |
+
freq_low (int, optional): Lowest frequency that can be detected (Hz) (Default: ``85``).
|
| 1137 |
+
freq_high (int, optional): Highest frequency that can be detected (Hz) (Default: ``3400``).
|
| 1138 |
+
|
| 1139 |
+
Returns:
|
| 1140 |
+
Tensor: Tensor of freq of dimension `(..., frame)`
|
| 1141 |
+
"""
|
| 1142 |
+
# pack batch
|
| 1143 |
+
shape = list(waveform.size())
|
| 1144 |
+
waveform = waveform.reshape([-1] + shape[-1:])
|
| 1145 |
+
|
| 1146 |
+
nccf = _compute_nccf(waveform, sample_rate, frame_time, freq_low)
|
| 1147 |
+
indices = _find_max_per_frame(nccf, sample_rate, freq_high)
|
| 1148 |
+
indices = _median_smoothing(indices, win_length)
|
| 1149 |
+
|
| 1150 |
+
# Convert indices to frequency
|
| 1151 |
+
EPSILON = 10 ** (-9)
|
| 1152 |
+
freq = sample_rate / (EPSILON + indices.to(torch.float))
|
| 1153 |
+
|
| 1154 |
+
# unpack batch
|
| 1155 |
+
freq = freq.reshape(shape[:-1] + list(freq.shape[-1:]))
|
| 1156 |
+
|
| 1157 |
+
return freq
|
| 1158 |
+
|
| 1159 |
+
|
| 1160 |
+
def sliding_window_cmn(
|
| 1161 |
+
specgram: Tensor,
|
| 1162 |
+
cmn_window: int = 600,
|
| 1163 |
+
min_cmn_window: int = 100,
|
| 1164 |
+
center: bool = False,
|
| 1165 |
+
norm_vars: bool = False,
|
| 1166 |
+
) -> Tensor:
|
| 1167 |
+
r"""
|
| 1168 |
+
Apply sliding-window cepstral mean (and optionally variance) normalization per utterance.
|
| 1169 |
+
|
| 1170 |
+
.. devices:: CPU CUDA
|
| 1171 |
+
|
| 1172 |
+
.. properties:: TorchScript
|
| 1173 |
+
|
| 1174 |
+
Args:
|
| 1175 |
+
specgram (Tensor): Tensor of spectrogram of dimension `(..., time, freq)`
|
| 1176 |
+
cmn_window (int, optional): Window in frames for running average CMN computation (int, default = 600)
|
| 1177 |
+
min_cmn_window (int, optional): Minimum CMN window used at start of decoding (adds latency only at start).
|
| 1178 |
+
Only applicable if center == false, ignored if center==true (int, default = 100)
|
| 1179 |
+
center (bool, optional): If true, use a window centered on the current frame
|
| 1180 |
+
(to the extent possible, modulo end effects). If false, window is to the left. (bool, default = false)
|
| 1181 |
+
norm_vars (bool, optional): If true, normalize variance to one. (bool, default = false)
|
| 1182 |
+
|
| 1183 |
+
Returns:
|
| 1184 |
+
Tensor: Tensor matching input shape `(..., freq, time)`
|
| 1185 |
+
"""
|
| 1186 |
+
input_shape = specgram.shape
|
| 1187 |
+
num_frames, num_feats = input_shape[-2:]
|
| 1188 |
+
specgram = specgram.view(-1, num_frames, num_feats)
|
| 1189 |
+
num_channels = specgram.shape[0]
|
| 1190 |
+
|
| 1191 |
+
dtype = specgram.dtype
|
| 1192 |
+
device = specgram.device
|
| 1193 |
+
last_window_start = last_window_end = -1
|
| 1194 |
+
cur_sum = torch.zeros(num_channels, num_feats, dtype=dtype, device=device)
|
| 1195 |
+
cur_sumsq = torch.zeros(num_channels, num_feats, dtype=dtype, device=device)
|
| 1196 |
+
cmn_specgram = torch.zeros(num_channels, num_frames, num_feats, dtype=dtype, device=device)
|
| 1197 |
+
for t in range(num_frames):
|
| 1198 |
+
window_start = 0
|
| 1199 |
+
window_end = 0
|
| 1200 |
+
if center:
|
| 1201 |
+
window_start = t - cmn_window // 2
|
| 1202 |
+
window_end = window_start + cmn_window
|
| 1203 |
+
else:
|
| 1204 |
+
window_start = t - cmn_window
|
| 1205 |
+
window_end = t + 1
|
| 1206 |
+
if window_start < 0:
|
| 1207 |
+
window_end -= window_start
|
| 1208 |
+
window_start = 0
|
| 1209 |
+
if not center:
|
| 1210 |
+
if window_end > t:
|
| 1211 |
+
window_end = max(t + 1, min_cmn_window)
|
| 1212 |
+
if window_end > num_frames:
|
| 1213 |
+
window_start -= window_end - num_frames
|
| 1214 |
+
window_end = num_frames
|
| 1215 |
+
if window_start < 0:
|
| 1216 |
+
window_start = 0
|
| 1217 |
+
if last_window_start == -1:
|
| 1218 |
+
input_part = specgram[:, window_start : window_end - window_start, :]
|
| 1219 |
+
cur_sum += torch.sum(input_part, 1)
|
| 1220 |
+
if norm_vars:
|
| 1221 |
+
cur_sumsq += torch.cumsum(input_part**2, 1)[:, -1, :]
|
| 1222 |
+
else:
|
| 1223 |
+
if window_start > last_window_start:
|
| 1224 |
+
frame_to_remove = specgram[:, last_window_start, :]
|
| 1225 |
+
cur_sum -= frame_to_remove
|
| 1226 |
+
if norm_vars:
|
| 1227 |
+
cur_sumsq -= frame_to_remove**2
|
| 1228 |
+
if window_end > last_window_end:
|
| 1229 |
+
frame_to_add = specgram[:, last_window_end, :]
|
| 1230 |
+
cur_sum += frame_to_add
|
| 1231 |
+
if norm_vars:
|
| 1232 |
+
cur_sumsq += frame_to_add**2
|
| 1233 |
+
window_frames = window_end - window_start
|
| 1234 |
+
last_window_start = window_start
|
| 1235 |
+
last_window_end = window_end
|
| 1236 |
+
cmn_specgram[:, t, :] = specgram[:, t, :] - cur_sum / window_frames
|
| 1237 |
+
if norm_vars:
|
| 1238 |
+
if window_frames == 1:
|
| 1239 |
+
cmn_specgram[:, t, :] = torch.zeros(num_channels, num_feats, dtype=dtype, device=device)
|
| 1240 |
+
else:
|
| 1241 |
+
variance = cur_sumsq
|
| 1242 |
+
variance = variance / window_frames
|
| 1243 |
+
variance -= (cur_sum**2) / (window_frames**2)
|
| 1244 |
+
variance = torch.pow(variance, -0.5)
|
| 1245 |
+
cmn_specgram[:, t, :] *= variance
|
| 1246 |
+
|
| 1247 |
+
cmn_specgram = cmn_specgram.view(input_shape[:-2] + (num_frames, num_feats))
|
| 1248 |
+
if len(input_shape) == 2:
|
| 1249 |
+
cmn_specgram = cmn_specgram.squeeze(0)
|
| 1250 |
+
return cmn_specgram
|
| 1251 |
+
|
| 1252 |
+
|
| 1253 |
+
def spectral_centroid(
|
| 1254 |
+
waveform: Tensor,
|
| 1255 |
+
sample_rate: int,
|
| 1256 |
+
pad: int,
|
| 1257 |
+
window: Tensor,
|
| 1258 |
+
n_fft: int,
|
| 1259 |
+
hop_length: int,
|
| 1260 |
+
win_length: int,
|
| 1261 |
+
) -> Tensor:
|
| 1262 |
+
r"""Compute the spectral centroid for each channel along the time axis.
|
| 1263 |
+
|
| 1264 |
+
.. devices:: CPU CUDA
|
| 1265 |
+
|
| 1266 |
+
.. properties:: Autograd TorchScript
|
| 1267 |
+
|
| 1268 |
+
The spectral centroid is defined as the weighted average of the
|
| 1269 |
+
frequency values, weighted by their magnitude.
|
| 1270 |
+
|
| 1271 |
+
Args:
|
| 1272 |
+
waveform (Tensor): Tensor of audio of dimension `(..., time)`
|
| 1273 |
+
sample_rate (int): Sample rate of the audio waveform
|
| 1274 |
+
pad (int): Two sided padding of signal
|
| 1275 |
+
window (Tensor): Window tensor that is applied/multiplied to each frame/window
|
| 1276 |
+
n_fft (int): Size of FFT
|
| 1277 |
+
hop_length (int): Length of hop between STFT windows
|
| 1278 |
+
win_length (int): Window size
|
| 1279 |
+
|
| 1280 |
+
Returns:
|
| 1281 |
+
Tensor: Dimension `(..., time)`
|
| 1282 |
+
"""
|
| 1283 |
+
specgram = spectrogram(
|
| 1284 |
+
waveform,
|
| 1285 |
+
pad=pad,
|
| 1286 |
+
window=window,
|
| 1287 |
+
n_fft=n_fft,
|
| 1288 |
+
hop_length=hop_length,
|
| 1289 |
+
win_length=win_length,
|
| 1290 |
+
power=1.0,
|
| 1291 |
+
normalized=False,
|
| 1292 |
+
)
|
| 1293 |
+
freqs = torch.linspace(0, sample_rate // 2, steps=1 + n_fft // 2, device=specgram.device).reshape((-1, 1))
|
| 1294 |
+
freq_dim = -2
|
| 1295 |
+
return (freqs * specgram).sum(dim=freq_dim) / specgram.sum(dim=freq_dim)
|
| 1296 |
+
|
| 1297 |
+
|
| 1298 |
+
@deprecated("Please migrate to :py:class:`torchaudio.io.AudioEffector`.", remove=False)
|
| 1299 |
+
def apply_codec(
|
| 1300 |
+
waveform: Tensor,
|
| 1301 |
+
sample_rate: int,
|
| 1302 |
+
format: str,
|
| 1303 |
+
channels_first: bool = True,
|
| 1304 |
+
compression: Optional[float] = None,
|
| 1305 |
+
encoding: Optional[str] = None,
|
| 1306 |
+
bits_per_sample: Optional[int] = None,
|
| 1307 |
+
) -> Tensor:
|
| 1308 |
+
r"""
|
| 1309 |
+
Apply codecs as a form of augmentation.
|
| 1310 |
+
|
| 1311 |
+
.. devices:: CPU
|
| 1312 |
+
|
| 1313 |
+
Args:
|
| 1314 |
+
waveform (Tensor): Audio data. Must be 2 dimensional. See also ```channels_first```.
|
| 1315 |
+
sample_rate (int): Sample rate of the audio waveform.
|
| 1316 |
+
format (str): File format.
|
| 1317 |
+
channels_first (bool, optional):
|
| 1318 |
+
When True, both the input and output Tensor have dimension `(channel, time)`.
|
| 1319 |
+
Otherwise, they have dimension `(time, channel)`.
|
| 1320 |
+
compression (float or None, optional): Used for formats other than WAV.
|
| 1321 |
+
For more details see :py:func:`torchaudio.backend.sox_io_backend.save`.
|
| 1322 |
+
encoding (str or None, optional): Changes the encoding for the supported formats.
|
| 1323 |
+
For more details see :py:func:`torchaudio.backend.sox_io_backend.save`.
|
| 1324 |
+
bits_per_sample (int or None, optional): Changes the bit depth for the supported formats.
|
| 1325 |
+
For more details see :py:func:`torchaudio.backend.sox_io_backend.save`.
|
| 1326 |
+
|
| 1327 |
+
Returns:
|
| 1328 |
+
Tensor: Resulting Tensor.
|
| 1329 |
+
If ``channels_first=True``, it has `(channel, time)` else `(time, channel)`.
|
| 1330 |
+
"""
|
| 1331 |
+
from torchaudio.backend import _sox_io_backend
|
| 1332 |
+
|
| 1333 |
+
with tempfile.NamedTemporaryFile() as f:
|
| 1334 |
+
torchaudio.backend._sox_io_backend.save(
|
| 1335 |
+
f.name, waveform, sample_rate, channels_first, compression, format, encoding, bits_per_sample
|
| 1336 |
+
)
|
| 1337 |
+
augmented, sr = _sox_io_backend.load(f.name, channels_first=channels_first, format=format)
|
| 1338 |
+
if sr != sample_rate:
|
| 1339 |
+
augmented = resample(augmented, sr, sample_rate)
|
| 1340 |
+
return augmented
|
| 1341 |
+
|
| 1342 |
+
|
| 1343 |
+
_CPU = torch.device("cpu")
|
| 1344 |
+
|
| 1345 |
+
|
| 1346 |
+
def _get_sinc_resample_kernel(
|
| 1347 |
+
orig_freq: int,
|
| 1348 |
+
new_freq: int,
|
| 1349 |
+
gcd: int,
|
| 1350 |
+
lowpass_filter_width: int = 6,
|
| 1351 |
+
rolloff: float = 0.99,
|
| 1352 |
+
resampling_method: str = "sinc_interp_hann",
|
| 1353 |
+
beta: Optional[float] = None,
|
| 1354 |
+
device: torch.device = _CPU,
|
| 1355 |
+
dtype: Optional[torch.dtype] = None,
|
| 1356 |
+
):
|
| 1357 |
+
if not (int(orig_freq) == orig_freq and int(new_freq) == new_freq):
|
| 1358 |
+
raise Exception(
|
| 1359 |
+
"Frequencies must be of integer type to ensure quality resampling computation. "
|
| 1360 |
+
"To work around this, manually convert both frequencies to integer values "
|
| 1361 |
+
"that maintain their resampling rate ratio before passing them into the function. "
|
| 1362 |
+
"Example: To downsample a 44100 hz waveform by a factor of 8, use "
|
| 1363 |
+
"`orig_freq=8` and `new_freq=1` instead of `orig_freq=44100` and `new_freq=5512.5`. "
|
| 1364 |
+
"For more information, please refer to https://github.com/pytorch/audio/issues/1487."
|
| 1365 |
+
)
|
| 1366 |
+
|
| 1367 |
+
if resampling_method in ["sinc_interpolation", "kaiser_window"]:
|
| 1368 |
+
method_map = {
|
| 1369 |
+
"sinc_interpolation": "sinc_interp_hann",
|
| 1370 |
+
"kaiser_window": "sinc_interp_kaiser",
|
| 1371 |
+
}
|
| 1372 |
+
warnings.warn(
|
| 1373 |
+
f'"{resampling_method}" resampling method name is being deprecated and replaced by '
|
| 1374 |
+
f'"{method_map[resampling_method]}" in the next release. '
|
| 1375 |
+
"The default behavior remains unchanged.",
|
| 1376 |
+
stacklevel=3,
|
| 1377 |
+
)
|
| 1378 |
+
elif resampling_method not in ["sinc_interp_hann", "sinc_interp_kaiser"]:
|
| 1379 |
+
raise ValueError("Invalid resampling method: {}".format(resampling_method))
|
| 1380 |
+
|
| 1381 |
+
orig_freq = int(orig_freq) // gcd
|
| 1382 |
+
new_freq = int(new_freq) // gcd
|
| 1383 |
+
|
| 1384 |
+
if lowpass_filter_width <= 0:
|
| 1385 |
+
raise ValueError("Low pass filter width should be positive.")
|
| 1386 |
+
base_freq = min(orig_freq, new_freq)
|
| 1387 |
+
# This will perform antialiasing filtering by removing the highest frequencies.
|
| 1388 |
+
# At first I thought I only needed this when downsampling, but when upsampling
|
| 1389 |
+
# you will get edge artifacts without this, as the edge is equivalent to zero padding,
|
| 1390 |
+
# which will add high freq artifacts.
|
| 1391 |
+
base_freq *= rolloff
|
| 1392 |
+
|
| 1393 |
+
# The key idea of the algorithm is that x(t) can be exactly reconstructed from x[i] (tensor)
|
| 1394 |
+
# using the sinc interpolation formula:
|
| 1395 |
+
# x(t) = sum_i x[i] sinc(pi * orig_freq * (i / orig_freq - t))
|
| 1396 |
+
# We can then sample the function x(t) with a different sample rate:
|
| 1397 |
+
# y[j] = x(j / new_freq)
|
| 1398 |
+
# or,
|
| 1399 |
+
# y[j] = sum_i x[i] sinc(pi * orig_freq * (i / orig_freq - j / new_freq))
|
| 1400 |
+
|
| 1401 |
+
# We see here that y[j] is the convolution of x[i] with a specific filter, for which
|
| 1402 |
+
# we take an FIR approximation, stopping when we see at least `lowpass_filter_width` zeros crossing.
|
| 1403 |
+
# But y[j+1] is going to have a different set of weights and so on, until y[j + new_freq].
|
| 1404 |
+
# Indeed:
|
| 1405 |
+
# y[j + new_freq] = sum_i x[i] sinc(pi * orig_freq * ((i / orig_freq - (j + new_freq) / new_freq))
|
| 1406 |
+
# = sum_i x[i] sinc(pi * orig_freq * ((i - orig_freq) / orig_freq - j / new_freq))
|
| 1407 |
+
# = sum_i x[i + orig_freq] sinc(pi * orig_freq * (i / orig_freq - j / new_freq))
|
| 1408 |
+
# so y[j+new_freq] uses the same filter as y[j], but on a shifted version of x by `orig_freq`.
|
| 1409 |
+
# This will explain the F.conv1d after, with a stride of orig_freq.
|
| 1410 |
+
width = math.ceil(lowpass_filter_width * orig_freq / base_freq)
|
| 1411 |
+
# If orig_freq is still big after GCD reduction, most filters will be very unbalanced, i.e.,
|
| 1412 |
+
# they will have a lot of almost zero values to the left or to the right...
|
| 1413 |
+
# There is probably a way to evaluate those filters more efficiently, but this is kept for
|
| 1414 |
+
# future work.
|
| 1415 |
+
idx_dtype = dtype if dtype is not None else torch.float64
|
| 1416 |
+
|
| 1417 |
+
idx = torch.arange(-width, width + orig_freq, dtype=idx_dtype, device=device)[None, None] / orig_freq
|
| 1418 |
+
|
| 1419 |
+
t = torch.arange(0, -new_freq, -1, dtype=dtype, device=device)[:, None, None] / new_freq + idx
|
| 1420 |
+
t *= base_freq
|
| 1421 |
+
t = t.clamp_(-lowpass_filter_width, lowpass_filter_width)
|
| 1422 |
+
|
| 1423 |
+
# we do not use built in torch windows here as we need to evaluate the window
|
| 1424 |
+
# at specific positions, not over a regular grid.
|
| 1425 |
+
if resampling_method == "sinc_interp_hann":
|
| 1426 |
+
window = torch.cos(t * math.pi / lowpass_filter_width / 2) ** 2
|
| 1427 |
+
else:
|
| 1428 |
+
# sinc_interp_kaiser
|
| 1429 |
+
if beta is None:
|
| 1430 |
+
beta = 14.769656459379492
|
| 1431 |
+
beta_tensor = torch.tensor(float(beta))
|
| 1432 |
+
window = torch.i0(beta_tensor * torch.sqrt(1 - (t / lowpass_filter_width) ** 2)) / torch.i0(beta_tensor)
|
| 1433 |
+
|
| 1434 |
+
t *= math.pi
|
| 1435 |
+
|
| 1436 |
+
scale = base_freq / orig_freq
|
| 1437 |
+
kernels = torch.where(t == 0, torch.tensor(1.0).to(t), t.sin() / t)
|
| 1438 |
+
kernels *= window * scale
|
| 1439 |
+
|
| 1440 |
+
if dtype is None:
|
| 1441 |
+
kernels = kernels.to(dtype=torch.float32)
|
| 1442 |
+
|
| 1443 |
+
return kernels, width
|
| 1444 |
+
|
| 1445 |
+
|
| 1446 |
+
def _apply_sinc_resample_kernel(
|
| 1447 |
+
waveform: Tensor,
|
| 1448 |
+
orig_freq: int,
|
| 1449 |
+
new_freq: int,
|
| 1450 |
+
gcd: int,
|
| 1451 |
+
kernel: Tensor,
|
| 1452 |
+
width: int,
|
| 1453 |
+
):
|
| 1454 |
+
if not waveform.is_floating_point():
|
| 1455 |
+
raise TypeError(f"Expected floating point type for waveform tensor, but received {waveform.dtype}.")
|
| 1456 |
+
|
| 1457 |
+
orig_freq = int(orig_freq) // gcd
|
| 1458 |
+
new_freq = int(new_freq) // gcd
|
| 1459 |
+
|
| 1460 |
+
# pack batch
|
| 1461 |
+
shape = waveform.size()
|
| 1462 |
+
waveform = waveform.view(-1, shape[-1])
|
| 1463 |
+
|
| 1464 |
+
num_wavs, length = waveform.shape
|
| 1465 |
+
waveform = torch.nn.functional.pad(waveform, (width, width + orig_freq))
|
| 1466 |
+
resampled = torch.nn.functional.conv1d(waveform[:, None], kernel, stride=orig_freq)
|
| 1467 |
+
resampled = resampled.transpose(1, 2).reshape(num_wavs, -1)
|
| 1468 |
+
target_length = torch.ceil(torch.as_tensor(new_freq * length / orig_freq)).long()
|
| 1469 |
+
resampled = resampled[..., :target_length]
|
| 1470 |
+
|
| 1471 |
+
# unpack batch
|
| 1472 |
+
resampled = resampled.view(shape[:-1] + resampled.shape[-1:])
|
| 1473 |
+
return resampled
|
| 1474 |
+
|
| 1475 |
+
|
| 1476 |
+
def resample(
|
| 1477 |
+
waveform: Tensor,
|
| 1478 |
+
orig_freq: int,
|
| 1479 |
+
new_freq: int,
|
| 1480 |
+
lowpass_filter_width: int = 6,
|
| 1481 |
+
rolloff: float = 0.99,
|
| 1482 |
+
resampling_method: str = "sinc_interp_hann",
|
| 1483 |
+
beta: Optional[float] = None,
|
| 1484 |
+
) -> Tensor:
|
| 1485 |
+
r"""Resamples the waveform at the new frequency using bandlimited interpolation. :cite:`RESAMPLE`.
|
| 1486 |
+
|
| 1487 |
+
.. devices:: CPU CUDA
|
| 1488 |
+
|
| 1489 |
+
.. properties:: Autograd TorchScript
|
| 1490 |
+
|
| 1491 |
+
Note:
|
| 1492 |
+
``transforms.Resample`` precomputes and reuses the resampling kernel, so using it will result in
|
| 1493 |
+
more efficient computation if resampling multiple waveforms with the same resampling parameters.
|
| 1494 |
+
|
| 1495 |
+
Args:
|
| 1496 |
+
waveform (Tensor): The input signal of dimension `(..., time)`
|
| 1497 |
+
orig_freq (int): The original frequency of the signal
|
| 1498 |
+
new_freq (int): The desired frequency
|
| 1499 |
+
lowpass_filter_width (int, optional): Controls the sharpness of the filter, more == sharper
|
| 1500 |
+
but less efficient. (Default: ``6``)
|
| 1501 |
+
rolloff (float, optional): The roll-off frequency of the filter, as a fraction of the Nyquist.
|
| 1502 |
+
Lower values reduce anti-aliasing, but also reduce some of the highest frequencies. (Default: ``0.99``)
|
| 1503 |
+
resampling_method (str, optional): The resampling method to use.
|
| 1504 |
+
Options: [``"sinc_interp_hann"``, ``"sinc_interp_kaiser"``] (Default: ``"sinc_interp_hann"``)
|
| 1505 |
+
beta (float or None, optional): The shape parameter used for kaiser window.
|
| 1506 |
+
|
| 1507 |
+
Returns:
|
| 1508 |
+
Tensor: The waveform at the new frequency of dimension `(..., time).`
|
| 1509 |
+
"""
|
| 1510 |
+
|
| 1511 |
+
if orig_freq <= 0.0 or new_freq <= 0.0:
|
| 1512 |
+
raise ValueError("Original frequency and desired frequecy should be positive")
|
| 1513 |
+
|
| 1514 |
+
if orig_freq == new_freq:
|
| 1515 |
+
return waveform
|
| 1516 |
+
|
| 1517 |
+
gcd = math.gcd(int(orig_freq), int(new_freq))
|
| 1518 |
+
|
| 1519 |
+
kernel, width = _get_sinc_resample_kernel(
|
| 1520 |
+
orig_freq,
|
| 1521 |
+
new_freq,
|
| 1522 |
+
gcd,
|
| 1523 |
+
lowpass_filter_width,
|
| 1524 |
+
rolloff,
|
| 1525 |
+
resampling_method,
|
| 1526 |
+
beta,
|
| 1527 |
+
waveform.device,
|
| 1528 |
+
waveform.dtype,
|
| 1529 |
+
)
|
| 1530 |
+
resampled = _apply_sinc_resample_kernel(waveform, orig_freq, new_freq, gcd, kernel, width)
|
| 1531 |
+
return resampled
|
| 1532 |
+
|
| 1533 |
+
|
| 1534 |
+
@torch.jit.unused
|
| 1535 |
+
def edit_distance(seq1: Sequence, seq2: Sequence) -> int:
|
| 1536 |
+
"""
|
| 1537 |
+
Calculate the word level edit (Levenshtein) distance between two sequences.
|
| 1538 |
+
|
| 1539 |
+
.. devices:: CPU
|
| 1540 |
+
|
| 1541 |
+
The function computes an edit distance allowing deletion, insertion and
|
| 1542 |
+
substitution. The result is an integer.
|
| 1543 |
+
|
| 1544 |
+
For most applications, the two input sequences should be the same type. If
|
| 1545 |
+
two strings are given, the output is the edit distance between the two
|
| 1546 |
+
strings (character edit distance). If two lists of strings are given, the
|
| 1547 |
+
output is the edit distance between sentences (word edit distance). Users
|
| 1548 |
+
may want to normalize the output by the length of the reference sequence.
|
| 1549 |
+
|
| 1550 |
+
Args:
|
| 1551 |
+
seq1 (Sequence): the first sequence to compare.
|
| 1552 |
+
seq2 (Sequence): the second sequence to compare.
|
| 1553 |
+
Returns:
|
| 1554 |
+
int: The distance between the first and second sequences.
|
| 1555 |
+
"""
|
| 1556 |
+
len_sent2 = len(seq2)
|
| 1557 |
+
dold = list(range(len_sent2 + 1))
|
| 1558 |
+
dnew = [0 for _ in range(len_sent2 + 1)]
|
| 1559 |
+
|
| 1560 |
+
for i in range(1, len(seq1) + 1):
|
| 1561 |
+
dnew[0] = i
|
| 1562 |
+
for j in range(1, len_sent2 + 1):
|
| 1563 |
+
if seq1[i - 1] == seq2[j - 1]:
|
| 1564 |
+
dnew[j] = dold[j - 1]
|
| 1565 |
+
else:
|
| 1566 |
+
substitution = dold[j - 1] + 1
|
| 1567 |
+
insertion = dnew[j - 1] + 1
|
| 1568 |
+
deletion = dold[j] + 1
|
| 1569 |
+
dnew[j] = min(substitution, insertion, deletion)
|
| 1570 |
+
|
| 1571 |
+
dnew, dold = dold, dnew
|
| 1572 |
+
|
| 1573 |
+
return int(dold[-1])
|
| 1574 |
+
|
| 1575 |
+
|
| 1576 |
+
def loudness(waveform: Tensor, sample_rate: int):
|
| 1577 |
+
r"""Measure audio loudness according to the ITU-R BS.1770-4 recommendation.
|
| 1578 |
+
|
| 1579 |
+
.. devices:: CPU CUDA
|
| 1580 |
+
|
| 1581 |
+
.. properties:: TorchScript
|
| 1582 |
+
|
| 1583 |
+
Args:
|
| 1584 |
+
waveform(torch.Tensor): audio waveform of dimension `(..., channels, time)`
|
| 1585 |
+
sample_rate (int): sampling rate of the waveform
|
| 1586 |
+
|
| 1587 |
+
Returns:
|
| 1588 |
+
Tensor: loudness estimates (LKFS)
|
| 1589 |
+
|
| 1590 |
+
Reference:
|
| 1591 |
+
- https://www.itu.int/rec/R-REC-BS.1770-4-201510-I/en
|
| 1592 |
+
"""
|
| 1593 |
+
|
| 1594 |
+
if waveform.size(-2) > 5:
|
| 1595 |
+
raise ValueError("Only up to 5 channels are supported.")
|
| 1596 |
+
|
| 1597 |
+
gate_duration = 0.4
|
| 1598 |
+
overlap = 0.75
|
| 1599 |
+
gamma_abs = -70.0
|
| 1600 |
+
kweight_bias = -0.691
|
| 1601 |
+
gate_samples = int(round(gate_duration * sample_rate))
|
| 1602 |
+
step = int(round(gate_samples * (1 - overlap)))
|
| 1603 |
+
|
| 1604 |
+
# Apply K-weighting
|
| 1605 |
+
waveform = treble_biquad(waveform, sample_rate, 4.0, 1500.0, 1 / math.sqrt(2))
|
| 1606 |
+
waveform = highpass_biquad(waveform, sample_rate, 38.0, 0.5)
|
| 1607 |
+
|
| 1608 |
+
# Compute the energy for each block
|
| 1609 |
+
energy = torch.square(waveform).unfold(-1, gate_samples, step)
|
| 1610 |
+
energy = torch.mean(energy, dim=-1)
|
| 1611 |
+
|
| 1612 |
+
# Compute channel-weighted summation
|
| 1613 |
+
g = torch.tensor([1.0, 1.0, 1.0, 1.41, 1.41], dtype=waveform.dtype, device=waveform.device)
|
| 1614 |
+
g = g[: energy.size(-2)]
|
| 1615 |
+
|
| 1616 |
+
energy_weighted = torch.sum(g.unsqueeze(-1) * energy, dim=-2)
|
| 1617 |
+
loudness = -0.691 + 10 * torch.log10(energy_weighted)
|
| 1618 |
+
|
| 1619 |
+
# Apply absolute gating of the blocks
|
| 1620 |
+
gated_blocks = loudness > gamma_abs
|
| 1621 |
+
gated_blocks = gated_blocks.unsqueeze(-2)
|
| 1622 |
+
|
| 1623 |
+
energy_filtered = torch.sum(gated_blocks * energy, dim=-1) / torch.count_nonzero(gated_blocks, dim=-1)
|
| 1624 |
+
energy_weighted = torch.sum(g * energy_filtered, dim=-1)
|
| 1625 |
+
gamma_rel = kweight_bias + 10 * torch.log10(energy_weighted) - 10
|
| 1626 |
+
|
| 1627 |
+
# Apply relative gating of the blocks
|
| 1628 |
+
gated_blocks = torch.logical_and(gated_blocks.squeeze(-2), loudness > gamma_rel.unsqueeze(-1))
|
| 1629 |
+
gated_blocks = gated_blocks.unsqueeze(-2)
|
| 1630 |
+
|
| 1631 |
+
energy_filtered = torch.sum(gated_blocks * energy, dim=-1) / torch.count_nonzero(gated_blocks, dim=-1)
|
| 1632 |
+
energy_weighted = torch.sum(g * energy_filtered, dim=-1)
|
| 1633 |
+
LKFS = kweight_bias + 10 * torch.log10(energy_weighted)
|
| 1634 |
+
return LKFS
|
| 1635 |
+
|
| 1636 |
+
|
| 1637 |
+
def pitch_shift(
|
| 1638 |
+
waveform: Tensor,
|
| 1639 |
+
sample_rate: int,
|
| 1640 |
+
n_steps: int,
|
| 1641 |
+
bins_per_octave: int = 12,
|
| 1642 |
+
n_fft: int = 512,
|
| 1643 |
+
win_length: Optional[int] = None,
|
| 1644 |
+
hop_length: Optional[int] = None,
|
| 1645 |
+
window: Optional[Tensor] = None,
|
| 1646 |
+
) -> Tensor:
|
| 1647 |
+
"""
|
| 1648 |
+
Shift the pitch of a waveform by ``n_steps`` steps.
|
| 1649 |
+
|
| 1650 |
+
.. devices:: CPU CUDA
|
| 1651 |
+
|
| 1652 |
+
.. properties:: TorchScript
|
| 1653 |
+
|
| 1654 |
+
Args:
|
| 1655 |
+
waveform (Tensor): The input waveform of shape `(..., time)`.
|
| 1656 |
+
sample_rate (int): Sample rate of `waveform`.
|
| 1657 |
+
n_steps (int): The (fractional) steps to shift `waveform`.
|
| 1658 |
+
bins_per_octave (int, optional): The number of steps per octave (Default: ``12``).
|
| 1659 |
+
n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins (Default: ``512``).
|
| 1660 |
+
win_length (int or None, optional): Window size. If None, then ``n_fft`` is used. (Default: ``None``).
|
| 1661 |
+
hop_length (int or None, optional): Length of hop between STFT windows. If None, then
|
| 1662 |
+
``win_length // 4`` is used (Default: ``None``).
|
| 1663 |
+
window (Tensor or None, optional): Window tensor that is applied/multiplied to each frame/window.
|
| 1664 |
+
If None, then ``torch.hann_window(win_length)`` is used (Default: ``None``).
|
| 1665 |
+
|
| 1666 |
+
|
| 1667 |
+
Returns:
|
| 1668 |
+
Tensor: The pitch-shifted audio waveform of shape `(..., time)`.
|
| 1669 |
+
"""
|
| 1670 |
+
waveform_stretch = _stretch_waveform(
|
| 1671 |
+
waveform,
|
| 1672 |
+
n_steps,
|
| 1673 |
+
bins_per_octave,
|
| 1674 |
+
n_fft,
|
| 1675 |
+
win_length,
|
| 1676 |
+
hop_length,
|
| 1677 |
+
window,
|
| 1678 |
+
)
|
| 1679 |
+
rate = 2.0 ** (-float(n_steps) / bins_per_octave)
|
| 1680 |
+
waveform_shift = resample(waveform_stretch, int(sample_rate / rate), sample_rate)
|
| 1681 |
+
|
| 1682 |
+
return _fix_waveform_shape(waveform_shift, waveform.size())
|
| 1683 |
+
|
| 1684 |
+
|
| 1685 |
+
def _stretch_waveform(
|
| 1686 |
+
waveform: Tensor,
|
| 1687 |
+
n_steps: int,
|
| 1688 |
+
bins_per_octave: int = 12,
|
| 1689 |
+
n_fft: int = 512,
|
| 1690 |
+
win_length: Optional[int] = None,
|
| 1691 |
+
hop_length: Optional[int] = None,
|
| 1692 |
+
window: Optional[Tensor] = None,
|
| 1693 |
+
) -> Tensor:
|
| 1694 |
+
"""
|
| 1695 |
+
Pitch shift helper function to preprocess and stretch waveform before resampling step.
|
| 1696 |
+
|
| 1697 |
+
Args:
|
| 1698 |
+
See pitch_shift arg descriptions.
|
| 1699 |
+
|
| 1700 |
+
Returns:
|
| 1701 |
+
Tensor: The preprocessed waveform stretched prior to resampling.
|
| 1702 |
+
"""
|
| 1703 |
+
if hop_length is None:
|
| 1704 |
+
hop_length = n_fft // 4
|
| 1705 |
+
if win_length is None:
|
| 1706 |
+
win_length = n_fft
|
| 1707 |
+
if window is None:
|
| 1708 |
+
window = torch.hann_window(window_length=win_length, device=waveform.device)
|
| 1709 |
+
|
| 1710 |
+
# pack batch
|
| 1711 |
+
shape = waveform.size()
|
| 1712 |
+
waveform = waveform.reshape(-1, shape[-1])
|
| 1713 |
+
|
| 1714 |
+
ori_len = shape[-1]
|
| 1715 |
+
rate = 2.0 ** (-float(n_steps) / bins_per_octave)
|
| 1716 |
+
spec_f = torch.stft(
|
| 1717 |
+
input=waveform,
|
| 1718 |
+
n_fft=n_fft,
|
| 1719 |
+
hop_length=hop_length,
|
| 1720 |
+
win_length=win_length,
|
| 1721 |
+
window=window,
|
| 1722 |
+
center=True,
|
| 1723 |
+
pad_mode="reflect",
|
| 1724 |
+
normalized=False,
|
| 1725 |
+
onesided=True,
|
| 1726 |
+
return_complex=True,
|
| 1727 |
+
)
|
| 1728 |
+
phase_advance = torch.linspace(0, math.pi * hop_length, spec_f.shape[-2], device=spec_f.device)[..., None]
|
| 1729 |
+
spec_stretch = phase_vocoder(spec_f, rate, phase_advance)
|
| 1730 |
+
len_stretch = int(round(ori_len / rate))
|
| 1731 |
+
waveform_stretch = torch.istft(
|
| 1732 |
+
spec_stretch, n_fft=n_fft, hop_length=hop_length, win_length=win_length, window=window, length=len_stretch
|
| 1733 |
+
)
|
| 1734 |
+
return waveform_stretch
|
| 1735 |
+
|
| 1736 |
+
|
| 1737 |
+
def _fix_waveform_shape(
|
| 1738 |
+
waveform_shift: Tensor,
|
| 1739 |
+
shape: List[int],
|
| 1740 |
+
) -> Tensor:
|
| 1741 |
+
"""
|
| 1742 |
+
PitchShift helper function to process after resampling step to fix the shape back.
|
| 1743 |
+
|
| 1744 |
+
Args:
|
| 1745 |
+
waveform_shift(Tensor): The waveform after stretch and resample
|
| 1746 |
+
shape (List[int]): The shape of initial waveform
|
| 1747 |
+
|
| 1748 |
+
Returns:
|
| 1749 |
+
Tensor: The pitch-shifted audio waveform of shape `(..., time)`.
|
| 1750 |
+
"""
|
| 1751 |
+
ori_len = shape[-1]
|
| 1752 |
+
shift_len = waveform_shift.size()[-1]
|
| 1753 |
+
if shift_len > ori_len:
|
| 1754 |
+
waveform_shift = waveform_shift[..., :ori_len]
|
| 1755 |
+
else:
|
| 1756 |
+
waveform_shift = torch.nn.functional.pad(waveform_shift, [0, ori_len - shift_len])
|
| 1757 |
+
|
| 1758 |
+
# unpack batch
|
| 1759 |
+
waveform_shift = waveform_shift.view(shape[:-1] + waveform_shift.shape[-1:])
|
| 1760 |
+
return waveform_shift
|
| 1761 |
+
|
| 1762 |
+
|
| 1763 |
+
def rnnt_loss(
|
| 1764 |
+
logits: Tensor,
|
| 1765 |
+
targets: Tensor,
|
| 1766 |
+
logit_lengths: Tensor,
|
| 1767 |
+
target_lengths: Tensor,
|
| 1768 |
+
blank: int = -1,
|
| 1769 |
+
clamp: float = -1,
|
| 1770 |
+
reduction: str = "mean",
|
| 1771 |
+
fused_log_softmax: bool = True,
|
| 1772 |
+
):
|
| 1773 |
+
"""Compute the RNN Transducer loss from *Sequence Transduction with Recurrent Neural Networks*
|
| 1774 |
+
:cite:`graves2012sequence`.
|
| 1775 |
+
|
| 1776 |
+
.. devices:: CPU CUDA
|
| 1777 |
+
|
| 1778 |
+
.. properties:: Autograd TorchScript
|
| 1779 |
+
|
| 1780 |
+
The RNN Transducer loss extends the CTC loss by defining a distribution over output
|
| 1781 |
+
sequences of all lengths, and by jointly modelling both input-output and output-output
|
| 1782 |
+
dependencies.
|
| 1783 |
+
|
| 1784 |
+
Args:
|
| 1785 |
+
logits (Tensor): Tensor of dimension `(batch, max seq length, max target length + 1, class)`
|
| 1786 |
+
containing output from joiner
|
| 1787 |
+
targets (Tensor): Tensor of dimension `(batch, max target length)` containing targets with zero padded
|
| 1788 |
+
logit_lengths (Tensor): Tensor of dimension `(batch)` containing lengths of each sequence from encoder
|
| 1789 |
+
target_lengths (Tensor): Tensor of dimension `(batch)` containing lengths of targets for each sequence
|
| 1790 |
+
blank (int, optional): blank label (Default: ``-1``)
|
| 1791 |
+
clamp (float, optional): clamp for gradients (Default: ``-1``)
|
| 1792 |
+
reduction (string, optional): Specifies the reduction to apply to the output:
|
| 1793 |
+
``"none"`` | ``"mean"`` | ``"sum"``. (Default: ``"mean"``)
|
| 1794 |
+
fused_log_softmax (bool): set to False if calling log_softmax outside of loss (Default: ``True``)
|
| 1795 |
+
Returns:
|
| 1796 |
+
Tensor: Loss with the reduction option applied. If ``reduction`` is ``"none"``, then size `(batch)`,
|
| 1797 |
+
otherwise scalar.
|
| 1798 |
+
"""
|
| 1799 |
+
if reduction not in ["none", "mean", "sum"]:
|
| 1800 |
+
raise ValueError('reduction should be one of "none", "mean", or "sum"')
|
| 1801 |
+
|
| 1802 |
+
if blank < 0: # reinterpret blank index if blank < 0.
|
| 1803 |
+
blank = logits.shape[-1] + blank
|
| 1804 |
+
|
| 1805 |
+
costs, _ = torch.ops.torchaudio.rnnt_loss(
|
| 1806 |
+
logits=logits,
|
| 1807 |
+
targets=targets,
|
| 1808 |
+
logit_lengths=logit_lengths,
|
| 1809 |
+
target_lengths=target_lengths,
|
| 1810 |
+
blank=blank,
|
| 1811 |
+
clamp=clamp,
|
| 1812 |
+
fused_log_softmax=fused_log_softmax,
|
| 1813 |
+
)
|
| 1814 |
+
|
| 1815 |
+
if reduction == "mean":
|
| 1816 |
+
return costs.mean()
|
| 1817 |
+
elif reduction == "sum":
|
| 1818 |
+
return costs.sum()
|
| 1819 |
+
|
| 1820 |
+
return costs
|
| 1821 |
+
|
| 1822 |
+
|
| 1823 |
+
def psd(
|
| 1824 |
+
specgram: Tensor,
|
| 1825 |
+
mask: Optional[Tensor] = None,
|
| 1826 |
+
normalize: bool = True,
|
| 1827 |
+
eps: float = 1e-10,
|
| 1828 |
+
) -> Tensor:
|
| 1829 |
+
"""Compute cross-channel power spectral density (PSD) matrix.
|
| 1830 |
+
|
| 1831 |
+
.. devices:: CPU CUDA
|
| 1832 |
+
|
| 1833 |
+
.. properties:: Autograd TorchScript
|
| 1834 |
+
|
| 1835 |
+
Args:
|
| 1836 |
+
specgram (torch.Tensor): Multi-channel complex-valued spectrum.
|
| 1837 |
+
Tensor with dimensions `(..., channel, freq, time)`.
|
| 1838 |
+
mask (torch.Tensor or None, optional): Time-Frequency mask for normalization.
|
| 1839 |
+
Tensor with dimensions `(..., freq, time)`. (Default: ``None``)
|
| 1840 |
+
normalize (bool, optional): If ``True``, normalize the mask along the time dimension. (Default: ``True``)
|
| 1841 |
+
eps (float, optional): Value to add to the denominator in mask normalization. (Default: ``1e-15``)
|
| 1842 |
+
|
| 1843 |
+
Returns:
|
| 1844 |
+
torch.Tensor: The complex-valued PSD matrix of the input spectrum.
|
| 1845 |
+
Tensor with dimensions `(..., freq, channel, channel)`
|
| 1846 |
+
"""
|
| 1847 |
+
specgram = specgram.transpose(-3, -2) # shape (freq, channel, time)
|
| 1848 |
+
# outer product:
|
| 1849 |
+
# (..., ch_1, time) x (..., ch_2, time) -> (..., time, ch_1, ch_2)
|
| 1850 |
+
psd = torch.einsum("...ct,...et->...tce", [specgram, specgram.conj()])
|
| 1851 |
+
|
| 1852 |
+
if mask is not None:
|
| 1853 |
+
if mask.shape[:-1] != specgram.shape[:-2] or mask.shape[-1] != specgram.shape[-1]:
|
| 1854 |
+
raise ValueError(
|
| 1855 |
+
"The dimensions of mask except the channel dimension should be the same as specgram."
|
| 1856 |
+
f"Found {mask.shape} for mask and {specgram.shape} for specgram."
|
| 1857 |
+
)
|
| 1858 |
+
# Normalized mask along time dimension:
|
| 1859 |
+
if normalize:
|
| 1860 |
+
mask = mask / (mask.sum(dim=-1, keepdim=True) + eps)
|
| 1861 |
+
|
| 1862 |
+
psd = psd * mask[..., None, None]
|
| 1863 |
+
|
| 1864 |
+
psd = psd.sum(dim=-3)
|
| 1865 |
+
return psd
|
| 1866 |
+
|
| 1867 |
+
|
| 1868 |
+
def _compute_mat_trace(input: torch.Tensor, dim1: int = -1, dim2: int = -2) -> torch.Tensor:
|
| 1869 |
+
r"""Compute the trace of a Tensor along ``dim1`` and ``dim2`` dimensions.
|
| 1870 |
+
|
| 1871 |
+
Args:
|
| 1872 |
+
input (torch.Tensor): Tensor with dimensions `(..., channel, channel)`.
|
| 1873 |
+
dim1 (int, optional): The first dimension of the diagonal matrix.
|
| 1874 |
+
(Default: ``-1``)
|
| 1875 |
+
dim2 (int, optional): The second dimension of the diagonal matrix.
|
| 1876 |
+
(Default: ``-2``)
|
| 1877 |
+
|
| 1878 |
+
Returns:
|
| 1879 |
+
Tensor: The trace of the input Tensor.
|
| 1880 |
+
"""
|
| 1881 |
+
if input.ndim < 2:
|
| 1882 |
+
raise ValueError("The dimension of the tensor must be at least 2.")
|
| 1883 |
+
if input.shape[dim1] != input.shape[dim2]:
|
| 1884 |
+
raise ValueError("The size of ``dim1`` and ``dim2`` must be the same.")
|
| 1885 |
+
input = torch.diagonal(input, 0, dim1=dim1, dim2=dim2)
|
| 1886 |
+
return input.sum(dim=-1)
|
| 1887 |
+
|
| 1888 |
+
|
| 1889 |
+
def _tik_reg(mat: torch.Tensor, reg: float = 1e-7, eps: float = 1e-8) -> torch.Tensor:
|
| 1890 |
+
"""Perform Tikhonov regularization (only modifying real part).
|
| 1891 |
+
|
| 1892 |
+
Args:
|
| 1893 |
+
mat (torch.Tensor): Input matrix with dimensions `(..., channel, channel)`.
|
| 1894 |
+
reg (float, optional): Regularization factor. (Default: 1e-8)
|
| 1895 |
+
eps (float, optional): Value to avoid the correlation matrix is all-zero. (Default: ``1e-8``)
|
| 1896 |
+
|
| 1897 |
+
Returns:
|
| 1898 |
+
Tensor: Regularized matrix with dimensions `(..., channel, channel)`.
|
| 1899 |
+
"""
|
| 1900 |
+
# Add eps
|
| 1901 |
+
C = mat.size(-1)
|
| 1902 |
+
eye = torch.eye(C, dtype=mat.dtype, device=mat.device)
|
| 1903 |
+
epsilon = _compute_mat_trace(mat).real[..., None, None] * reg
|
| 1904 |
+
# in case that correlation_matrix is all-zero
|
| 1905 |
+
epsilon = epsilon + eps
|
| 1906 |
+
mat = mat + epsilon * eye[..., :, :]
|
| 1907 |
+
return mat
|
| 1908 |
+
|
| 1909 |
+
|
| 1910 |
+
def _assert_psd_matrices(psd_s: torch.Tensor, psd_n: torch.Tensor) -> None:
|
| 1911 |
+
"""Assertion checks of the PSD matrices of target speech and noise.
|
| 1912 |
+
|
| 1913 |
+
Args:
|
| 1914 |
+
psd_s (torch.Tensor): The complex-valued power spectral density (PSD) matrix of target speech.
|
| 1915 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 1916 |
+
psd_n (torch.Tensor): The complex-valued power spectral density (PSD) matrix of noise.
|
| 1917 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 1918 |
+
"""
|
| 1919 |
+
if psd_s.ndim < 3 or psd_n.ndim < 3:
|
| 1920 |
+
raise ValueError(
|
| 1921 |
+
"Expected at least 3D Tensor (..., freq, channel, channel) for psd_s and psd_n. "
|
| 1922 |
+
f"Found {psd_s.shape} for psd_s and {psd_n.shape} for psd_n."
|
| 1923 |
+
)
|
| 1924 |
+
if not (psd_s.is_complex() and psd_n.is_complex()):
|
| 1925 |
+
raise TypeError(
|
| 1926 |
+
"The type of psd_s and psd_n must be ``torch.cfloat`` or ``torch.cdouble``. "
|
| 1927 |
+
f"Found {psd_s.dtype} for psd_s and {psd_n.dtype} for psd_n."
|
| 1928 |
+
)
|
| 1929 |
+
if psd_s.shape != psd_n.shape:
|
| 1930 |
+
raise ValueError(
|
| 1931 |
+
f"The dimensions of psd_s and psd_n should be the same. Found {psd_s.shape} and {psd_n.shape}."
|
| 1932 |
+
)
|
| 1933 |
+
if psd_s.shape[-1] != psd_s.shape[-2]:
|
| 1934 |
+
raise ValueError(f"The last two dimensions of psd_s should be the same. Found {psd_s.shape}.")
|
| 1935 |
+
|
| 1936 |
+
|
| 1937 |
+
def mvdr_weights_souden(
|
| 1938 |
+
psd_s: Tensor,
|
| 1939 |
+
psd_n: Tensor,
|
| 1940 |
+
reference_channel: Union[int, Tensor],
|
| 1941 |
+
diagonal_loading: bool = True,
|
| 1942 |
+
diag_eps: float = 1e-7,
|
| 1943 |
+
eps: float = 1e-8,
|
| 1944 |
+
) -> Tensor:
|
| 1945 |
+
r"""Compute the Minimum Variance Distortionless Response (*MVDR* :cite:`capon1969high`) beamforming weights
|
| 1946 |
+
by the method proposed by *Souden et, al.* :cite:`souden2009optimal`.
|
| 1947 |
+
|
| 1948 |
+
.. devices:: CPU CUDA
|
| 1949 |
+
|
| 1950 |
+
.. properties:: Autograd TorchScript
|
| 1951 |
+
|
| 1952 |
+
Given the power spectral density (PSD) matrix of target speech :math:`\bf{\Phi}_{\textbf{SS}}`,
|
| 1953 |
+
the PSD matrix of noise :math:`\bf{\Phi}_{\textbf{NN}}`, and a one-hot vector that represents the
|
| 1954 |
+
reference channel :math:`\bf{u}`, the method computes the MVDR beamforming weight martrix
|
| 1955 |
+
:math:`\textbf{w}_{\text{MVDR}}`. The formula is defined as:
|
| 1956 |
+
|
| 1957 |
+
.. math::
|
| 1958 |
+
\textbf{w}_{\text{MVDR}}(f) =
|
| 1959 |
+
\frac{{{\bf{\Phi}_{\textbf{NN}}^{-1}}(f){\bf{\Phi}_{\textbf{SS}}}}(f)}
|
| 1960 |
+
{\text{Trace}({{{\bf{\Phi}_{\textbf{NN}}^{-1}}(f) \bf{\Phi}_{\textbf{SS}}}(f))}}\bm{u}
|
| 1961 |
+
|
| 1962 |
+
Args:
|
| 1963 |
+
psd_s (torch.Tensor): The complex-valued power spectral density (PSD) matrix of target speech.
|
| 1964 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 1965 |
+
psd_n (torch.Tensor): The complex-valued power spectral density (PSD) matrix of noise.
|
| 1966 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 1967 |
+
reference_channel (int or torch.Tensor): Specifies the reference channel.
|
| 1968 |
+
If the dtype is ``int``, it represents the reference channel index.
|
| 1969 |
+
If the dtype is ``torch.Tensor``, its shape is `(..., channel)`, where the ``channel`` dimension
|
| 1970 |
+
is one-hot.
|
| 1971 |
+
diagonal_loading (bool, optional): If ``True``, enables applying diagonal loading to ``psd_n``.
|
| 1972 |
+
(Default: ``True``)
|
| 1973 |
+
diag_eps (float, optional): The coefficient multiplied to the identity matrix for diagonal loading.
|
| 1974 |
+
It is only effective when ``diagonal_loading`` is set to ``True``. (Default: ``1e-7``)
|
| 1975 |
+
eps (float, optional): Value to add to the denominator in the beamforming weight formula.
|
| 1976 |
+
(Default: ``1e-8``)
|
| 1977 |
+
|
| 1978 |
+
Returns:
|
| 1979 |
+
torch.Tensor: The complex-valued MVDR beamforming weight matrix with dimensions `(..., freq, channel)`.
|
| 1980 |
+
"""
|
| 1981 |
+
_assert_psd_matrices(psd_s, psd_n)
|
| 1982 |
+
|
| 1983 |
+
if diagonal_loading:
|
| 1984 |
+
psd_n = _tik_reg(psd_n, reg=diag_eps)
|
| 1985 |
+
numerator = torch.linalg.solve(psd_n, psd_s) # psd_n.inv() @ psd_s
|
| 1986 |
+
# ws: (..., C, C) / (...,) -> (..., C, C)
|
| 1987 |
+
ws = numerator / (_compute_mat_trace(numerator)[..., None, None] + eps)
|
| 1988 |
+
if torch.jit.isinstance(reference_channel, int):
|
| 1989 |
+
beamform_weights = ws[..., :, reference_channel]
|
| 1990 |
+
elif torch.jit.isinstance(reference_channel, Tensor):
|
| 1991 |
+
reference_channel = reference_channel.to(psd_n.dtype)
|
| 1992 |
+
# h: (..., F, C_1, C_2) x (..., C_2) -> (..., F, C_1)
|
| 1993 |
+
beamform_weights = torch.einsum("...c,...c->...", [ws, reference_channel[..., None, None, :]])
|
| 1994 |
+
else:
|
| 1995 |
+
raise TypeError(f'Expected "int" or "Tensor" for reference_channel. Found: {type(reference_channel)}.')
|
| 1996 |
+
|
| 1997 |
+
return beamform_weights
|
| 1998 |
+
|
| 1999 |
+
|
| 2000 |
+
def mvdr_weights_rtf(
|
| 2001 |
+
rtf: Tensor,
|
| 2002 |
+
psd_n: Tensor,
|
| 2003 |
+
reference_channel: Optional[Union[int, Tensor]] = None,
|
| 2004 |
+
diagonal_loading: bool = True,
|
| 2005 |
+
diag_eps: float = 1e-7,
|
| 2006 |
+
eps: float = 1e-8,
|
| 2007 |
+
) -> Tensor:
|
| 2008 |
+
r"""Compute the Minimum Variance Distortionless Response (*MVDR* :cite:`capon1969high`) beamforming weights
|
| 2009 |
+
based on the relative transfer function (RTF) and power spectral density (PSD) matrix of noise.
|
| 2010 |
+
|
| 2011 |
+
.. devices:: CPU CUDA
|
| 2012 |
+
|
| 2013 |
+
.. properties:: Autograd TorchScript
|
| 2014 |
+
|
| 2015 |
+
Given the relative transfer function (RTF) matrix or the steering vector of target speech :math:`\bm{v}`,
|
| 2016 |
+
the PSD matrix of noise :math:`\bf{\Phi}_{\textbf{NN}}`, and a one-hot vector that represents the
|
| 2017 |
+
reference channel :math:`\bf{u}`, the method computes the MVDR beamforming weight martrix
|
| 2018 |
+
:math:`\textbf{w}_{\text{MVDR}}`. The formula is defined as:
|
| 2019 |
+
|
| 2020 |
+
.. math::
|
| 2021 |
+
\textbf{w}_{\text{MVDR}}(f) =
|
| 2022 |
+
\frac{{{\bf{\Phi}_{\textbf{NN}}^{-1}}(f){\bm{v}}(f)}}
|
| 2023 |
+
{{\bm{v}^{\mathsf{H}}}(f){\bf{\Phi}_{\textbf{NN}}^{-1}}(f){\bm{v}}(f)}
|
| 2024 |
+
|
| 2025 |
+
where :math:`(.)^{\mathsf{H}}` denotes the Hermitian Conjugate operation.
|
| 2026 |
+
|
| 2027 |
+
Args:
|
| 2028 |
+
rtf (torch.Tensor): The complex-valued RTF vector of target speech.
|
| 2029 |
+
Tensor with dimensions `(..., freq, channel)`.
|
| 2030 |
+
psd_n (torch.Tensor): The complex-valued power spectral density (PSD) matrix of noise.
|
| 2031 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 2032 |
+
reference_channel (int or torch.Tensor): Specifies the reference channel.
|
| 2033 |
+
If the dtype is ``int``, it represents the reference channel index.
|
| 2034 |
+
If the dtype is ``torch.Tensor``, its shape is `(..., channel)`, where the ``channel`` dimension
|
| 2035 |
+
is one-hot.
|
| 2036 |
+
diagonal_loading (bool, optional): If ``True``, enables applying diagonal loading to ``psd_n``.
|
| 2037 |
+
(Default: ``True``)
|
| 2038 |
+
diag_eps (float, optional): The coefficient multiplied to the identity matrix for diagonal loading.
|
| 2039 |
+
It is only effective when ``diagonal_loading`` is set to ``True``. (Default: ``1e-7``)
|
| 2040 |
+
eps (float, optional): Value to add to the denominator in the beamforming weight formula.
|
| 2041 |
+
(Default: ``1e-8``)
|
| 2042 |
+
|
| 2043 |
+
Returns:
|
| 2044 |
+
torch.Tensor: The complex-valued MVDR beamforming weight matrix with dimensions `(..., freq, channel)`.
|
| 2045 |
+
"""
|
| 2046 |
+
if rtf.ndim < 2:
|
| 2047 |
+
raise ValueError(f"Expected at least 2D Tensor (..., freq, channel) for rtf. Found {rtf.shape}.")
|
| 2048 |
+
if psd_n.ndim < 3:
|
| 2049 |
+
raise ValueError(f"Expected at least 3D Tensor (..., freq, channel, channel) for psd_n. Found {psd_n.shape}.")
|
| 2050 |
+
if not (rtf.is_complex() and psd_n.is_complex()):
|
| 2051 |
+
raise TypeError(
|
| 2052 |
+
"The type of rtf and psd_n must be ``torch.cfloat`` or ``torch.cdouble``. "
|
| 2053 |
+
f"Found {rtf.dtype} for rtf and {psd_n.dtype} for psd_n."
|
| 2054 |
+
)
|
| 2055 |
+
if rtf.shape != psd_n.shape[:-1]:
|
| 2056 |
+
raise ValueError(
|
| 2057 |
+
"The dimensions of rtf and the dimensions withou the last dimension of psd_n should be the same. "
|
| 2058 |
+
f"Found {rtf.shape} for rtf and {psd_n.shape} for psd_n."
|
| 2059 |
+
)
|
| 2060 |
+
if psd_n.shape[-1] != psd_n.shape[-2]:
|
| 2061 |
+
raise ValueError(f"The last two dimensions of psd_n should be the same. Found {psd_n.shape}.")
|
| 2062 |
+
|
| 2063 |
+
if diagonal_loading:
|
| 2064 |
+
psd_n = _tik_reg(psd_n, reg=diag_eps)
|
| 2065 |
+
# numerator = psd_n.inv() @ stv
|
| 2066 |
+
numerator = torch.linalg.solve(psd_n, rtf.unsqueeze(-1)).squeeze(-1) # (..., freq, channel)
|
| 2067 |
+
# denominator = stv^H @ psd_n.inv() @ stv
|
| 2068 |
+
denominator = torch.einsum("...d,...d->...", [rtf.conj(), numerator])
|
| 2069 |
+
beamform_weights = numerator / (denominator.real.unsqueeze(-1) + eps)
|
| 2070 |
+
# normalize the numerator
|
| 2071 |
+
if reference_channel is not None:
|
| 2072 |
+
if torch.jit.isinstance(reference_channel, int):
|
| 2073 |
+
scale = rtf[..., reference_channel].conj()
|
| 2074 |
+
elif torch.jit.isinstance(reference_channel, Tensor):
|
| 2075 |
+
reference_channel = reference_channel.to(psd_n.dtype)
|
| 2076 |
+
scale = torch.einsum("...c,...c->...", [rtf.conj(), reference_channel[..., None, :]])
|
| 2077 |
+
else:
|
| 2078 |
+
raise TypeError(f'Expected "int" or "Tensor" for reference_channel. Found: {type(reference_channel)}.')
|
| 2079 |
+
|
| 2080 |
+
beamform_weights = beamform_weights * scale[..., None]
|
| 2081 |
+
|
| 2082 |
+
return beamform_weights
|
| 2083 |
+
|
| 2084 |
+
|
| 2085 |
+
def rtf_evd(psd_s: Tensor) -> Tensor:
|
| 2086 |
+
r"""Estimate the relative transfer function (RTF) or the steering vector by eigenvalue decomposition.
|
| 2087 |
+
|
| 2088 |
+
.. devices:: CPU CUDA
|
| 2089 |
+
|
| 2090 |
+
.. properties:: TorchScript
|
| 2091 |
+
|
| 2092 |
+
Args:
|
| 2093 |
+
psd_s (Tensor): The complex-valued power spectral density (PSD) matrix of target speech.
|
| 2094 |
+
Tensor of dimension `(..., freq, channel, channel)`
|
| 2095 |
+
|
| 2096 |
+
Returns:
|
| 2097 |
+
Tensor: The estimated complex-valued RTF of target speech.
|
| 2098 |
+
Tensor of dimension `(..., freq, channel)`
|
| 2099 |
+
"""
|
| 2100 |
+
if not psd_s.is_complex():
|
| 2101 |
+
raise TypeError(f"The type of psd_s must be ``torch.cfloat`` or ``torch.cdouble``. Found {psd_s.dtype}.")
|
| 2102 |
+
if psd_s.shape[-1] != psd_s.shape[-2]:
|
| 2103 |
+
raise ValueError(f"The last two dimensions of psd_s should be the same. Found {psd_s.shape}.")
|
| 2104 |
+
_, v = torch.linalg.eigh(psd_s) # v is sorted along with eigenvalues in ascending order
|
| 2105 |
+
rtf = v[..., -1] # choose the eigenvector with max eigenvalue
|
| 2106 |
+
return rtf
|
| 2107 |
+
|
| 2108 |
+
|
| 2109 |
+
def rtf_power(
|
| 2110 |
+
psd_s: Tensor,
|
| 2111 |
+
psd_n: Tensor,
|
| 2112 |
+
reference_channel: Union[int, Tensor],
|
| 2113 |
+
n_iter: int = 3,
|
| 2114 |
+
diagonal_loading: bool = True,
|
| 2115 |
+
diag_eps: float = 1e-7,
|
| 2116 |
+
) -> Tensor:
|
| 2117 |
+
r"""Estimate the relative transfer function (RTF) or the steering vector by the power method.
|
| 2118 |
+
|
| 2119 |
+
.. devices:: CPU CUDA
|
| 2120 |
+
|
| 2121 |
+
.. properties:: Autograd TorchScript
|
| 2122 |
+
|
| 2123 |
+
Args:
|
| 2124 |
+
psd_s (torch.Tensor): The complex-valued power spectral density (PSD) matrix of target speech.
|
| 2125 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 2126 |
+
psd_n (torch.Tensor): The complex-valued power spectral density (PSD) matrix of noise.
|
| 2127 |
+
Tensor with dimensions `(..., freq, channel, channel)`.
|
| 2128 |
+
reference_channel (int or torch.Tensor): Specifies the reference channel.
|
| 2129 |
+
If the dtype is ``int``, it represents the reference channel index.
|
| 2130 |
+
If the dtype is ``torch.Tensor``, its shape is `(..., channel)`, where the ``channel`` dimension
|
| 2131 |
+
is one-hot.
|
| 2132 |
+
diagonal_loading (bool, optional): If ``True``, enables applying diagonal loading to ``psd_n``.
|
| 2133 |
+
(Default: ``True``)
|
| 2134 |
+
diag_eps (float, optional): The coefficient multiplied to the identity matrix for diagonal loading.
|
| 2135 |
+
It is only effective when ``diagonal_loading`` is set to ``True``. (Default: ``1e-7``)
|
| 2136 |
+
|
| 2137 |
+
Returns:
|
| 2138 |
+
torch.Tensor: The estimated complex-valued RTF of target speech.
|
| 2139 |
+
Tensor of dimension `(..., freq, channel)`.
|
| 2140 |
+
"""
|
| 2141 |
+
_assert_psd_matrices(psd_s, psd_n)
|
| 2142 |
+
if n_iter <= 0:
|
| 2143 |
+
raise ValueError("The number of iteration must be greater than 0.")
|
| 2144 |
+
|
| 2145 |
+
# Apply diagonal loading to psd_n to improve robustness.
|
| 2146 |
+
if diagonal_loading:
|
| 2147 |
+
psd_n = _tik_reg(psd_n, reg=diag_eps)
|
| 2148 |
+
# phi is regarded as the first iteration
|
| 2149 |
+
phi = torch.linalg.solve(psd_n, psd_s) # psd_n.inv() @ psd_s
|
| 2150 |
+
if torch.jit.isinstance(reference_channel, int):
|
| 2151 |
+
rtf = phi[..., reference_channel]
|
| 2152 |
+
elif torch.jit.isinstance(reference_channel, Tensor):
|
| 2153 |
+
reference_channel = reference_channel.to(psd_n.dtype)
|
| 2154 |
+
rtf = torch.einsum("...c,...c->...", [phi, reference_channel[..., None, None, :]])
|
| 2155 |
+
else:
|
| 2156 |
+
raise TypeError(f'Expected "int" or "Tensor" for reference_channel. Found: {type(reference_channel)}.')
|
| 2157 |
+
rtf = rtf.unsqueeze(-1) # (..., freq, channel, 1)
|
| 2158 |
+
if n_iter >= 2:
|
| 2159 |
+
# The number of iterations in the for loop is `n_iter - 2`
|
| 2160 |
+
# because the `phi` above and `torch.matmul(psd_s, rtf)` are regarded as
|
| 2161 |
+
# two iterations.
|
| 2162 |
+
for _ in range(n_iter - 2):
|
| 2163 |
+
rtf = torch.matmul(phi, rtf)
|
| 2164 |
+
rtf = torch.matmul(psd_s, rtf)
|
| 2165 |
+
else:
|
| 2166 |
+
# if there is only one iteration, the rtf is the psd_s[..., referenc_channel]
|
| 2167 |
+
# which is psd_n @ phi @ ref_channel
|
| 2168 |
+
rtf = torch.matmul(psd_n, rtf)
|
| 2169 |
+
return rtf.squeeze(-1)
|
| 2170 |
+
|
| 2171 |
+
|
| 2172 |
+
def apply_beamforming(beamform_weights: Tensor, specgram: Tensor) -> Tensor:
|
| 2173 |
+
r"""Apply the beamforming weight to the multi-channel noisy spectrum to obtain the single-channel enhanced spectrum.
|
| 2174 |
+
|
| 2175 |
+
.. devices:: CPU CUDA
|
| 2176 |
+
|
| 2177 |
+
.. properties:: Autograd TorchScript
|
| 2178 |
+
|
| 2179 |
+
.. math::
|
| 2180 |
+
\hat{\textbf{S}}(f) = \textbf{w}_{\text{bf}}(f)^{\mathsf{H}} \textbf{Y}(f)
|
| 2181 |
+
|
| 2182 |
+
where :math:`\textbf{w}_{\text{bf}}(f)` is the beamforming weight for the :math:`f`-th frequency bin,
|
| 2183 |
+
:math:`\textbf{Y}` is the multi-channel spectrum for the :math:`f`-th frequency bin.
|
| 2184 |
+
|
| 2185 |
+
Args:
|
| 2186 |
+
beamform_weights (Tensor): The complex-valued beamforming weight matrix.
|
| 2187 |
+
Tensor of dimension `(..., freq, channel)`
|
| 2188 |
+
specgram (Tensor): The multi-channel complex-valued noisy spectrum.
|
| 2189 |
+
Tensor of dimension `(..., channel, freq, time)`
|
| 2190 |
+
|
| 2191 |
+
Returns:
|
| 2192 |
+
Tensor: The single-channel complex-valued enhanced spectrum.
|
| 2193 |
+
Tensor of dimension `(..., freq, time)`
|
| 2194 |
+
"""
|
| 2195 |
+
if beamform_weights.shape[:-2] != specgram.shape[:-3]:
|
| 2196 |
+
raise ValueError(
|
| 2197 |
+
"The dimensions except the last two dimensions of beamform_weights should be the same "
|
| 2198 |
+
"as the dimensions except the last three dimensions of specgram. "
|
| 2199 |
+
f"Found {beamform_weights.shape} for beamform_weights and {specgram.shape} for specgram."
|
| 2200 |
+
)
|
| 2201 |
+
|
| 2202 |
+
if not (beamform_weights.is_complex() and specgram.is_complex()):
|
| 2203 |
+
raise TypeError(
|
| 2204 |
+
"The type of beamform_weights and specgram must be ``torch.cfloat`` or ``torch.cdouble``. "
|
| 2205 |
+
f"Found {beamform_weights.dtype} for beamform_weights and {specgram.dtype} for specgram."
|
| 2206 |
+
)
|
| 2207 |
+
|
| 2208 |
+
# (..., freq, channel) x (..., channel, freq, time) -> (..., freq, time)
|
| 2209 |
+
specgram_enhanced = torch.einsum("...fc,...cft->...ft", [beamform_weights.conj(), specgram])
|
| 2210 |
+
return specgram_enhanced
|
| 2211 |
+
|
| 2212 |
+
|
| 2213 |
+
def _check_shape_compatible(x: torch.Tensor, y: torch.Tensor) -> None:
|
| 2214 |
+
if x.ndim != y.ndim:
|
| 2215 |
+
raise ValueError(f"The operands must be the same dimension (got {x.ndim} and {y.ndim}).")
|
| 2216 |
+
|
| 2217 |
+
for i in range(x.ndim - 1):
|
| 2218 |
+
xi = x.size(i)
|
| 2219 |
+
yi = y.size(i)
|
| 2220 |
+
if xi == yi or xi == 1 or yi == 1:
|
| 2221 |
+
continue
|
| 2222 |
+
raise ValueError(f"Leading dimensions of x and y are not broadcastable (got {x.shape} and {y.shape}).")
|
| 2223 |
+
|
| 2224 |
+
|
| 2225 |
+
def _check_convolve_mode(mode: str) -> None:
|
| 2226 |
+
valid_convolve_modes = ["full", "valid", "same"]
|
| 2227 |
+
if mode not in valid_convolve_modes:
|
| 2228 |
+
raise ValueError(f"Unrecognized mode value '{mode}'. Please specify one of {valid_convolve_modes}.")
|
| 2229 |
+
|
| 2230 |
+
|
| 2231 |
+
def _apply_convolve_mode(conv_result: torch.Tensor, x_length: int, y_length: int, mode: str) -> torch.Tensor:
|
| 2232 |
+
valid_convolve_modes = ["full", "valid", "same"]
|
| 2233 |
+
if mode == "full":
|
| 2234 |
+
return conv_result
|
| 2235 |
+
elif mode == "valid":
|
| 2236 |
+
target_length = max(x_length, y_length) - min(x_length, y_length) + 1
|
| 2237 |
+
start_idx = (conv_result.size(-1) - target_length) // 2
|
| 2238 |
+
return conv_result[..., start_idx : start_idx + target_length]
|
| 2239 |
+
elif mode == "same":
|
| 2240 |
+
start_idx = (conv_result.size(-1) - x_length) // 2
|
| 2241 |
+
return conv_result[..., start_idx : start_idx + x_length]
|
| 2242 |
+
else:
|
| 2243 |
+
raise ValueError(f"Unrecognized mode value '{mode}'. Please specify one of {valid_convolve_modes}.")
|
| 2244 |
+
|
| 2245 |
+
|
| 2246 |
+
def fftconvolve(x: torch.Tensor, y: torch.Tensor, mode: str = "full") -> torch.Tensor:
|
| 2247 |
+
r"""
|
| 2248 |
+
Convolves inputs along their last dimension using FFT. For inputs with large last dimensions, this function
|
| 2249 |
+
is generally much faster than :meth:`convolve`.
|
| 2250 |
+
Note that, in contrast to :meth:`torch.nn.functional.conv1d`, which actually applies the valid cross-correlation
|
| 2251 |
+
operator, this function applies the true `convolution`_ operator.
|
| 2252 |
+
Also note that this function can only output float tensors (int tensor inputs will be cast to float).
|
| 2253 |
+
|
| 2254 |
+
.. devices:: CPU CUDA
|
| 2255 |
+
|
| 2256 |
+
.. properties:: Autograd TorchScript
|
| 2257 |
+
|
| 2258 |
+
Args:
|
| 2259 |
+
x (torch.Tensor): First convolution operand, with shape `(..., N)`.
|
| 2260 |
+
y (torch.Tensor): Second convolution operand, with shape `(..., M)`
|
| 2261 |
+
(leading dimensions must be broadcast-able with those of ``x``).
|
| 2262 |
+
mode (str, optional): Must be one of ("full", "valid", "same").
|
| 2263 |
+
|
| 2264 |
+
* "full": Returns the full convolution result, with shape `(..., N + M - 1)`. (Default)
|
| 2265 |
+
* "valid": Returns the segment of the full convolution result corresponding to where
|
| 2266 |
+
the two inputs overlap completely, with shape `(..., max(N, M) - min(N, M) + 1)`.
|
| 2267 |
+
* "same": Returns the center segment of the full convolution result, with shape `(..., N)`.
|
| 2268 |
+
|
| 2269 |
+
Returns:
|
| 2270 |
+
torch.Tensor: Result of convolving ``x`` and ``y``, with shape `(..., L)`, where
|
| 2271 |
+
the leading dimensions match those of ``x`` and `L` is dictated by ``mode``.
|
| 2272 |
+
|
| 2273 |
+
.. _convolution:
|
| 2274 |
+
https://en.wikipedia.org/wiki/Convolution
|
| 2275 |
+
"""
|
| 2276 |
+
_check_shape_compatible(x, y)
|
| 2277 |
+
_check_convolve_mode(mode)
|
| 2278 |
+
|
| 2279 |
+
n = x.size(-1) + y.size(-1) - 1
|
| 2280 |
+
fresult = torch.fft.rfft(x, n=n) * torch.fft.rfft(y, n=n)
|
| 2281 |
+
result = torch.fft.irfft(fresult, n=n)
|
| 2282 |
+
return _apply_convolve_mode(result, x.size(-1), y.size(-1), mode)
|
| 2283 |
+
|
| 2284 |
+
|
| 2285 |
+
def convolve(x: torch.Tensor, y: torch.Tensor, mode: str = "full") -> torch.Tensor:
|
| 2286 |
+
r"""
|
| 2287 |
+
Convolves inputs along their last dimension using the direct method.
|
| 2288 |
+
Note that, in contrast to :meth:`torch.nn.functional.conv1d`, which actually applies the valid cross-correlation
|
| 2289 |
+
operator, this function applies the true `convolution`_ operator.
|
| 2290 |
+
|
| 2291 |
+
.. devices:: CPU CUDA
|
| 2292 |
+
|
| 2293 |
+
.. properties:: Autograd TorchScript
|
| 2294 |
+
|
| 2295 |
+
Args:
|
| 2296 |
+
x (torch.Tensor): First convolution operand, with shape `(..., N)`.
|
| 2297 |
+
y (torch.Tensor): Second convolution operand, with shape `(..., M)`
|
| 2298 |
+
(leading dimensions must be broadcast-able with those of ``x``).
|
| 2299 |
+
mode (str, optional): Must be one of ("full", "valid", "same").
|
| 2300 |
+
|
| 2301 |
+
* "full": Returns the full convolution result, with shape `(..., N + M - 1)`. (Default)
|
| 2302 |
+
* "valid": Returns the segment of the full convolution result corresponding to where
|
| 2303 |
+
the two inputs overlap completely, with shape `(..., max(N, M) - min(N, M) + 1)`.
|
| 2304 |
+
* "same": Returns the center segment of the full convolution result, with shape `(..., N)`.
|
| 2305 |
+
|
| 2306 |
+
Returns:
|
| 2307 |
+
torch.Tensor: Result of convolving ``x`` and ``y``, with shape `(..., L)`, where
|
| 2308 |
+
the leading dimensions match those of ``x`` and `L` is dictated by ``mode``.
|
| 2309 |
+
|
| 2310 |
+
.. _convolution:
|
| 2311 |
+
https://en.wikipedia.org/wiki/Convolution
|
| 2312 |
+
"""
|
| 2313 |
+
_check_shape_compatible(x, y)
|
| 2314 |
+
_check_convolve_mode(mode)
|
| 2315 |
+
|
| 2316 |
+
x_size, y_size = x.size(-1), y.size(-1)
|
| 2317 |
+
|
| 2318 |
+
if x.size(-1) < y.size(-1):
|
| 2319 |
+
x, y = y, x
|
| 2320 |
+
|
| 2321 |
+
if x.shape[:-1] != y.shape[:-1]:
|
| 2322 |
+
new_shape = [max(i, j) for i, j in zip(x.shape[:-1], y.shape[:-1])]
|
| 2323 |
+
x = x.broadcast_to(new_shape + [x.shape[-1]])
|
| 2324 |
+
y = y.broadcast_to(new_shape + [y.shape[-1]])
|
| 2325 |
+
|
| 2326 |
+
num_signals = torch.tensor(x.shape[:-1]).prod()
|
| 2327 |
+
reshaped_x = x.reshape((int(num_signals), x.size(-1)))
|
| 2328 |
+
reshaped_y = y.reshape((int(num_signals), y.size(-1)))
|
| 2329 |
+
output = torch.nn.functional.conv1d(
|
| 2330 |
+
input=reshaped_x,
|
| 2331 |
+
weight=reshaped_y.flip(-1).unsqueeze(1),
|
| 2332 |
+
stride=1,
|
| 2333 |
+
groups=reshaped_x.size(0),
|
| 2334 |
+
padding=reshaped_y.size(-1) - 1,
|
| 2335 |
+
)
|
| 2336 |
+
output_shape = x.shape[:-1] + (-1,)
|
| 2337 |
+
result = output.reshape(output_shape)
|
| 2338 |
+
return _apply_convolve_mode(result, x_size, y_size, mode)
|
| 2339 |
+
|
| 2340 |
+
|
| 2341 |
+
def add_noise(
|
| 2342 |
+
waveform: torch.Tensor, noise: torch.Tensor, snr: torch.Tensor, lengths: Optional[torch.Tensor] = None
|
| 2343 |
+
) -> torch.Tensor:
|
| 2344 |
+
r"""Scales and adds noise to waveform per signal-to-noise ratio.
|
| 2345 |
+
|
| 2346 |
+
Specifically, for each pair of waveform vector :math:`x \in \mathbb{R}^L` and noise vector
|
| 2347 |
+
:math:`n \in \mathbb{R}^L`, the function computes output :math:`y` as
|
| 2348 |
+
|
| 2349 |
+
.. math::
|
| 2350 |
+
y = x + a n \, \text{,}
|
| 2351 |
+
|
| 2352 |
+
where
|
| 2353 |
+
|
| 2354 |
+
.. math::
|
| 2355 |
+
a = \sqrt{ \frac{ ||x||_{2}^{2} }{ ||n||_{2}^{2} } \cdot 10^{-\frac{\text{SNR}}{10}} } \, \text{,}
|
| 2356 |
+
|
| 2357 |
+
with :math:`\text{SNR}` being the desired signal-to-noise ratio between :math:`x` and :math:`n`, in dB.
|
| 2358 |
+
|
| 2359 |
+
Note that this function broadcasts singleton leading dimensions in its inputs in a manner that is
|
| 2360 |
+
consistent with the above formulae and PyTorch's broadcasting semantics.
|
| 2361 |
+
|
| 2362 |
+
.. devices:: CPU CUDA
|
| 2363 |
+
|
| 2364 |
+
.. properties:: Autograd TorchScript
|
| 2365 |
+
|
| 2366 |
+
Args:
|
| 2367 |
+
waveform (torch.Tensor): Input waveform, with shape `(..., L)`.
|
| 2368 |
+
noise (torch.Tensor): Noise, with shape `(..., L)` (same shape as ``waveform``).
|
| 2369 |
+
snr (torch.Tensor): Signal-to-noise ratios in dB, with shape `(...,)`.
|
| 2370 |
+
lengths (torch.Tensor or None, optional): Valid lengths of signals in ``waveform`` and ``noise``, with shape
|
| 2371 |
+
`(...,)` (leading dimensions must match those of ``waveform``). If ``None``, all elements in ``waveform``
|
| 2372 |
+
and ``noise`` are treated as valid. (Default: ``None``)
|
| 2373 |
+
|
| 2374 |
+
Returns:
|
| 2375 |
+
torch.Tensor: Result of scaling and adding ``noise`` to ``waveform``, with shape `(..., L)`
|
| 2376 |
+
(same shape as ``waveform``).
|
| 2377 |
+
"""
|
| 2378 |
+
|
| 2379 |
+
if not (waveform.ndim - 1 == noise.ndim - 1 == snr.ndim and (lengths is None or lengths.ndim == snr.ndim)):
|
| 2380 |
+
raise ValueError("Input leading dimensions don't match.")
|
| 2381 |
+
|
| 2382 |
+
L = waveform.size(-1)
|
| 2383 |
+
|
| 2384 |
+
if L != noise.size(-1):
|
| 2385 |
+
raise ValueError(f"Length dimensions of waveform and noise don't match (got {L} and {noise.size(-1)}).")
|
| 2386 |
+
|
| 2387 |
+
# compute scale
|
| 2388 |
+
if lengths is not None:
|
| 2389 |
+
mask = torch.arange(0, L, device=lengths.device).expand(waveform.shape) < lengths.unsqueeze(
|
| 2390 |
+
-1
|
| 2391 |
+
) # (*, L) < (*, 1) = (*, L)
|
| 2392 |
+
masked_waveform = waveform * mask
|
| 2393 |
+
masked_noise = noise * mask
|
| 2394 |
+
else:
|
| 2395 |
+
masked_waveform = waveform
|
| 2396 |
+
masked_noise = noise
|
| 2397 |
+
|
| 2398 |
+
energy_signal = torch.linalg.vector_norm(masked_waveform, ord=2, dim=-1) ** 2 # (*,)
|
| 2399 |
+
energy_noise = torch.linalg.vector_norm(masked_noise, ord=2, dim=-1) ** 2 # (*,)
|
| 2400 |
+
original_snr_db = 10 * (torch.log10(energy_signal) - torch.log10(energy_noise))
|
| 2401 |
+
scale = 10 ** ((original_snr_db - snr) / 20.0) # (*,)
|
| 2402 |
+
|
| 2403 |
+
# scale noise
|
| 2404 |
+
scaled_noise = scale.unsqueeze(-1) * noise # (*, 1) * (*, L) = (*, L)
|
| 2405 |
+
|
| 2406 |
+
return waveform + scaled_noise # (*, L)
|
| 2407 |
+
|
| 2408 |
+
|
| 2409 |
+
def speed(
|
| 2410 |
+
waveform: torch.Tensor, orig_freq: int, factor: float, lengths: Optional[torch.Tensor] = None
|
| 2411 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 2412 |
+
r"""Adjusts waveform speed.
|
| 2413 |
+
|
| 2414 |
+
.. devices:: CPU CUDA
|
| 2415 |
+
|
| 2416 |
+
.. properties:: Autograd TorchScript
|
| 2417 |
+
|
| 2418 |
+
Args:
|
| 2419 |
+
waveform (torch.Tensor): Input signals, with shape `(..., time)`.
|
| 2420 |
+
orig_freq (int): Original frequency of the signals in ``waveform``.
|
| 2421 |
+
factor (float): Factor by which to adjust speed of input. Values greater than 1.0
|
| 2422 |
+
compress ``waveform`` in time, whereas values less than 1.0 stretch ``waveform`` in time.
|
| 2423 |
+
lengths (torch.Tensor or None, optional): Valid lengths of signals in ``waveform``, with shape `(...)`.
|
| 2424 |
+
If ``None``, all elements in ``waveform`` are treated as valid. (Default: ``None``)
|
| 2425 |
+
|
| 2426 |
+
Returns:
|
| 2427 |
+
(torch.Tensor, torch.Tensor or None):
|
| 2428 |
+
torch.Tensor
|
| 2429 |
+
Speed-adjusted waveform, with shape `(..., new_time).`
|
| 2430 |
+
torch.Tensor or None
|
| 2431 |
+
If ``lengths`` is not ``None``, valid lengths of signals in speed-adjusted waveform,
|
| 2432 |
+
with shape `(...)`; otherwise, ``None``.
|
| 2433 |
+
"""
|
| 2434 |
+
|
| 2435 |
+
source_sample_rate = int(factor * orig_freq)
|
| 2436 |
+
target_sample_rate = int(orig_freq)
|
| 2437 |
+
|
| 2438 |
+
gcd = math.gcd(source_sample_rate, target_sample_rate)
|
| 2439 |
+
source_sample_rate = source_sample_rate // gcd
|
| 2440 |
+
target_sample_rate = target_sample_rate // gcd
|
| 2441 |
+
|
| 2442 |
+
if lengths is None:
|
| 2443 |
+
out_lengths = None
|
| 2444 |
+
else:
|
| 2445 |
+
out_lengths = torch.ceil(lengths * target_sample_rate / source_sample_rate).to(lengths.dtype)
|
| 2446 |
+
|
| 2447 |
+
return resample(waveform, source_sample_rate, target_sample_rate), out_lengths
|
| 2448 |
+
|
| 2449 |
+
|
| 2450 |
+
def preemphasis(waveform, coeff: float = 0.97) -> torch.Tensor:
|
| 2451 |
+
r"""Pre-emphasizes a waveform along its last dimension, i.e.
|
| 2452 |
+
for each signal :math:`x` in ``waveform``, computes
|
| 2453 |
+
output :math:`y` as
|
| 2454 |
+
|
| 2455 |
+
.. math::
|
| 2456 |
+
y[i] = x[i] - \text{coeff} \cdot x[i - 1]
|
| 2457 |
+
|
| 2458 |
+
.. devices:: CPU CUDA
|
| 2459 |
+
|
| 2460 |
+
.. properties:: Autograd TorchScript
|
| 2461 |
+
|
| 2462 |
+
Args:
|
| 2463 |
+
waveform (torch.Tensor): Waveform, with shape `(..., N)`.
|
| 2464 |
+
coeff (float, optional): Pre-emphasis coefficient. Typically between 0.0 and 1.0.
|
| 2465 |
+
(Default: 0.97)
|
| 2466 |
+
|
| 2467 |
+
Returns:
|
| 2468 |
+
torch.Tensor: Pre-emphasized waveform, with shape `(..., N)`.
|
| 2469 |
+
"""
|
| 2470 |
+
waveform = waveform.clone()
|
| 2471 |
+
waveform[..., 1:] -= coeff * waveform[..., :-1]
|
| 2472 |
+
return waveform
|
| 2473 |
+
|
| 2474 |
+
|
| 2475 |
+
def deemphasis(waveform, coeff: float = 0.97) -> torch.Tensor:
|
| 2476 |
+
r"""De-emphasizes a waveform along its last dimension.
|
| 2477 |
+
Inverse of :meth:`preemphasis`. Concretely, for each signal
|
| 2478 |
+
:math:`x` in ``waveform``, computes output :math:`y` as
|
| 2479 |
+
|
| 2480 |
+
.. math::
|
| 2481 |
+
y[i] = x[i] + \text{coeff} \cdot y[i - 1]
|
| 2482 |
+
|
| 2483 |
+
.. devices:: CPU CUDA
|
| 2484 |
+
|
| 2485 |
+
.. properties:: Autograd TorchScript
|
| 2486 |
+
|
| 2487 |
+
Args:
|
| 2488 |
+
waveform (torch.Tensor): Waveform, with shape `(..., N)`.
|
| 2489 |
+
coeff (float, optional): De-emphasis coefficient. Typically between 0.0 and 1.0.
|
| 2490 |
+
(Default: 0.97)
|
| 2491 |
+
|
| 2492 |
+
Returns:
|
| 2493 |
+
torch.Tensor: De-emphasized waveform, with shape `(..., N)`.
|
| 2494 |
+
"""
|
| 2495 |
+
a_coeffs = torch.tensor([1.0, -coeff], dtype=waveform.dtype, device=waveform.device)
|
| 2496 |
+
b_coeffs = torch.tensor([1.0, 0.0], dtype=waveform.dtype, device=waveform.device)
|
| 2497 |
+
return torchaudio.functional.lfilter(waveform, a_coeffs=a_coeffs, b_coeffs=b_coeffs)
|
| 2498 |
+
|
| 2499 |
+
|
| 2500 |
+
def frechet_distance(mu_x, sigma_x, mu_y, sigma_y):
|
| 2501 |
+
r"""Computes the Fréchet distance between two multivariate normal distributions :cite:`dowson1982frechet`.
|
| 2502 |
+
|
| 2503 |
+
Concretely, for multivariate Gaussians :math:`X(\mu_X, \Sigma_X)`
|
| 2504 |
+
and :math:`Y(\mu_Y, \Sigma_Y)`, the function computes and returns :math:`F` as
|
| 2505 |
+
|
| 2506 |
+
.. math::
|
| 2507 |
+
F(X, Y) = || \mu_X - \mu_Y ||_2^2
|
| 2508 |
+
+ \text{Tr}\left( \Sigma_X + \Sigma_Y - 2 \sqrt{\Sigma_X \Sigma_Y} \right)
|
| 2509 |
+
|
| 2510 |
+
Args:
|
| 2511 |
+
mu_x (torch.Tensor): mean :math:`\mu_X` of multivariate Gaussian :math:`X`, with shape `(N,)`.
|
| 2512 |
+
sigma_x (torch.Tensor): covariance matrix :math:`\Sigma_X` of :math:`X`, with shape `(N, N)`.
|
| 2513 |
+
mu_y (torch.Tensor): mean :math:`\mu_Y` of multivariate Gaussian :math:`Y`, with shape `(N,)`.
|
| 2514 |
+
sigma_y (torch.Tensor): covariance matrix :math:`\Sigma_Y` of :math:`Y`, with shape `(N, N)`.
|
| 2515 |
+
|
| 2516 |
+
Returns:
|
| 2517 |
+
torch.Tensor: the Fréchet distance between :math:`X` and :math:`Y`.
|
| 2518 |
+
"""
|
| 2519 |
+
if len(mu_x.size()) != 1:
|
| 2520 |
+
raise ValueError(f"Input mu_x must be one-dimensional; got dimension {len(mu_x.size())}.")
|
| 2521 |
+
if len(sigma_x.size()) != 2:
|
| 2522 |
+
raise ValueError(f"Input sigma_x must be two-dimensional; got dimension {len(sigma_x.size())}.")
|
| 2523 |
+
if sigma_x.size(0) != sigma_x.size(1) != mu_x.size(0):
|
| 2524 |
+
raise ValueError("Each of sigma_x's dimensions must match mu_x's size.")
|
| 2525 |
+
if mu_x.size() != mu_y.size():
|
| 2526 |
+
raise ValueError(f"Inputs mu_x and mu_y must have the same shape; got {mu_x.size()} and {mu_y.size()}.")
|
| 2527 |
+
if sigma_x.size() != sigma_y.size():
|
| 2528 |
+
raise ValueError(
|
| 2529 |
+
f"Inputs sigma_x and sigma_y must have the same shape; got {sigma_x.size()} and {sigma_y.size()}."
|
| 2530 |
+
)
|
| 2531 |
+
|
| 2532 |
+
a = (mu_x - mu_y).square().sum()
|
| 2533 |
+
b = sigma_x.trace() + sigma_y.trace()
|
| 2534 |
+
c = torch.linalg.eigvals(sigma_x @ sigma_y).sqrt().real.sum()
|
| 2535 |
+
return a + b - 2 * c
|
lib/python3.12/site-packages/torchaudio/lib/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/torchaudio/lib/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (193 Bytes). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ._hdemucs import HDemucs, hdemucs_high, hdemucs_low, hdemucs_medium
|
| 2 |
+
from .conformer import Conformer
|
| 3 |
+
from .conv_tasnet import conv_tasnet_base, ConvTasNet
|
| 4 |
+
from .deepspeech import DeepSpeech
|
| 5 |
+
from .emformer import Emformer
|
| 6 |
+
from .rnnt import emformer_rnnt_base, emformer_rnnt_model, RNNT
|
| 7 |
+
from .rnnt_decoder import Hypothesis, RNNTBeamSearch
|
| 8 |
+
from .squim import (
|
| 9 |
+
squim_objective_base,
|
| 10 |
+
squim_objective_model,
|
| 11 |
+
squim_subjective_base,
|
| 12 |
+
squim_subjective_model,
|
| 13 |
+
SquimObjective,
|
| 14 |
+
SquimSubjective,
|
| 15 |
+
)
|
| 16 |
+
from .tacotron2 import Tacotron2
|
| 17 |
+
from .wav2letter import Wav2Letter
|
| 18 |
+
from .wav2vec2 import (
|
| 19 |
+
hubert_base,
|
| 20 |
+
hubert_large,
|
| 21 |
+
hubert_pretrain_base,
|
| 22 |
+
hubert_pretrain_large,
|
| 23 |
+
hubert_pretrain_model,
|
| 24 |
+
hubert_pretrain_xlarge,
|
| 25 |
+
hubert_xlarge,
|
| 26 |
+
HuBERTPretrainModel,
|
| 27 |
+
wav2vec2_base,
|
| 28 |
+
wav2vec2_large,
|
| 29 |
+
wav2vec2_large_lv60k,
|
| 30 |
+
wav2vec2_model,
|
| 31 |
+
wav2vec2_xlsr_1b,
|
| 32 |
+
wav2vec2_xlsr_2b,
|
| 33 |
+
wav2vec2_xlsr_300m,
|
| 34 |
+
Wav2Vec2Model,
|
| 35 |
+
wavlm_base,
|
| 36 |
+
wavlm_large,
|
| 37 |
+
wavlm_model,
|
| 38 |
+
)
|
| 39 |
+
from .wavernn import WaveRNN
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
__all__ = [
|
| 43 |
+
"Wav2Letter",
|
| 44 |
+
"WaveRNN",
|
| 45 |
+
"ConvTasNet",
|
| 46 |
+
"conv_tasnet_base",
|
| 47 |
+
"DeepSpeech",
|
| 48 |
+
"Wav2Vec2Model",
|
| 49 |
+
"HuBERTPretrainModel",
|
| 50 |
+
"wavlm_model",
|
| 51 |
+
"wavlm_base",
|
| 52 |
+
"wavlm_large",
|
| 53 |
+
"wav2vec2_model",
|
| 54 |
+
"wav2vec2_base",
|
| 55 |
+
"wav2vec2_large",
|
| 56 |
+
"wav2vec2_large_lv60k",
|
| 57 |
+
"hubert_base",
|
| 58 |
+
"hubert_large",
|
| 59 |
+
"hubert_xlarge",
|
| 60 |
+
"hubert_pretrain_model",
|
| 61 |
+
"hubert_pretrain_base",
|
| 62 |
+
"hubert_pretrain_large",
|
| 63 |
+
"hubert_pretrain_xlarge",
|
| 64 |
+
"wav2vec2_xlsr_300m",
|
| 65 |
+
"wav2vec2_xlsr_1b",
|
| 66 |
+
"wav2vec2_xlsr_2b",
|
| 67 |
+
"Tacotron2",
|
| 68 |
+
"Conformer",
|
| 69 |
+
"Emformer",
|
| 70 |
+
"Hypothesis",
|
| 71 |
+
"RNNT",
|
| 72 |
+
"RNNTBeamSearch",
|
| 73 |
+
"emformer_rnnt_base",
|
| 74 |
+
"emformer_rnnt_model",
|
| 75 |
+
"HDemucs",
|
| 76 |
+
"hdemucs_low",
|
| 77 |
+
"hdemucs_medium",
|
| 78 |
+
"hdemucs_high",
|
| 79 |
+
"squim_objective_base",
|
| 80 |
+
"squim_objective_model",
|
| 81 |
+
"squim_subjective_base",
|
| 82 |
+
"squim_subjective_model",
|
| 83 |
+
"SquimObjective",
|
| 84 |
+
"SquimSubjective",
|
| 85 |
+
]
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.82 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/_hdemucs.cpython-312.pyc
ADDED
|
Binary file (48.5 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/conformer.cpython-312.pyc
ADDED
|
Binary file (13.5 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/conv_tasnet.cpython-312.pyc
ADDED
|
Binary file (14.5 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/deepspeech.cpython-312.pyc
ADDED
|
Binary file (4.57 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/emformer.cpython-312.pyc
ADDED
|
Binary file (46.8 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/rnnt.cpython-312.pyc
ADDED
|
Binary file (40.5 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/rnnt_decoder.cpython-312.pyc
ADDED
|
Binary file (17.6 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/tacotron2.cpython-312.pyc
ADDED
|
Binary file (47.4 kB). View file
|
|
|
lib/python3.12/site-packages/torchaudio/models/__pycache__/wav2letter.cpython-312.pyc
ADDED
|
Binary file (4.13 kB). View file
|
|
|