| import os |
| import torch |
| import torchaudio |
| import torchaudio.transforms as T |
| import gradio as gr |
| from multi_stem_pipeline import MultiStemSeparationModel |
|
|
| |
| |
| |
| try: |
| import spaces |
| HAS_SPACES = True |
| except ImportError: |
| HAS_SPACES = False |
| class spaces: |
| @staticmethod |
| def GPU(fn): |
| return fn |
|
|
| |
| STEMS_LIST = ["vocals", "backing_vocals", "drums", "bass", "guitar", "synth"] |
| cached_model = None |
|
|
| |
| |
| |
| @spaces.GPU |
| def separate_multi_stems(audio_path): |
| global cached_model |
| if not audio_path: |
| return [None] * len(STEMS_LIST) |
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| sample_rate = 16000 |
|
|
| |
| if cached_model is None: |
| model_path = "./audio_separation_model" |
| if os.path.exists(model_path): |
| print(f"Loading local multi-stem model from {model_path}...") |
| cached_model = MultiStemSeparationModel.from_pretrained(model_path) |
| else: |
| print("No trained model found. Launching with untrained model fallback...") |
| cached_model = MultiStemSeparationModel(in_channels=1, num_stems=len(STEMS_LIST), base_features=16) |
|
|
| model = cached_model.to(device) |
| model.eval() |
|
|
| |
| waveform, sr = torchaudio.load(audio_path) |
| if sr != sample_rate: |
| resampler = T.Resample(orig_freq=sr, new_freq=sample_rate) |
| waveform = resampler(waveform) |
| |
| if waveform.shape[0] > 1: |
| waveform = torch.mean(waveform, dim=0, keepdim=True) |
|
|
| |
| complex_spec = torch.stft( |
| waveform, n_fft=512, win_length=512, hop_length=128, normalized=True, return_complex=True |
| ) |
| magnitude = torch.abs(complex_spec) |
| phase = torch.angle(complex_spec) |
|
|
| input_tensor = magnitude.unsqueeze(0).to(device) |
|
|
| |
| with torch.no_grad(): |
| pred_stems_spec, masks = model(input_tensor) |
|
|
| |
| pred_stems_spec = pred_stems_spec.squeeze(0).cpu() |
| phase = phase.cpu() |
|
|
| saved_filepaths = [] |
| |
| |
| for idx, stem_name in enumerate(STEMS_LIST): |
| stem_spec = pred_stems_spec[idx] |
| |
| |
| stem_complex = torch.polar(stem_spec, phase) |
| stem_wav = torch.istft( |
| stem_complex, n_fft=512, win_length=512, hop_length=128, normalized=True |
| ) |
| |
| |
| out_path = f"output_{stem_name}.wav" |
| torchaudio.save(out_path, stem_wav, sample_rate) |
| saved_filepaths.append(out_path) |
|
|
| return saved_filepaths |
|
|
|
|
| |
| |
| |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: |
| gr.Markdown( |
| f""" |
| # 🎙️ Multi-Stem AI Audio Splitter |
| Upload a song to automatically separate it into **{len(STEMS_LIST)}** individual, isolated instrument tracks using advanced spectrogram masking! |
| """ |
| ) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| input_audio = gr.Audio(type="filepath", label="Upload Mixed Song (.wav, .mp3)") |
| submit_btn = gr.Button("Split Stems ⚡", variant="primary") |
| |
| with gr.Column(scale=2): |
| |
| audio_outputs = [] |
| for stem_name in STEMS_LIST: |
| player = gr.Audio(label=f"Isolated {stem_name.replace('_', ' ').title()}", interactive=False) |
| audio_outputs.append(player) |
|
|
| |
| submit_btn.click( |
| fn=separate_multi_stems, |
| inputs=input_audio, |
| outputs=audio_outputs |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|