Source code for lhotse.dataset.cut_transforms.clipping

import random
from dataclasses import dataclass
from typing import Literal, Optional, Tuple, Union

from lhotse import CutSet
from lhotse.dataset.dataloading import resolve_seed


[docs]@dataclass class ClippingTransform: """ Applies clipping to each Cut in a CutSet with a given probability. The clipping is applied with a probability of ``p``. The gain_db is randomly sampled from the provided range if an interval is given, or the fixed value is used if a single float is provided. :param gain_db: A single value or an interval (tuple/list with two values). The amount of gain in decibels to apply before clipping. If an interval is provided, the value is sampled uniformly. :param hard: If True, apply hard clipping (sharp cutoff); otherwise, apply soft clipping (saturation). :param normalize: If True, normalize the input signal to 0 dBFS before applying clipping. :param p: The probability of applying clipping (default: 0.5). :param seed: Random seed for reproducibility (default: 42). :param rng: Optional random number generator (overrides seed if provided). :param oversampling: Optional integer factor for oversampling before clipping. :param preserve_id: Whether to preserve the original cut ID (default: False). """ gain_db: Union[float, Tuple[float, float]] normalize: bool = True p: float = 0.5 p_hard: float = 0.5 seed: Union[int, Literal["trng", "randomized"]] = 42 rng: Optional[random.Random] = None oversampling: Optional[int] = 2 preserve_id: bool = False def __post_init__(self) -> None: if isinstance(self.gain_db, (tuple, list)): assert ( len(self.gain_db) == 2 ), f"Expected gain_db to be a tuple or a list with two values, got {self.gain_db}" min_gain, max_gain = self.gain_db assert ( min_gain < max_gain ), f"Expected min_gain < max_gain, got {min_gain} >= {max_gain}" assert 0 <= self.p <= 1, f"Probability p must be between 0 and 1, got {self.p}" if self.rng is not None and self.seed is not None: raise ValueError("Either rng or seed must be provided, not both") if self.rng is None: self.rng = random.Random(resolve_seed(self.seed)) def __call__(self, cuts: CutSet) -> CutSet: saturated_cuts = [] for cut in cuts: if self.rng.random() <= self.p: if self.rng.random() <= self.p_hard: hard = True else: hard = False if isinstance(self.gain_db, (tuple, list)): min_gain, max_gain = self.gain_db gain_db = self.rng.uniform(min_gain, max_gain) else: gain_db = self.gain_db new_cut = cut.clip_amplitude( hard=hard, gain_db=gain_db, normalize=self.normalize, affix_id=not self.preserve_id, oversampling=self.oversampling, ) saturated_cuts.append(new_cut) else: saturated_cuts.append(cut) return CutSet(saturated_cuts)