mosibi/RVC_HFv2
0
1import numpy as np2 3 4# This function is obtained from librosa.5def get_rms(6 y,7 frame_length=2048,8 hop_length=512,9 pad_mode="constant",10):11 padding = (int(frame_length // 2), int(frame_length // 2))12 y = np.pad(y, padding, mode=pad_mode)13 14 axis = -115 # put our new within-frame axis at the end for now16 out_strides = y.strides + tuple([y.strides[axis]])17 # Reduce the shape on the framing axis18 x_shape_trimmed = list(y.shape)19 x_shape_trimmed[axis] -= frame_length - 120 out_shape = tuple(x_shape_trimmed) + tuple([frame_length])21 xw = np.lib.stride_tricks.as_strided(y, shape=out_shape, strides=out_strides)22 if axis < 0:23 target_axis = axis - 124 else:25 target_axis = axis + 126 xw = np.moveaxis(xw, -1, target_axis)27 # Downsample along the target axis28 slices = [slice(None)] * xw.ndim29 slices[axis] = slice(0, None, hop_length)30 x = xw[tuple(slices)]31 32 # Calculate power33 power = np.mean(np.abs(x) ** 2, axis=-2, keepdims=True)34 35 return np.sqrt(power)36 37 38class Slicer:39 def __init__(40 self,41 sr: int,42 threshold: float = -40.0,43 min_length: int = 5000,44 min_interval: int = 300,45 hop_size: int = 20,46 max_sil_kept: int = 5000,47 ):48 if not min_length >= min_interval >= hop_size:49 raise ValueError(50 "The following condition must be satisfied: min_length >= min_interval >= hop_size"51 )52 if not max_sil_kept >= hop_size:53 raise ValueError(54 "The following condition must be satisfied: max_sil_kept >= hop_size"55 )56 min_interval = sr * min_interval / 100057 self.threshold = 10 ** (threshold / 20.0)58 self.hop_size = round(sr * hop_size / 1000)59 self.win_size = min(round(min_interval), 4 * self.hop_size)60 self.min_length = round(sr * min_length / 1000 / self.hop_size)61 self.min_interval = round(min_interval / self.hop_size)62 self.max_sil_kept = round(sr * max_sil_kept / 1000 / self.hop_size)63 64 def _apply_slice(self, waveform, begin, end):65 if len(waveform.shape) > 1:66 return waveform[67 :, begin * self.hop_size : min(waveform.shape[1], end * self.hop_size)68 ]69 else:70 return waveform[71 begin * self.hop_size : min(waveform.shape[0], end * self.hop_size)72 ]73 74 # @timeit75 def slice(self, waveform):76 if len(waveform.shape) > 1:77 samples = waveform.mean(axis=0)78 else:79 samples = waveform80 if samples.shape[0] <= self.min_length:81 return [waveform]82 rms_list = get_rms(83 y=samples, frame_length=self.win_size, hop_length=self.hop_size84 ).squeeze(0)85 sil_tags = []86 silence_start = None87 clip_start = 088 for i, rms in enumerate(rms_list):89 # Keep looping while frame is silent.90 if rms < self.threshold:91 # Record start of silent frames.92 if silence_start is None:93 silence_start = i94 continue95 # Keep looping while frame is not silent and silence start has not been recorded.96 if silence_start is None:97 continue98 # Clear recorded silence start if interval is not enough or clip is too short99 is_leading_silence = silence_start == 0 and i > self.max_sil_kept100 need_slice_middle = (101 i - silence_start >= self.min_interval102 and i - clip_start >= self.min_length103 )104 if not is_leading_silence and not need_slice_middle:105 silence_start = None106 continue107 # Need slicing. Record the range of silent frames to be removed.108 if i - silence_start <= self.max_sil_kept:109 pos = rms_list[silence_start : i + 1].argmin() + silence_start110 if silence_start == 0:111 sil_tags.append((0, pos))112 else:113 sil_tags.append((pos, pos))114 clip_start = pos115 elif i - silence_start <= self.max_sil_kept * 2:116 pos = rms_list[117 i - self.max_sil_kept : silence_start + self.max_sil_kept + 1118 ].argmin()119 pos += i - self.max_sil_kept120 pos_l = (121 rms_list[122 silence_start : silence_start + self.max_sil_kept + 1123 ].argmin()124 + silence_start125 )126 pos_r = (127 rms_list[i - self.max_sil_kept : i + 1].argmin()128 + i129 - self.max_sil_kept130 )131 if silence_start == 0:132 sil_tags.append((0, pos_r))133 clip_start = pos_r134 else:135 sil_tags.append((min(pos_l, pos), max(pos_r, pos)))136 clip_start = max(pos_r, pos)137 else:138 pos_l = (139 rms_list[140 silence_start : silence_start + self.max_sil_kept + 1141 ].argmin()142 + silence_start143 )144 pos_r = (145 rms_list[i - self.max_sil_kept : i + 1].argmin()146 + i147 - self.max_sil_kept148 )149 if silence_start == 0:150 sil_tags.append((0, pos_r))151 else:152 sil_tags.append((pos_l, pos_r))153 clip_start = pos_r154 silence_start = None155 # Deal with trailing silence.156 total_frames = rms_list.shape[0]157 if (158 silence_start is not None159 and total_frames - silence_start >= self.min_interval160 ):161 silence_end = min(total_frames, silence_start + self.max_sil_kept)162 pos = rms_list[silence_start : silence_end + 1].argmin() + silence_start163 sil_tags.append((pos, total_frames + 1))164 # Apply and return slices.165 if len(sil_tags) == 0:166 return [waveform]167 else:168 chunks = []169 if sil_tags[0][0] > 0:170 chunks.append(self._apply_slice(waveform, 0, sil_tags[0][0]))171 for i in range(len(sil_tags) - 1):172 chunks.append(173 self._apply_slice(waveform, sil_tags[i][1], sil_tags[i + 1][0])174 )175 if sil_tags[-1][1] < total_frames:176 chunks.append(177 self._apply_slice(waveform, sil_tags[-1][1], total_frames)178 )179 return chunks180 181 182def main():183 import os.path184 from argparse import ArgumentParser185 186 import librosa187 import soundfile188 189 parser = ArgumentParser()190 parser.add_argument("audio", type=str, help="The audio to be sliced")191 parser.add_argument(192 "--out", type=str, help="Output directory of the sliced audio clips"193 )194 parser.add_argument(195 "--db_thresh",196 type=float,197 required=False,198 default=-40,199 help="The dB threshold for silence detection",200 )201 parser.add_argument(202 "--min_length",203 type=int,204 required=False,205 default=5000,206 help="The minimum milliseconds required for each sliced audio clip",207 )208 parser.add_argument(209 "--min_interval",210 type=int,211 required=False,212 default=300,213 help="The minimum milliseconds for a silence part to be sliced",214 )215 parser.add_argument(216 "--hop_size",217 type=int,218 required=False,219 default=10,220 help="Frame length in milliseconds",221 )222 parser.add_argument(223 "--max_sil_kept",224 type=int,225 required=False,226 default=500,227 help="The maximum silence length kept around the sliced clip, presented in milliseconds",228 )229 args = parser.parse_args()230 out = args.out231 if out is None:232 out = os.path.dirname(os.path.abspath(args.audio))233 audio, sr = librosa.load(args.audio, sr=None, mono=False)234 slicer = Slicer(235 sr=sr,236 threshold=args.db_thresh,237 min_length=args.min_length,238 min_interval=args.min_interval,239 hop_size=args.hop_size,240 max_sil_kept=args.max_sil_kept,241 )242 chunks = slicer.slice(audio)243 if not os.path.exists(out):244 os.makedirs(out)245 for i, chunk in enumerate(chunks):246 if len(chunk.shape) > 1:247 chunk = chunk.T248 soundfile.write(249 os.path.join(250 out,251 f"%s_%d.wav"252 % (os.path.basename(args.audio).rsplit(".", maxsplit=1)[0], i),253 ),254 chunk,255 sr,256 )257 258 259if __name__ == "__main__":260 main()261 