Loop-Architect / app.py
SaltProphet's picture
Update app.py
62fc10d verified
raw
history blame
7.79 kB
import gradio as gr
import os
import shutil
import asyncio
import librosa
import librosa.display
import soundfile as sf
import numpy as np
import time
import zipfile
import tempfile
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
def update_output_visibility(choice):
if "2 Stems" in choice:
return {
vocals_output: gr.update(visible=True),
drums_output: gr.update(visible=False),
bass_output: gr.update(visible=False),
other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
}
elif "4 Stems" in choice:
return {
vocals_output: gr.update(visible=True),
drums_output: gr.update(visible=True),
bass_output: gr.update(visible=True),
other_output: gr.update(visible=True, label="Other")
}
async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
if audio_file_path is None: raise gr.Error("No audio file uploaded!")
progress(0, desc="Starting...")
try:
progress(0.05, desc="Preparing audio file...")
original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
stable_input_path = f"stable_input_{original_filename_base}.wav"
shutil.copy(audio_file_path, stable_input_path)
model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
output_dir = "separated"
if os.path.exists(output_dir): shutil.rmtree(output_dir)
command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
progress(0.2, desc="Running Demucs (this can take a minute)...")
process = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await process.communicate()
if process.returncode != 0:
raise gr.Error(f"Demucs failed to run. Error: {stderr.decode()[:500]}")
progress(0.8, desc="Locating separated stem files...")
stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
model_folder_name = next(os.walk(output_dir))[1][0]
stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
if not os.path.exists(stems_path):
raise gr.Error(f"Demucs finished, but the output directory was not found!")
vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
os.remove(stable_input_path)
return vocals_path, drums_path, bass_path, other_path
except Exception as e:
print(f"An error occurred: {e}")
raise gr.Error(str(e))
def visualize_slices(stem_audio_data, progress=gr.Progress(track_tqdm=True)):
if stem_audio_data is None:
gr.Warning("This stem is empty. Cannot visualize.")
return None, None, None
sample_rate, y_int = stem_audio_data
y = librosa.util.buf_to_float(y_int)
progress(0.3, desc="Finding transients...")
onset_frames = librosa.onset.onset_detect(y=librosa.to_mono(y.T) if y.ndim > 1 else y, sr=sample_rate, wait=1, pre_avg=1, post_avg=1, post_max=1, delta=0.05)
onset_times = librosa.frames_to_time(onset_frames, sr=sample_rate)
progress(0.7, desc="Generating waveform plot...")
fig, ax = plt.subplots(figsize=(10, 3))
fig.patch.set_facecolor('#1f2937')
ax.set_facecolor('#111827')
librosa.display.waveshow(y, sr=sample_rate, ax=ax, color='#32f6ff', alpha=0.7)
for t in onset_times:
ax.axvline(x=t, color='#ff3b3b', linestyle='--', linewidth=1)
ax.tick_params(colors='gray'); ax.xaxis.label.set_color('gray'); ax.yaxis.label.set_color('gray')
ax.set_xlabel("Time (s)"); ax.set_ylabel("Amplitude"); ax.set_title("Detected Slices", color='white')
plt.tight_layout()
progress(1, desc="Done!")
return fig, onset_times, stem_audio_data
def preview_slice(active_stem_audio, onset_times, evt: gr.SelectData):
if active_stem_audio is None or onset_times is None: return None
sample_rate, y = active_stem_audio; clicked_time = evt.index[0]
start_time = 0; end_time = len(y) / sample_rate
for i, t in enumerate(onset_times):
if t > clicked_time:
end_time = t; break
start_time = t
start_sample = librosa.time_to_samples(start_time, sr=sample_rate)
end_sample = librosa.time_to_samples(end_time, sr=sample_rate)
sliced_audio = y[start_sample:end_sample]
return (sample_rate, sliced_audio)
with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
gr.Markdown("# 🎵 Loop Architect")
onset_times_state = gr.State(value=None)
active_stem_state = gr.State(value=None)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 1. Separate Stems")
audio_input = gr.Audio(type="filepath", label="Upload a Track")
stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
submit_button = gr.Button("Separate Stems")
with gr.Column(scale=2):
with gr.Accordion("Separated Stems", open=True):
with gr.Row():
vocals_output = gr.Audio(label="Vocals", scale=4)
slice_vocals_btn = gr.Button("Visualize Slices", scale=1)
with gr.Row():
drums_output = gr.Audio(label="Drums", scale=4)
slice_drums_btn = gr.Button("Visualize Slices", scale=1)
with gr.Row():
bass_output = gr.Audio(label="Bass", scale=4)
slice_bass_btn = gr.Button("Visualize Slices", scale=1)
with gr.Row():
other_output = gr.Audio(label="Other / Instrumental", scale=4)
slice_other_btn = gr.Button("Visualize Slices", scale=1)
gr.Markdown("### Slice Editor")
slice_plot = gr.Plot(label="Click a region on the waveform to preview a slice")
preview_player = gr.Audio(label="Slice Preview")
submit_button.click(fn=separate_stems, inputs=[audio_input, stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
stem_options.change(fn=update_output_visibility, inputs=stem_options, outputs=[vocals_output, drums_output, bass_output, other_output])
slice_vocals_btn.click(fn=visualize_slices, inputs=vocals_output, outputs=[slice_plot, onset_times_state, active_stem_state])
slice_drums_btn.click(fn=visualize_slices, inputs=drums_output, outputs=[slice_plot, onset_times_state, active_stem_state])
slice_bass_btn.click(fn=visualize_slices, inputs=bass_output, outputs=[slice_plot, onset_times_state, active_stem_state])
slice_other_btn.click(fn=visualize_slices, inputs=other_output, outputs=[slice_plot, onset_times_state, active_stem_state])
slice_plot.select(fn=preview_slice, inputs=[active_stem_state, onset_times_state], outputs=preview_player)
demo.launch()