CoolFace
Apppublic

Clicko777/RVC_HFv2

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
resample.py217 linesDownload Raw Back to julius
1# File under the MIT license, see https://github.com/adefossez/julius/LICENSE for details.2# Author: adefossez, 20203"""4Differentiable, Pytorch based resampling.5Implementation of Julius O. Smith algorithm for resampling.6See https://ccrma.stanford.edu/~jos/resample/ for details.7This implementation is specially optimized for when new_sr / old_sr is a fraction8with a small numerator and denominator when removing the gcd (e.g. new_sr = 700, old_sr = 500).9 10Very similar to [bmcfee/resampy](https://github.com/bmcfee/resampy) except this implementation11is optimized for the case mentioned before, while resampy is slower but more general.12 13"""14 15import math16from typing import Optional17 18import torch19from torch.nn import functional as F20 21from .core import sinc22from .utils import simple_repr23 24 25class ResampleFrac(torch.nn.Module):26    """27    Resampling from the sample rate `old_sr` to `new_sr`.28    """29    def __init__(self, old_sr: int, new_sr: int, zeros: int = 24, rolloff: float = 0.945):30        """31        Args:32            old_sr (int): sample rate of the input signal x.33            new_sr (int): sample rate of the output.34            zeros (int): number of zero crossing to keep in the sinc filter.35            rolloff (float): use a lowpass filter that is `rolloff * new_sr / 2`,36                to ensure sufficient margin due to the imperfection of the FIR filter used.37                Lowering this value will reduce anti-aliasing, but will reduce some of the38                highest frequencies.39 40        Shape:41 42            - Input: `[*, T]`43            - Output: `[*, T']` with `T' = int(new_sr * T / old_sr)44 45 46        .. caution::47            After dividing `old_sr` and `new_sr` by their GCD, both should be small48            for this implementation to be fast.49 50        >>> import torch51        >>> resample = ResampleFrac(4, 5)52        >>> x = torch.randn(1000)53        >>> print(len(resample(x)))54        125055        """56        super().__init__()57        if not isinstance(old_sr, int) or not isinstance(new_sr, int):58            raise ValueError("old_sr and new_sr should be integers")59        gcd = math.gcd(old_sr, new_sr)60        self.old_sr = old_sr // gcd61        self.new_sr = new_sr // gcd62        self.zeros = zeros63        self.rolloff = rolloff64 65        self._init_kernels()66 67    def _init_kernels(self):68        if self.old_sr == self.new_sr:69            return70 71        kernels = []72        sr = min(self.new_sr, self.old_sr)73        # rolloff will perform antialiasing filtering by removing the highest frequencies.74        # At first I thought I only needed this when downsampling, but when upsampling75        # you will get edge artifacts without this, the edge is equivalent to zero padding,76        # which will add high freq artifacts.77        sr *= self.rolloff78 79        # The key idea of the algorithm is that x(t) can be exactly reconstructed from x[i] (tensor)80        # using the sinc interpolation formula:81        #   x(t) = sum_i x[i] sinc(pi * old_sr * (i / old_sr - t))82        # We can then sample the function x(t) with a different sample rate:83        #    y[j] = x(j / new_sr)84        # or,85        #    y[j] = sum_i x[i] sinc(pi * old_sr * (i / old_sr - j / new_sr))86 87        # We see here that y[j] is the convolution of x[i] with a specific filter, for which88        # we take an FIR approximation, stopping when we see at least `zeros` zeros crossing.89        # But y[j+1] is going to have a different set of weights and so on, until y[j + new_sr].90        # Indeed:91        # y[j + new_sr] = sum_i x[i] sinc(pi * old_sr * ((i / old_sr - (j + new_sr) / new_sr))92        #               = sum_i x[i] sinc(pi * old_sr * ((i - old_sr) / old_sr - j / new_sr))93        #               = sum_i x[i + old_sr] sinc(pi * old_sr * (i / old_sr - j / new_sr))94        # so y[j+new_sr] uses the same filter as y[j], but on a shifted version of x by `old_sr`.95        # This will explain the F.conv1d after, with a stride of old_sr.96        self._width = math.ceil(self.zeros * self.old_sr / sr)97        # If old_sr is still big after GCD reduction, most filters will be very unbalanced, i.e.,98        # they will have a lot of almost zero values to the left or to the right...99        # There is probably a way to evaluate those filters more efficiently, but this is kept for100        # future work.101        idx = torch.arange(-self._width, self._width + self.old_sr).float()102        for i in range(self.new_sr):103            t = (-i/self.new_sr + idx/self.old_sr) * sr104            t = t.clamp_(-self.zeros, self.zeros)105            t *= math.pi106            window = torch.cos(t/self.zeros/2)**2107            kernel = sinc(t) * window108            # Renormalize kernel to ensure a constant signal is preserved.109            kernel.div_(kernel.sum())110            kernels.append(kernel)111 112        self.register_buffer("kernel", torch.stack(kernels).view(self.new_sr, 1, -1))113 114    def forward(self, x: torch.Tensor, output_length: Optional[int] = None, full: bool = False):115        """116        Resample x.117        Args:118            x (Tensor): signal to resample, time should be the last dimension119            output_length (None or int): This can be set to the desired output length120                (last dimension). Allowed values are between 0 and121                ceil(length * new_sr / old_sr). When None (default) is specified, the122                floored output length will be used. In order to select the largest possible123                size, use the `full` argument.124            full (bool): return the longest possible output from the input. This can be useful125                if you chain resampling operations, and want to give the `output_length` only126                for the last one, while passing `full=True` to all the other ones.127        """128        if self.old_sr == self.new_sr:129            return x130        shape = x.shape131        length = x.shape[-1]132        x = x.reshape(-1, length)133        x = F.pad(x[:, None], (self._width, self._width + self.old_sr), mode='replicate')134        ys = F.conv1d(x, self.kernel, stride=self.old_sr)  # type: ignore135        y = ys.transpose(1, 2).reshape(list(shape[:-1]) + [-1])136 137        float_output_length = self.new_sr * length / self.old_sr138        max_output_length = int(math.ceil(float_output_length))139        default_output_length = int(float_output_length)140        if output_length is None:141            output_length = max_output_length if full else default_output_length142        elif output_length < 0 or output_length > max_output_length:143            raise ValueError(f"output_length must be between 0 and {max_output_length}")144        else:145            if full:146                raise ValueError("You cannot pass both full=True and output_length")147        return y[..., :output_length]148 149    def __repr__(self):150        return simple_repr(self)151 152 153def resample_frac(x: torch.Tensor, old_sr: int, new_sr: int,154                  zeros: int = 24, rolloff: float = 0.945,155                  output_length: Optional[int] = None, full: bool = False):156    """157    Functional version of `ResampleFrac`, refer to its documentation for more information.158 159    ..warning::160        If you call repeatidly this functions with the same sample rates, then the161        resampling kernel will be recomputed everytime. For best performance, you should use162        and cache an instance of `ResampleFrac`.163    """164    return ResampleFrac(old_sr, new_sr, zeros, rolloff).to(x)(x, output_length, full)165 166 167# Easier implementations for downsampling and upsampling by a factor of 2168# Kept for testing and reference169 170def _kernel_upsample2_downsample2(zeros):171    # Kernel for upsampling and downsampling by a factor of 2. Interestingly,172    # it is the same kernel used for both.173    win = torch.hann_window(4 * zeros + 1, periodic=False)174    winodd = win[1::2]175    t = torch.linspace(-zeros + 0.5, zeros - 0.5, 2 * zeros)176    t *= math.pi177    kernel = (sinc(t) * winodd).view(1, 1, -1)178    return kernel179 180 181def _upsample2(x, zeros=24):182    """183    Upsample x by a factor of two. The output will be exactly twice as long as the input.184    Args:185        x (Tensor): signal to upsample, time should be the last dimension186        zeros (int): number of zero crossing to keep in the sinc filter.187 188    This function is kept only for reference, you should use the more generic `resample_frac`189    one. This function does not perform anti-aliasing filtering.190    """191    *other, time = x.shape192    kernel = _kernel_upsample2_downsample2(zeros).to(x)193    out = F.conv1d(x.view(-1, 1, time), kernel, padding=zeros)[..., 1:].view(*other, time)194    y = torch.stack([x, out], dim=-1)195    return y.view(*other, -1)196 197 198def _downsample2(x, zeros=24):199    """200    Downsample x by a factor of two. The output length is half of the input, ceiled.201    Args:202        x (Tensor): signal to downsample, time should be the last dimension203        zeros (int): number of zero crossing to keep in the sinc filter.204 205    This function is kept only for reference, you should use the more generic `resample_frac`206    one. This function does not perform anti-aliasing filtering.207    """208    if x.shape[-1] % 2 != 0:209        x = F.pad(x, (0, 1))210    xeven = x[..., ::2]211    xodd = x[..., 1::2]212    *other, time = xodd.shape213    kernel = _kernel_upsample2_downsample2(zeros).to(x)214    out = xeven + F.conv1d(xodd.view(-1, 1, time), kernel, padding=zeros)[..., :-1].view(215        *other, time)216    return out.view(*other, -1).mul(0.5)217