fix&feat: 12_SUBTAB.md chỉnh sửa audioclip ở subtab
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import numpy as np
|
||||
import scipy.signal as signal
|
||||
import librosa
|
||||
|
||||
class SubTabDSPEngine:
|
||||
@staticmethod
|
||||
def change_speed(y: np.ndarray, sr: int, speed_ratio: float, preserve_pitch: bool = True) -> np.ndarray:
|
||||
"""
|
||||
Alters the playback velocity (Time-Stretching) of a NumPy signal array.
|
||||
"""
|
||||
if speed_ratio == 1.0:
|
||||
return y
|
||||
|
||||
if preserve_pitch:
|
||||
return librosa.effects.time_stretch(y, rate=speed_ratio)
|
||||
else:
|
||||
num_samples_new = int(len(y) / speed_ratio)
|
||||
return signal.resample(y, num_samples_new)
|
||||
|
||||
@staticmethod
|
||||
def normalize(y: np.ndarray, target_db: float = 0.0) -> np.ndarray:
|
||||
"""
|
||||
Performs Peak Normalization on an array to scale it to the target decibel value.
|
||||
"""
|
||||
target_amplitude = 10.0 ** (target_db / 20.0)
|
||||
max_amplitude = np.max(np.abs(y))
|
||||
|
||||
if max_amplitude == 0:
|
||||
return y
|
||||
|
||||
gain = target_amplitude / max_amplitude
|
||||
return y * gain
|
||||
|
||||
@staticmethod
|
||||
def merge_back_to_parent(
|
||||
parent_track_audio: np.ndarray,
|
||||
sr: int,
|
||||
edited_sub_audio: np.ndarray,
|
||||
start_seconds: float,
|
||||
original_duration_seconds: float
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Splices the modified audio segment from the Sub-tab back into the parent track array.
|
||||
Applies a 10ms micro-crossfade at the boundaries to eliminate pop/click noise.
|
||||
"""
|
||||
start_sample = int(start_seconds * sr)
|
||||
original_samples_len = int(original_duration_seconds * sr)
|
||||
edited_samples_len = len(edited_sub_audio)
|
||||
crossfade_samples = int(0.01 * sr) # 10ms crossfade window
|
||||
|
||||
# 1. Allocate the target output array dimension bounds
|
||||
new_total_len = len(parent_track_audio) - original_samples_len + edited_samples_len
|
||||
output_audio = np.zeros(new_total_len, dtype=np.float32)
|
||||
|
||||
# 2. Extract leading unedited block
|
||||
output_audio[:start_sample] = parent_track_audio[:start_sample]
|
||||
|
||||
# 3. Stitch the modified audio payload
|
||||
output_audio[start_sample:start_sample + edited_samples_len] = edited_sub_audio
|
||||
|
||||
# 4. Extract trailing unedited block
|
||||
post_start_original = start_sample + original_samples_len
|
||||
post_start_new = start_sample + edited_samples_len
|
||||
output_audio[post_start_new:] = parent_track_audio[post_start_original:]
|
||||
|
||||
# 5. Execute micro-crossfade across the initial splice junction
|
||||
if start_sample > crossfade_samples:
|
||||
fade_in_ramp = np.linspace(0.0, 1.0, crossfade_samples)
|
||||
fade_out_ramp = np.linspace(1.0, 0.0, crossfade_samples)
|
||||
|
||||
# Smooth 10ms interpolation overlay
|
||||
output_audio[start_sample : start_sample + crossfade_samples] = (
|
||||
edited_sub_audio[:crossfade_samples] * fade_in_ramp +
|
||||
parent_track_audio[start_sample : start_sample + crossfade_samples] * fade_out_ramp
|
||||
)
|
||||
|
||||
# 6. Execute micro-crossfade across the trailing splice junction
|
||||
if post_start_new + crossfade_samples < len(output_audio):
|
||||
fade_in_ramp = np.linspace(0.0, 1.0, crossfade_samples)
|
||||
fade_out_ramp = np.linspace(1.0, 0.0, crossfade_samples)
|
||||
|
||||
output_audio[post_start_new : post_start_new + crossfade_samples] = (
|
||||
parent_track_audio[post_start_original : post_start_original + crossfade_samples] * fade_in_ramp +
|
||||
edited_sub_audio[-crossfade_samples:] * fade_out_ramp
|
||||
)
|
||||
|
||||
return output_audio
|
||||
Reference in New Issue
Block a user