Clicko777/RVC_HFv2
0
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 