Module audiocraft.utils.audio_effects

Functions

def apply_compression_skip_grad(tensor: torch.Tensor, compression_fn, **kwargs)
Expand source code
def apply_compression_skip_grad(tensor: torch.Tensor, compression_fn, **kwargs):
    """Applies a specified compression function to the audio tensor.
    Whire carrying over the grads to the output tensor with skip through estimator
    this is a straight through estimator to make mp3/aac compression differentiable
    see more: Yin et al. 2019 https://arxiv.org/pdf/1903.05662.pdf

    Args:
        tensor (torch.Tensor): The input audio tensor.
        compression_fn (function): The compression function to apply.
        **kwargs: Additional keyword arguments for the compression function.

    Returns:
        torch.Tensor: The output tensor after applying compression and straight through estimator.
    """
    compressed = compression_fn(tensor.detach(), **kwargs)

    # Trim compressed output if needed
    compressed = compressed[:, :, : tensor.size(-1)]

    # Straight through estimator for differentiable compression
    out = tensor + (compressed - tensor).detach()

    # Check that gradients are not broken
    if out.requires_grad:
        assert (
            out.grad_fn
        ), "The computation graph might be broken due to compression augmentation."

    return out

Applies a specified compression function to the audio tensor. Whire carrying over the grads to the output tensor with skip through estimator this is a straight through estimator to make mp3/aac compression differentiable see more: Yin et al. 2019 https://arxiv.org/pdf/1903.05662.pdf

Args

tensor : torch.Tensor
The input audio tensor.
compression_fn : function
The compression function to apply.
**kwargs
Additional keyword arguments for the compression function.

Returns

torch.Tensor
The output tensor after applying compression and straight through estimator.
def audio_effect_return(tensor: torch.Tensor, mask: torch.Tensor | None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
def audio_effect_return(
    tensor: torch.Tensor, mask: tp.Optional[torch.Tensor]
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Return the mask if it was in the input otherwise only the output tensor"""
    if mask is None:
        return tensor
    else:
        return tensor, mask

Return the mask if it was in the input otherwise only the output tensor

def compress_with_encodec(tensor: torch.Tensor,
n_q: int,
model: CompressionModel,
sample_rate: int,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
def compress_with_encodec(
    tensor: torch.Tensor,
    n_q: int,
    model: "CompressionModel",
    sample_rate: int,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Special augmentation function that compresses and decompresses wav tensor
    using a compression model with the n_q codebooks
    """

    model.to(tensor.device)
    model.set_num_codebooks(n_q)
    codes, scale = model.encode(
        julius.resample_frac(tensor, old_sr=sample_rate, new_sr=model.sample_rate)
    )
    compressed = model.decode(codes=codes, scale=scale)
    return audio_effect_return(
        tensor=julius.resample_frac(
            compressed, old_sr=model.sample_rate, new_sr=sample_rate
        ),
        mask=mask,
    )

Special augmentation function that compresses and decompresses wav tensor using a compression model with the n_q codebooks

def generate_pink_noise(length: int) ‑> torch.Tensor
Expand source code
def generate_pink_noise(length: int) -> torch.Tensor:
    """Generate pink noise using Voss-McCartney algorithm with PyTorch."""
    num_rows = 16
    array = torch.randn(num_rows, length // num_rows + 1)
    reshaped_array = torch.cumsum(array, dim=1)
    reshaped_array = reshaped_array.reshape(-1)
    reshaped_array = reshaped_array[:length]
    # Normalize
    pink_noise = reshaped_array / torch.max(torch.abs(reshaped_array))
    return pink_noise

Generate pink noise using Voss-McCartney algorithm with PyTorch.

def get_audio_effects(cfg: omegaconf.dictconfig.DictConfig)
Expand source code
def get_audio_effects(cfg: omegaconf.DictConfig):
    """Automatically pull the list all effects available in this class based on the parameters from the cfg

    Returns:
        dict: A dict of names and pointers to all methods in this class.
    """
    assert hasattr(cfg, "audio_effects")
    cfg_audio_effects = dict(cfg["audio_effects"])
    return {
        name: partial(value, **cfg_audio_effects.get(name, {}))
        for name, value in inspect.getmembers(AudioEffects)
        if inspect.isfunction(value)
    }

Automatically pull the list all effects available in this class based on the parameters from the cfg

Returns

dict
A dict of names and pointers to all methods in this class.
def select_audio_effects(audio_effects: Dict,
weights: Dict | None = None,
mode: str = 'all',
max_length: int | None = None)
Expand source code
def select_audio_effects(
    audio_effects: tp.Dict,
    weights: tp.Optional[tp.Dict] = None,
    mode: str = "all",
    max_length: tp.Optional[int] = None,
):
    """Samples a subset of audio effects methods from the `AudioEffects` class.

    This function allows you to select a subset of audio effects
    based on the chosen selection mode and optional weights.

    Args:
        audio_effects (dict): A dictionary of available audio augmentations, usually
            obtained from the output of the 'get_audio_effects' function.
        weights (dict): A dictionary mapping augmentation names to their corresponding
            probabilities of being selected. This argument is used when 'mode' is set
            to "weighted." If 'weights' is None, all augmentations have equal
            probability of being selected.
        mode (str): The selection mode, which can be one of the following:
            - "all": Select all available augmentations.
            - "weighted": Select augmentations based on their probabilities in the
              'weights' dictionary.
        max_length (int): The maximum number of augmentations to select. If 'max_length'
            is None, no limit is applied.

    Returns:
        dict: A subset of the 'audio_effects' dictionary containing the selected audio
        augmentations.

    Note:
        - In "all" mode, all available augmentations are selected.
        - In "weighted" mode, augmentations are selected with a probability
          proportional to their weights specified in the 'weights' dictionary.
        - If 'max_length' is set, the function limits the number of selected
          augmentations.
        - If no augmentations are selected or 'audio_effects' is empty, the function
          defaults to including an "identity" augmentation.
        - The "identity" augmentation means that no audio effect is applied.
    """
    if mode == "all":  # original code
        out = audio_effects
    elif mode == "weighted":
        # Probability proportionnal to weights
        assert weights is not None
        out = {
            name: value
            for name, value in audio_effects.items()
            if random.random() < weights.get(name, 1.0)
        }
    else:
        raise ValueError(f"Unknown mode {mode}")
    if max_length is not None:
        # Help having a deterministic limit of the gpu memory usage
        random_keys = random.sample(list(out.keys()), max_length)
        out = {key: out[key] for key in random_keys}
    if len(out) == 0:  # Check not to return empty dict
        out = {"identity": AudioEffects.identity}
    return out

Samples a subset of audio effects methods from the AudioEffects class.

This function allows you to select a subset of audio effects based on the chosen selection mode and optional weights.

Args

audio_effects : dict
A dictionary of available audio augmentations, usually obtained from the output of the 'get_audio_effects' function.
weights : dict
A dictionary mapping augmentation names to their corresponding probabilities of being selected. This argument is used when 'mode' is set to "weighted." If 'weights' is None, all augmentations have equal probability of being selected.
mode : str
The selection mode, which can be one of the following: - "all": Select all available augmentations. - "weighted": Select augmentations based on their probabilities in the 'weights' dictionary.
max_length : int
The maximum number of augmentations to select. If 'max_length' is None, no limit is applied.

Returns

dict
A subset of the 'audio_effects' dictionary containing the selected audio

augmentations.

Note

  • In "all" mode, all available augmentations are selected.
  • In "weighted" mode, augmentations are selected with a probability proportional to their weights specified in the 'weights' dictionary.
  • If 'max_length' is set, the function limits the number of selected augmentations.
  • If no augmentations are selected or 'audio_effects' is empty, the function defaults to including an "identity" augmentation.
  • The "identity" augmentation means that no audio effect is applied.

Classes

class AudioEffects
Expand source code
class AudioEffects:
    @staticmethod
    def speed(
        tensor: torch.Tensor,
        speed_range: tuple = (0.5, 1.5),
        sample_rate: int = 16000,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Function to change the speed of a batch of audio data.
        The output will have a different length !

        Args:
            audio_batch (torch.Tensor): The batch of audio data in torch tensor format.
            speed (float): The speed to change the audio to.

        Returns:
            torch.Tensor: The batch of audio data with the speed changed.
        """
        speed = torch.FloatTensor(1).uniform_(*speed_range)
        new_sr = int(sample_rate * 1 / speed)
        resampled_tensor = julius.resample.resample_frac(tensor, sample_rate, new_sr)
        if mask is None:
            return resampled_tensor
        else:
            return resampled_tensor, torch.nn.functional.interpolate(
                mask, size=resampled_tensor.size(-1), mode="nearest-exact"
            )

    @staticmethod
    def updownresample(
        tensor: torch.Tensor,
        sample_rate: int = 16000,
        intermediate_freq: int = 32000,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:

        orig_shape = tensor.shape
        # upsample
        tensor = resample_frac(tensor, sample_rate, intermediate_freq)
        # downsample
        tensor = resample_frac(tensor, intermediate_freq, sample_rate)

        assert tensor.shape == orig_shape
        return audio_effect_return(tensor=tensor, mask=mask)

    @staticmethod
    def echo(
        tensor: torch.Tensor,
        volume_range: tuple = (0.1, 0.5),
        duration_range: tuple = (0.1, 0.5),
        sample_rate: int = 16000,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Attenuating the audio volume by a factor of 0.4, delaying it by 100ms,
        and then overlaying it with the original.

        Args:
            tensor: 3D Tensor representing the audio signal [bsz, channels, frames]
            volumne range: volume range of the echo signal
            duration range: duration range of the echo signal
            sample_rate: Sample rate of the audio signal.
        Returns:
            Audio signal with reverb.
        """

        # Create a simple impulse response
        # Duration of the impulse response in seconds
        duration = torch.FloatTensor(1).uniform_(*duration_range)
        volume = torch.FloatTensor(1).uniform_(*volume_range)

        n_samples = int(sample_rate * duration)
        impulse_response = torch.zeros(n_samples).type(tensor.type()).to(tensor.device)

        # Define a few reflections with decreasing amplitude
        impulse_response[0] = 1.0  # Direct sound

        impulse_response[
            int(sample_rate * duration) - 1
        ] = volume  # First reflection after 100ms

        # Add batch and channel dimensions to the impulse response
        impulse_response = impulse_response.unsqueeze(0).unsqueeze(0)

        # Convolve the audio signal with the impulse response
        reverbed_signal = fft_conv1d(tensor, impulse_response)

        # Normalize to the original amplitude range for stability
        reverbed_signal = (
            reverbed_signal
            / torch.max(torch.abs(reverbed_signal))
            * torch.max(torch.abs(tensor))
        )

        # Ensure tensor size is not changed
        tmp = torch.zeros_like(tensor)
        tmp[..., : reverbed_signal.shape[-1]] = reverbed_signal
        reverbed_signal = tmp

        return audio_effect_return(tensor=reverbed_signal, mask=mask)

    @staticmethod
    def random_noise(
        waveform: torch.Tensor,
        noise_std: float = 0.001,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Add Gaussian noise to the waveform."""
        noise = torch.randn_like(waveform) * noise_std
        noisy_waveform = waveform + noise
        return audio_effect_return(tensor=noisy_waveform, mask=mask)

    @staticmethod
    def pink_noise(
        waveform: torch.Tensor,
        noise_std: float = 0.01,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Add pink background noise to the waveform."""
        noise = generate_pink_noise(waveform.shape[-1]) * noise_std
        noise = noise.to(waveform.device)
        # Assuming waveform is of shape (bsz, channels, length)
        noisy_waveform = waveform + noise.unsqueeze(0).unsqueeze(0).to(waveform.device)
        return audio_effect_return(tensor=noisy_waveform, mask=mask)

    @staticmethod
    def lowpass_filter(
        waveform: torch.Tensor,
        cutoff_freq: float = 5000,
        sample_rate: int = 16000,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Filter the lowpass frequency from the waveform"""
        return audio_effect_return(
            tensor=julius.lowpass_filter(waveform, cutoff=cutoff_freq / sample_rate),
            mask=mask,
        )

    @staticmethod
    def highpass_filter(
        waveform: torch.Tensor,
        cutoff_freq: float = 500,
        sample_rate: int = 16000,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Filter the highpass frequency from the waveform"""
        return audio_effect_return(
            tensor=julius.highpass_filter(waveform, cutoff=cutoff_freq / sample_rate),
            mask=mask,
        )

    @staticmethod
    def bandpass_filter(
        waveform: torch.Tensor,
        cutoff_freq_low: float = 300,
        cutoff_freq_high: float = 8000,
        sample_rate: int = 16000,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Apply a bandpass filter to the waveform by cascading
        a high-pass filter followed by a low-pass filter.

        Args:
            waveform (torch.Tensor): Input audio waveform.
            low_cutoff (float): Lower cutoff frequency.
            high_cutoff (float): Higher cutoff frequency.
            sample_rate (int): The sample rate of the waveform.

        Returns:
            torch.Tensor: Filtered audio waveform.
        """

        return audio_effect_return(
            tensor=julius.bandpass_filter(
                waveform,
                cutoff_low=cutoff_freq_low / sample_rate,
                cutoff_high=cutoff_freq_high / sample_rate,
            ),
            mask=mask,
        )

    @staticmethod
    def smooth(
        tensor: torch.Tensor,
        window_size_range: tuple = (2, 10),
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Smooths the input tensor (audio signal) using a moving average filter with the
        given window size.

        Args:
            tensor (torch.Tensor): Input audio tensor. Assumes tensor shape is (batch_size,
            channels, time).
            window_size (int): Size of the moving average window.
            mask: Masks for the input wave

        Returns:
            torch.Tensor: Smoothed audio tensor.
        """

        window_size = int(torch.FloatTensor(1).uniform_(*window_size_range))
        # Create a uniform smoothing kernel
        kernel = torch.ones(1, 1, window_size).type(tensor.type()) / window_size
        kernel = kernel.to(tensor.device)

        smoothed = fft_conv1d(tensor, kernel)
        # Ensure tensor size is not changed
        tmp = torch.zeros_like(tensor)
        tmp[..., : smoothed.shape[-1]] = smoothed
        smoothed = tmp

        return audio_effect_return(tensor=smoothed, mask=mask)

    @staticmethod
    def boost_audio(
        tensor: torch.Tensor,
        amount: float = 20,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Filter the lowpass frequency from the waveform"""
        return audio_effect_return(tensor=tensor * (1 + amount / 100), mask=mask)

    @staticmethod
    def duck_audio(
        tensor: torch.Tensor,
        amount: float = 20,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Mask input wav with some ducked signnals"""
        return audio_effect_return(tensor=tensor * (1 - amount / 100), mask=mask)

    @staticmethod
    def identity(
        tensor: torch.Tensor, mask: tp.Optional[torch.Tensor] = None
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        return audio_effect_return(tensor=tensor, mask=mask)

    @staticmethod
    def mp3_compression(
        tensor: torch.Tensor,
        sample_rate: int = 16000,
        bitrate: str = "128k",
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """
        Compress audio using MP3 algorithm
        Args:
            tensor (torch.Tensor): The input audio tensor.
            sample_rate (int): The sample rate of the audio.
            bitrate (str): The bitrate for MP3 compression.

        Returns:
            torch.Tensor: The output tensor after applying MP3 compression.
        """
        out = apply_compression_skip_grad(
            tensor, get_mp3, sr=sample_rate, bitrate=bitrate
        )
        return audio_effect_return(tensor=out, mask=mask)

    @staticmethod
    def aac_compression(
        tensor: torch.Tensor,
        sample_rate: int = 16000,
        bitrate: str = "128k",
        lowpass_freq: tp.Optional[int] = None,
        mask: tp.Optional[torch.Tensor] = None,
    ) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
        """Applies AAC compression to an audio tensor.

        Args:
            tensor (torch.Tensor): The input audio tensor.
            sample_rate (int): The sample rate of the audio.
            bitrate (str): The bitrate for AAC compression.
            lowpass_freq (Optional[int]): The frequency for a low-pass filter.

        Returns:
            torch.Tensor: The output tensor after applying AAC compression.
        """
        out = apply_compression_skip_grad(
            tensor, get_aac, sr=sample_rate, bitrate=bitrate, lowpass_freq=lowpass_freq
        )
        return audio_effect_return(tensor=out, mask=mask)

Static methods

def aac_compression(tensor: torch.Tensor,
sample_rate: int = 16000,
bitrate: str = '128k',
lowpass_freq: int | None = None,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def aac_compression(
    tensor: torch.Tensor,
    sample_rate: int = 16000,
    bitrate: str = "128k",
    lowpass_freq: tp.Optional[int] = None,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Applies AAC compression to an audio tensor.

    Args:
        tensor (torch.Tensor): The input audio tensor.
        sample_rate (int): The sample rate of the audio.
        bitrate (str): The bitrate for AAC compression.
        lowpass_freq (Optional[int]): The frequency for a low-pass filter.

    Returns:
        torch.Tensor: The output tensor after applying AAC compression.
    """
    out = apply_compression_skip_grad(
        tensor, get_aac, sr=sample_rate, bitrate=bitrate, lowpass_freq=lowpass_freq
    )
    return audio_effect_return(tensor=out, mask=mask)

Applies AAC compression to an audio tensor.

Args

tensor : torch.Tensor
The input audio tensor.
sample_rate : int
The sample rate of the audio.
bitrate : str
The bitrate for AAC compression.
lowpass_freq : Optional[int]
The frequency for a low-pass filter.

Returns

torch.Tensor
The output tensor after applying AAC compression.
def bandpass_filter(waveform: torch.Tensor,
cutoff_freq_low: float = 300,
cutoff_freq_high: float = 8000,
sample_rate: int = 16000,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def bandpass_filter(
    waveform: torch.Tensor,
    cutoff_freq_low: float = 300,
    cutoff_freq_high: float = 8000,
    sample_rate: int = 16000,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Apply a bandpass filter to the waveform by cascading
    a high-pass filter followed by a low-pass filter.

    Args:
        waveform (torch.Tensor): Input audio waveform.
        low_cutoff (float): Lower cutoff frequency.
        high_cutoff (float): Higher cutoff frequency.
        sample_rate (int): The sample rate of the waveform.

    Returns:
        torch.Tensor: Filtered audio waveform.
    """

    return audio_effect_return(
        tensor=julius.bandpass_filter(
            waveform,
            cutoff_low=cutoff_freq_low / sample_rate,
            cutoff_high=cutoff_freq_high / sample_rate,
        ),
        mask=mask,
    )

Apply a bandpass filter to the waveform by cascading a high-pass filter followed by a low-pass filter.

Args

waveform : torch.Tensor
Input audio waveform.
low_cutoff : float
Lower cutoff frequency.
high_cutoff : float
Higher cutoff frequency.
sample_rate : int
The sample rate of the waveform.

Returns

torch.Tensor
Filtered audio waveform.
def boost_audio(tensor: torch.Tensor, amount: float = 20, mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def boost_audio(
    tensor: torch.Tensor,
    amount: float = 20,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Filter the lowpass frequency from the waveform"""
    return audio_effect_return(tensor=tensor * (1 + amount / 100), mask=mask)

Filter the lowpass frequency from the waveform

def duck_audio(tensor: torch.Tensor, amount: float = 20, mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def duck_audio(
    tensor: torch.Tensor,
    amount: float = 20,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Mask input wav with some ducked signnals"""
    return audio_effect_return(tensor=tensor * (1 - amount / 100), mask=mask)

Mask input wav with some ducked signnals

def echo(tensor: torch.Tensor,
volume_range: tuple = (0.1, 0.5),
duration_range: tuple = (0.1, 0.5),
sample_rate: int = 16000,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def echo(
    tensor: torch.Tensor,
    volume_range: tuple = (0.1, 0.5),
    duration_range: tuple = (0.1, 0.5),
    sample_rate: int = 16000,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Attenuating the audio volume by a factor of 0.4, delaying it by 100ms,
    and then overlaying it with the original.

    Args:
        tensor: 3D Tensor representing the audio signal [bsz, channels, frames]
        volumne range: volume range of the echo signal
        duration range: duration range of the echo signal
        sample_rate: Sample rate of the audio signal.
    Returns:
        Audio signal with reverb.
    """

    # Create a simple impulse response
    # Duration of the impulse response in seconds
    duration = torch.FloatTensor(1).uniform_(*duration_range)
    volume = torch.FloatTensor(1).uniform_(*volume_range)

    n_samples = int(sample_rate * duration)
    impulse_response = torch.zeros(n_samples).type(tensor.type()).to(tensor.device)

    # Define a few reflections with decreasing amplitude
    impulse_response[0] = 1.0  # Direct sound

    impulse_response[
        int(sample_rate * duration) - 1
    ] = volume  # First reflection after 100ms

    # Add batch and channel dimensions to the impulse response
    impulse_response = impulse_response.unsqueeze(0).unsqueeze(0)

    # Convolve the audio signal with the impulse response
    reverbed_signal = fft_conv1d(tensor, impulse_response)

    # Normalize to the original amplitude range for stability
    reverbed_signal = (
        reverbed_signal
        / torch.max(torch.abs(reverbed_signal))
        * torch.max(torch.abs(tensor))
    )

    # Ensure tensor size is not changed
    tmp = torch.zeros_like(tensor)
    tmp[..., : reverbed_signal.shape[-1]] = reverbed_signal
    reverbed_signal = tmp

    return audio_effect_return(tensor=reverbed_signal, mask=mask)

Attenuating the audio volume by a factor of 0.4, delaying it by 100ms, and then overlaying it with the original.

Args

tensor
3D Tensor representing the audio signal [bsz, channels, frames]
volumne range: volume range of the echo signal
duration range: duration range of the echo signal
sample_rate
Sample rate of the audio signal.

Returns

Audio signal with reverb.

def highpass_filter(waveform: torch.Tensor,
cutoff_freq: float = 500,
sample_rate: int = 16000,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def highpass_filter(
    waveform: torch.Tensor,
    cutoff_freq: float = 500,
    sample_rate: int = 16000,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Filter the highpass frequency from the waveform"""
    return audio_effect_return(
        tensor=julius.highpass_filter(waveform, cutoff=cutoff_freq / sample_rate),
        mask=mask,
    )

Filter the highpass frequency from the waveform

def identity(tensor: torch.Tensor, mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def identity(
    tensor: torch.Tensor, mask: tp.Optional[torch.Tensor] = None
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    return audio_effect_return(tensor=tensor, mask=mask)
def lowpass_filter(waveform: torch.Tensor,
cutoff_freq: float = 5000,
sample_rate: int = 16000,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def lowpass_filter(
    waveform: torch.Tensor,
    cutoff_freq: float = 5000,
    sample_rate: int = 16000,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Filter the lowpass frequency from the waveform"""
    return audio_effect_return(
        tensor=julius.lowpass_filter(waveform, cutoff=cutoff_freq / sample_rate),
        mask=mask,
    )

Filter the lowpass frequency from the waveform

def mp3_compression(tensor: torch.Tensor,
sample_rate: int = 16000,
bitrate: str = '128k',
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def mp3_compression(
    tensor: torch.Tensor,
    sample_rate: int = 16000,
    bitrate: str = "128k",
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """
    Compress audio using MP3 algorithm
    Args:
        tensor (torch.Tensor): The input audio tensor.
        sample_rate (int): The sample rate of the audio.
        bitrate (str): The bitrate for MP3 compression.

    Returns:
        torch.Tensor: The output tensor after applying MP3 compression.
    """
    out = apply_compression_skip_grad(
        tensor, get_mp3, sr=sample_rate, bitrate=bitrate
    )
    return audio_effect_return(tensor=out, mask=mask)

Compress audio using MP3 algorithm

Args

tensor : torch.Tensor
The input audio tensor.
sample_rate : int
The sample rate of the audio.
bitrate : str
The bitrate for MP3 compression.

Returns

torch.Tensor
The output tensor after applying MP3 compression.
def pink_noise(waveform: torch.Tensor,
noise_std: float = 0.01,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def pink_noise(
    waveform: torch.Tensor,
    noise_std: float = 0.01,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Add pink background noise to the waveform."""
    noise = generate_pink_noise(waveform.shape[-1]) * noise_std
    noise = noise.to(waveform.device)
    # Assuming waveform is of shape (bsz, channels, length)
    noisy_waveform = waveform + noise.unsqueeze(0).unsqueeze(0).to(waveform.device)
    return audio_effect_return(tensor=noisy_waveform, mask=mask)

Add pink background noise to the waveform.

def random_noise(waveform: torch.Tensor,
noise_std: float = 0.001,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def random_noise(
    waveform: torch.Tensor,
    noise_std: float = 0.001,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Add Gaussian noise to the waveform."""
    noise = torch.randn_like(waveform) * noise_std
    noisy_waveform = waveform + noise
    return audio_effect_return(tensor=noisy_waveform, mask=mask)

Add Gaussian noise to the waveform.

def smooth(tensor: torch.Tensor,
window_size_range: tuple = (2, 10),
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def smooth(
    tensor: torch.Tensor,
    window_size_range: tuple = (2, 10),
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Smooths the input tensor (audio signal) using a moving average filter with the
    given window size.

    Args:
        tensor (torch.Tensor): Input audio tensor. Assumes tensor shape is (batch_size,
        channels, time).
        window_size (int): Size of the moving average window.
        mask: Masks for the input wave

    Returns:
        torch.Tensor: Smoothed audio tensor.
    """

    window_size = int(torch.FloatTensor(1).uniform_(*window_size_range))
    # Create a uniform smoothing kernel
    kernel = torch.ones(1, 1, window_size).type(tensor.type()) / window_size
    kernel = kernel.to(tensor.device)

    smoothed = fft_conv1d(tensor, kernel)
    # Ensure tensor size is not changed
    tmp = torch.zeros_like(tensor)
    tmp[..., : smoothed.shape[-1]] = smoothed
    smoothed = tmp

    return audio_effect_return(tensor=smoothed, mask=mask)

Smooths the input tensor (audio signal) using a moving average filter with the given window size.

Args

tensor : torch.Tensor
Input audio tensor. Assumes tensor shape is (batch_size,
channels, time).
window_size : int
Size of the moving average window.
mask
Masks for the input wave

Returns

torch.Tensor
Smoothed audio tensor.
def speed(tensor: torch.Tensor,
speed_range: tuple = (0.5, 1.5),
sample_rate: int = 16000,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def speed(
    tensor: torch.Tensor,
    speed_range: tuple = (0.5, 1.5),
    sample_rate: int = 16000,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:
    """Function to change the speed of a batch of audio data.
    The output will have a different length !

    Args:
        audio_batch (torch.Tensor): The batch of audio data in torch tensor format.
        speed (float): The speed to change the audio to.

    Returns:
        torch.Tensor: The batch of audio data with the speed changed.
    """
    speed = torch.FloatTensor(1).uniform_(*speed_range)
    new_sr = int(sample_rate * 1 / speed)
    resampled_tensor = julius.resample.resample_frac(tensor, sample_rate, new_sr)
    if mask is None:
        return resampled_tensor
    else:
        return resampled_tensor, torch.nn.functional.interpolate(
            mask, size=resampled_tensor.size(-1), mode="nearest-exact"
        )

Function to change the speed of a batch of audio data. The output will have a different length !

Args

audio_batch : torch.Tensor
The batch of audio data in torch tensor format.
speed : float
The speed to change the audio to.

Returns

torch.Tensor
The batch of audio data with the speed changed.
def updownresample(tensor: torch.Tensor,
sample_rate: int = 16000,
intermediate_freq: int = 32000,
mask: torch.Tensor | None = None) ‑> torch.Tensor | Tuple[torch.Tensor, torch.Tensor]
Expand source code
@staticmethod
def updownresample(
    tensor: torch.Tensor,
    sample_rate: int = 16000,
    intermediate_freq: int = 32000,
    mask: tp.Optional[torch.Tensor] = None,
) -> tp.Union[tp.Tuple[torch.Tensor, torch.Tensor], torch.Tensor]:

    orig_shape = tensor.shape
    # upsample
    tensor = resample_frac(tensor, sample_rate, intermediate_freq)
    # downsample
    tensor = resample_frac(tensor, intermediate_freq, sample_rate)

    assert tensor.shape == orig_shape
    return audio_effect_return(tensor=tensor, mask=mask)