SaltProphet commited on
Commit
f9cba31
·
verified ·
1 Parent(s): 4453bee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -175
app.py CHANGED
@@ -6,235 +6,258 @@ import librosa
6
  import librosa.display
7
  import soundfile as sf
8
  import numpy as np
9
- import tempfile
10
  import zipfile
 
 
11
  import matplotlib
12
  matplotlib.use('Agg')
13
 
14
- # ------------------------------------------------------------
15
- # --- Stem Separation using Demucs ----------------------------
16
- # ------------------------------------------------------------
17
- async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
18
- if audio_file_path is None:
19
- raise gr.Error("No audio file uploaded!")
 
 
 
 
 
 
 
 
 
20
 
 
 
21
  log_history = "Starting separation...\n"
22
- yield { "status_log": log_history, "progress_bar": progress(0, desc="Starting...", visible=True) }
23
-
24
  try:
25
  progress(0.05, desc="Preparing audio file...")
 
26
  original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
27
  stable_input_path = f"stable_input_{original_filename_base}.wav"
28
  shutil.copy(audio_file_path, stable_input_path)
29
 
30
  model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
31
  output_dir = "separated"
32
- if os.path.exists(output_dir):
33
- shutil.rmtree(output_dir)
34
 
35
  command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
36
- log_history += f"Running Demucs: {command}\n"
37
- yield { "status_log": log_history, "progress_bar": progress(0.2, desc="Running Demucs...") }
38
 
39
  process = await asyncio.create_subprocess_shell(
40
- command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
41
- )
42
  stdout, stderr = await process.communicate()
 
43
  if process.returncode != 0:
44
- raise gr.Error(f"Demucs failed: {stderr.decode()[:500]}")
45
 
46
- log_history += "Demucs finished. Locating stem files...\n"
47
- yield { "status_log": log_history, "progress_bar": progress(0.8, desc="Locating stems...") }
48
 
49
  subfolders = [f.name for f in os.scandir(output_dir) if f.is_dir()]
50
- if not subfolders:
51
- raise gr.Error("Demucs output folder structure not found!")
52
-
53
  model_folder_name = subfolders[0]
54
- stems_path = os.path.join(output_dir, model_folder_name, original_filename_base)
55
 
56
- def get_path(name):
57
- p = os.path.join(stems_path, name)
58
- return p if os.path.exists(p) else None
59
 
60
- vocals_path = get_path("vocals.wav")
61
- drums_path = get_path("drums.wav")
62
- bass_path = get_path("bass.wav")
63
- other_name = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
64
- other_path = get_path(other_name)
65
 
66
  os.remove(stable_input_path)
67
 
68
- log_history += "✅ Stem separation complete!\n"
69
  yield {
70
- "status_log": log_history,
71
- "progress_bar": progress(1, desc="Complete!", visible=False),
72
- "vocals_output": vocals_path,
73
- "drums_output": drums_path,
74
- "bass_output": bass_path,
75
- "other_output": other_path
76
  }
77
-
78
  except Exception as e:
79
- yield { "status_log": log_history + f"❌ ERROR: {e}", "progress_bar": gr.update(visible=False) }
 
80
 
81
-
82
- # ------------------------------------------------------------
83
- # --- Slicing + Loop Generation -------------------------------
84
- # ------------------------------------------------------------
85
- def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, bpm_override=None, quantize_grid=False, progress_fn=None):
86
- if stem_audio_data is None:
87
- return None, None
88
-
89
- sr, y_int = stem_audio_data
90
- y = librosa.util.buf_to_float(y_int)
91
  y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
92
-
93
  if progress_fn: progress_fn(0.1, desc="Detecting BPM...")
94
- tempo, _ = librosa.beat.beat_track(y=y_mono, sr=sr)
95
- bpm = bpm_override if bpm_override else int(np.round(tempo))
96
- if bpm == 0: bpm = 120
97
-
98
- loops_dir = tempfile.mkdtemp()
99
- output_files = []
100
-
101
  if "One-Shots" in loop_choice:
102
  if progress_fn: progress_fn(0.3, desc="Finding transients...")
103
- onset_frames = librosa.onset.onset_detect(y=y_mono, sr=sr, delta=sensitivity, wait=1)
104
  onset_samples = librosa.frames_to_samples(onset_frames)
105
-
106
  if progress_fn: progress_fn(0.5, desc="Slicing one-shots...")
107
- for i, start in enumerate(onset_samples):
108
- end = onset_samples[i + 1] if i + 1 < len(onset_samples) else len(y)
109
- slice_data = y[start:end]
110
- fname = os.path.join(loops_dir, f"{stem_name}_one_shot_{i+1:03d}.wav")
111
- sf.write(fname, slice_data, sr)
112
- output_files.append(fname)
113
- if progress_fn: progress_fn(0.5 + (i / len(onset_samples) * 0.5), desc=f"{i+1}/{len(onset_samples)}")
114
-
 
 
115
  else:
116
- bars = int(loop_choice.split(" ")[0])
117
- spb = 60.0 / bpm
118
- spbar = spb * 4
119
- loop_len = int(spbar * bars * sr)
120
-
121
- if progress_fn: progress_fn(0.4, desc=f"Slicing {bars}-bar loops...")
122
- total = len(y) // loop_len
123
- for i in range(total):
124
- start = i * loop_len
125
- end = start + loop_len
126
- slice_data = y[start:end]
127
-
128
- # Quantize slices if requested
129
- if quantize_grid:
130
- slice_data = librosa.effects.time_stretch(slice_data, rate=1.0)
131
-
132
- fname = os.path.join(loops_dir, f"{stem_name}_{bars}bar_loop_{i+1:03d}_{bpm}bpm.wav")
133
- sf.write(fname, slice_data, sr)
134
- output_files.append(fname)
135
- if progress_fn: progress_fn(0.4 + (i / total * 0.6), desc=f"Loop {i+1}/{total}")
136
-
137
  return output_files, loops_dir
138
 
139
-
140
- # ------------------------------------------------------------
141
- # --- Slice All Stems & Zip ----------------------------------
142
- # ------------------------------------------------------------
143
- async def slice_all_and_zip(vocals, drums, bass, other, loop_choice, sensitivity, bpm_override, quantize_grid, progress=gr.Progress(track_tqdm=True)):
144
- log = "Starting batch slice...\n"
145
- yield { "status_log": log, "progress_bar": progress(0, desc="Starting...", visible=True) }
146
-
147
- stems = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
148
  zip_path = "Loop_Architect_Pack.zip"
149
- num_stems = sum(1 for v in stems.values() if v is not None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- if num_stems == 0:
152
- raise gr.Error("No stems to process! Please separate first.")
 
153
 
154
- tmp_dirs = []
 
 
155
 
156
- with zipfile.ZipFile(zip_path, "w") as zf:
157
- done = 0
158
- for name, data in stems.items():
159
- if data is not None:
160
- log += f"--- Slicing {name} ---\n"
161
- yield { "status_log": log }
162
 
163
- def update_prog(p, desc=""):
164
- overall = (done + p) / num_stems
165
- progress(overall, desc=f"{name}: {desc}", visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- files, tmp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, name, bpm_override, quantize_grid, progress_fn=update_prog)
168
- if files:
169
- log += f"Generated {len(files)} slices for {name}.\n"
170
- for f in files:
171
- zf.write(f, os.path.join(name, os.path.basename(f)))
172
- tmp_dirs.append(tmp_dir)
173
- done += 1
174
 
175
- for d in tmp_dirs:
176
- shutil.rmtree(d, ignore_errors=True)
177
 
178
- yield {
179
- "status_log": log + "✅ Pack ready!",
180
- "progress_bar": progress(1, desc="Done", visible=False),
181
- "download_zip_file": zip_path
182
- }
183
 
 
 
 
 
 
184
 
185
- # ------------------------------------------------------------
186
- # --- Gradio UI ----------------------------------------------
187
- # ------------------------------------------------------------
188
- with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
189
- gr.Markdown("# 🎵 Loop Architect — SALT PRØPHET Edition")
190
 
191
- with gr.Row():
192
- with gr.Column(scale=1):
193
- gr.Markdown("### 1️⃣ Separate Stems")
194
- audio_input = gr.Audio(type="filepath", label="Upload Track")
195
- stem_choice = gr.Radio(
196
- ["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"],
197
- label="Separation Type",
198
- value="4 Stems (Vocals, Drums, Bass, Other)"
199
- )
200
- separate_btn = gr.Button("Separate Stems", variant="primary")
201
-
202
- gr.Markdown("### 2️⃣ Slicing Options")
203
- loop_choice = gr.Radio(
204
- ["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"],
205
- label="Slice Type", value="One-Shots (All Transients)"
206
- )
207
- sensitivity = gr.Slider(0.01, 0.5, 0.05, step=0.01, label="One-Shot Sensitivity")
208
- bpm_override = gr.Number(label="BPM Override (optional)", value=None)
209
- quantize_grid = gr.Checkbox(label="Enable Quantized Slice Grid", value=False)
210
-
211
- gr.Markdown("### 3️⃣ Create Pack")
212
- slice_btn = gr.Button("Slice All & Create Pack")
213
- download_zip_file = gr.File(label="Download Pack", visible=False)
214
- progress_bar = gr.HTML()
215
- status_log = gr.Textbox(label="Status Log", lines=10)
216
 
217
- with gr.Column(scale=2):
218
- with gr.Accordion("Separated Stems", open=True):
219
- vocals_output = gr.Audio(label="Vocals")
220
- drums_output = gr.Audio(label="Drums")
221
- bass_output = gr.Audio(label="Bass")
222
- other_output = gr.Audio(label="Other / Instrumental")
223
-
224
- # --- Actions ---
225
- separate_btn.click(
226
- separate_stems,
227
- [audio_input, stem_choice],
228
- [vocals_output, drums_output, bass_output, other_output, status_log]
229
- )
230
 
231
- slice_btn.click(
232
- slice_all_and_zip,
233
- [vocals_output, drums_output, bass_output, other_output, loop_choice, sensitivity, bpm_override, quantize_grid],
234
- [download_zip_file, status_log]
235
  )
236
 
237
- # ------------------------------------------------------------
238
- # --- Launch --------------------------------------------------
239
- # ------------------------------------------------------------
240
- demo.launch()
 
6
  import librosa.display
7
  import soundfile as sf
8
  import numpy as np
9
+ import time
10
  import zipfile
11
+ import tempfile
12
+ import matplotlib.pyplot as plt
13
  import matplotlib
14
  matplotlib.use('Agg')
15
 
16
+ def update_output_visibility(choice):
17
+ if "2 Stems" in choice:
18
+ return {
19
+ vocals_output: gr.update(visible=True),
20
+ drums_output: gr.update(visible=False),
21
+ bass_output: gr.update(visible=False),
22
+ other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
23
+ }
24
+ elif "4 Stems" in choice:
25
+ return {
26
+ vocals_output: gr.update(visible=True),
27
+ drums_output: gr.update(visible=True),
28
+ bass_output: gr.update(visible=True),
29
+ other_output: gr.update(visible=True, label="Other")
30
+ }
31
 
32
+ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
33
+ if audio_file_path is None: raise gr.Error("No audio file uploaded!")
34
  log_history = "Starting separation...\n"
35
+ yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
 
36
  try:
37
  progress(0.05, desc="Preparing audio file...")
38
+ log_history += "Preparing audio file...\n"; yield { status_log: log_history, progress_bar: progress(0.05, desc="Preparing...") }
39
  original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
40
  stable_input_path = f"stable_input_{original_filename_base}.wav"
41
  shutil.copy(audio_file_path, stable_input_path)
42
 
43
  model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
44
  output_dir = "separated"
45
+ if os.path.exists(output_dir): shutil.rmtree(output_dir)
 
46
 
47
  command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
48
+ log_history += f"Running Demucs command: {command}\n(This may take a minute or more depending on track length)\n";
49
+ yield { status_log: log_history, progress_bar: progress(0.2, desc="Running Demucs...") }
50
 
51
  process = await asyncio.create_subprocess_shell(
52
+ command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
 
53
  stdout, stderr = await process.communicate()
54
+
55
  if process.returncode != 0:
56
+ raise gr.Error(f"Demucs failed. Error: {stderr.decode()[:500]}")
57
 
58
+ log_history += "Demucs finished. Locating stem files...\n"; yield { status_log: log_history, progress_bar: progress(0.8, desc="Locating stems...") }
59
+ stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
60
 
61
  subfolders = [f.name for f in os.scandir(output_dir) if f.is_dir()]
62
+ if not subfolders: raise gr.Error("Demucs output folder structure not found!")
 
 
63
  model_folder_name = subfolders[0]
64
+ stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
65
 
66
+ if not os.path.exists(stems_path):
67
+ raise gr.Error(f"Demucs output directory was not found! Looked for: {stems_path}")
 
68
 
69
+ vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
70
+ drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
71
+ bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
72
+ other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
73
+ other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
74
 
75
  os.remove(stable_input_path)
76
 
77
+ log_history += "✅ Stem separation complete!\n";
78
  yield {
79
+ status_log: log_history,
80
+ progress_bar: progress(1, desc="Complete!", visible=False), # Hide progress bar when done
81
+ vocals_output: gr.update(value=vocals_path),
82
+ drums_output: gr.update(value=drums_path),
83
+ bass_output: gr.update(value=bass_path),
84
+ other_output: gr.update(value=other_path)
85
  }
 
86
  except Exception as e:
87
+ print(f"An error occurred during separation: {e}")
88
+ yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) } # Hide progress bar on error
89
 
90
+ def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, progress_fn=None):
91
+ if stem_audio_data is None: return None, None
92
+ sample_rate, y_int = stem_audio_data; y = librosa.util.buf_to_float(y_int)
 
 
 
 
 
 
 
93
  y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
 
94
  if progress_fn: progress_fn(0.1, desc="Detecting BPM...")
95
+ tempo, beats = librosa.beat.beat_track(y=y_mono, sr=sample_rate)
96
+ bpm = 120 if tempo is None else np.round(tempo); bpm_int = int(bpm.item())
97
+ if bpm_int == 0: bpm_int = 120; print("BPM detection failed, defaulting to 120 BPM.")
98
+ print(f"Detected BPM for {stem_name}: {bpm_int}")
99
+ output_files = []; loops_dir = tempfile.mkdtemp()
 
 
100
  if "One-Shots" in loop_choice:
101
  if progress_fn: progress_fn(0.3, desc="Finding transients...")
102
+ onset_frames = librosa.onset.onset_detect(y=y_mono, sr=sample_rate, delta=sensitivity, wait=1, pre_avg=1, post_avg=1, post_max=1)
103
  onset_samples = librosa.frames_to_samples(onset_frames)
 
104
  if progress_fn: progress_fn(0.5, desc="Slicing one-shots...")
105
+ if len(onset_samples) > 0:
106
+ num_onsets = len(onset_samples)
107
+ for i, start_sample in enumerate(onset_samples):
108
+ end_sample = onset_samples[i+1] if i+1 < num_onsets else len(y)
109
+ slice_data = y[start_sample:end_sample]
110
+ filename = os.path.join(loops_dir, f"{stem_name}_one_shot_{i+1:03d}.wav")
111
+ sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
112
+ output_files.append(filename)
113
+ if progress_fn and num_onsets > 1:
114
+ progress_fn(0.5 + (i / (num_onsets - 1) * 0.5), desc=f"Exporting slice {i+1}/{num_onsets}...")
115
  else:
116
+ bars = int(loop_choice.split(" ")[0]); seconds_per_beat = 60.0 / bpm_int; seconds_per_bar = seconds_per_beat * 4
117
+ loop_duration_seconds = seconds_per_bar * bars; loop_duration_samples = int(loop_duration_seconds * sample_rate)
118
+ if progress_fn: progress_fn(0.4, desc=f"Slicing into {bars}-bar loops...")
119
+ num_loops = len(y) // loop_duration_samples
120
+ if num_loops == 0: print(f"Audio for {stem_name} is too short for {bars}-bar loops at {bpm_int} BPM."); return None, None
121
+ for i in range(num_loops):
122
+ start_sample = i * loop_duration_samples; end_sample = start_sample + loop_duration_samples
123
+ slice_data = y[start_sample:end_sample]
124
+ filename = os.path.join(loops_dir, f"{stem_name}_{bars}bar_loop_{i+1:03d}_{bpm_int}bpm.wav")
125
+ sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
126
+ output_files.append(filename)
127
+ if progress_fn and num_loops > 1:
128
+ progress_fn(0.4 + (i / (num_loops - 1) * 0.6), desc=f"Exporting loop {i+1}/{num_loops}...")
129
+ if not output_files: return None, None
 
 
 
 
 
 
 
130
  return output_files, loops_dir
131
 
132
+ async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
133
+ log_history = "Starting batch slice...\n"
134
+ yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
135
+ await asyncio.sleep(0.1)
136
+ stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
 
 
 
 
137
  zip_path = "Loop_Architect_Pack.zip"
138
+ num_stems = sum(1 for data in stems_to_process.values() if data is not None)
139
+ if num_stems == 0: raise gr.Error("No stems to process! Please separate stems first.")
140
+ all_temp_dirs = []
141
+ try:
142
+ with zipfile.ZipFile(zip_path, 'w') as zf:
143
+ processed_count = 0
144
+ for name, data in stems_to_process.items():
145
+ if data is not None:
146
+ log_history += f"--- Slicing {name} stem ---\n"; yield { status_log: log_history }
147
+ def update_main_progress(p, desc=""):
148
+ overall_progress = (processed_count + p) / num_stems
149
+ progress(overall_progress, desc=f"Slicing {name}: {desc}", visible=True)
150
+
151
+ sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, name, progress_fn=update_main_progress)
152
+
153
+ if sliced_files:
154
+ log_history += f"Generated {len(sliced_files)} slices for {name}.\n"; yield { status_log: log_history }
155
+ all_temp_dirs.append(temp_dir)
156
+ for loop_file in sliced_files:
157
+ arcname = os.path.join(name, os.path.basename(loop_file))
158
+ zf.write(loop_file, arcname)
159
+ else:
160
+ log_history += f"No slices generated for {name}.\n"; yield { status_log: log_history }
161
+
162
+ processed_count += 1
163
+ yield { status_log: log_history, progress_bar: progress(processed_count / num_stems, desc=f"Finished {name}", visible=True) }
164
+
165
+ log_history += "Packaging complete!\n";
166
+ yield {
167
+ status_log: log_history + "✅ Pack ready for download!",
168
+ progress_bar: progress(1, desc="Pack Ready!", visible=False),
169
+ download_zip_file: gr.update(value=zip_path, visible=True)
170
+ }
171
+ except Exception as e:
172
+ print(f"An error occurred during slice all: {e}")
173
+ yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) }
174
+ finally:
175
+ for d in all_temp_dirs:
176
+ if d and os.path.exists(d):
177
+ shutil.rmtree(d)
178
+
179
+ # --- Create the full Gradio Interface ---
180
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
181
+ gr.Markdown("# 🎵 Loop Architect")
182
+ with gr.Row():
183
+ with gr.Column(scale=1):
184
+ gr.Markdown("### 1. Separate Stems")
185
+ audio_input = gr.Audio(type="filepath", label="Upload a Track")
186
+ stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
187
+ submit_button = gr.Button("Separate Stems")
188
 
189
+ with gr.Accordion("Slicing Options", open=True):
190
+ loop_options_radio = gr.Radio(["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"], label="Slice Type", value="One-Shots (All Transients)")
191
+ sensitivity_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.05, step=0.01, label="One-Shot Sensitivity", info="Lower values = more slices")
192
 
193
+ gr.Markdown("### 3. Create Pack")
194
+ slice_all_button = gr.Button("Slice All Stems & Create Pack", variant="primary")
195
+ download_zip_file = gr.File(label="Download Your Loop Pack", visible=False)
196
 
197
+ gr.Markdown("### Status")
198
+ # --- THIS IS THE FIX --- Initialize without any arguments
199
+ progress_bar = gr.Progress()
200
+ status_log = gr.Textbox(label="Status Log", lines=10, interactive=False)
 
 
201
 
202
+ with gr.Column(scale=2):
203
+ with gr.Accordion("Separated Stems", open=True):
204
+ with gr.Row():
205
+ vocals_output = gr.Audio(label="Vocals", scale=4)
206
+ slice_vocals_btn = gr.Button("Slice Vocals", scale=1)
207
+ with gr.Row():
208
+ drums_output = gr.Audio(label="Drums", scale=4)
209
+ slice_drums_btn = gr.Button("Slice Drums", scale=1)
210
+ with gr.Row():
211
+ bass_output = gr.Audio(label="Bass", scale=4)
212
+ slice_bass_btn = gr.Button("Slice Bass", scale=1)
213
+ with gr.Row():
214
+ other_output = gr.Audio(label="Other / Instrumental", scale=4)
215
+ slice_other_btn = gr.Button("Slice Other", scale=1)
216
+ gr.Markdown("### Sliced Loops / Samples (Preview)")
217
+ loop_gallery = gr.Gallery(label="Generated Loops Preview", columns=8, object_fit="contain", height="auto", preview=True)
218
+
219
+ # --- Define Event Listeners ---
220
+ def slice_and_display(stem_data, loop_choice, sensitivity, stem_name):
221
+ log_history = f"Slicing {stem_name}...\n"
222
+ # Make progress bar visible when starting
223
+ yield {
224
+ status_log: log_history,
225
+ progress_bar: gr.update(value=0, visible=True, label=f"Slicing {stem_name}...")
226
+ }
227
 
228
+ # Define how slice_stem_real updates the progress bar using gr.Progress directly
229
+ def update_single_progress(p, desc=""):
230
+ gr.Progress(track_tqdm=True)(p, desc=desc) # Pass progress updates
 
 
 
 
231
 
232
+ files, temp_dir = slice_stem_real(stem_data, loop_choice, sensitivity, stem_name, progress_fn=update_single_progress)
 
233
 
234
+ if temp_dir and os.path.exists(temp_dir):
235
+ shutil.rmtree(temp_dir)
 
 
 
236
 
237
+ yield {
238
+ loop_gallery: gr.update(value=files),
239
+ status_log: log_history + f"✅ Sliced {stem_name} into {len(files) if files else 0} pieces.",
240
+ progress_bar: gr.update(visible=False) # Hide progress bar when done
241
+ }
242
 
243
+ submit_event = submit_button.click(
244
+ fn=separate_stems,
245
+ inputs=[audio_input, stem_options],
246
+ outputs=[vocals_output, drums_output, bass_output, other_output, status_log]
247
+ )
248
 
249
+ stem_options.change(fn=update_output_visibility, inputs=stem_options, outputs=[vocals_output, drums_output, bass_output, other_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
+ slice_vocals_btn.click(fn=slice_and_display, inputs=[vocals_output, loop_options_radio, sensitivity_slider, gr.Textbox("vocals", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
252
+ slice_drums_btn.click(fn=slice_and_display, inputs=[drums_output, loop_options_radio, sensitivity_slider, gr.Textbox("drums", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
253
+ slice_bass_btn.click(fn=slice_and_display, inputs=[bass_output, loop_options_radio, sensitivity_slider, gr.Textbox("bass", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
254
+ slice_other_btn.click(fn=slice_and_display, inputs=[other_output, loop_options_radio, sensitivity_slider, gr.Textbox("other", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
 
 
 
 
 
 
 
 
 
255
 
256
+ slice_all_event = slice_all_button.click(
257
+ fn=slice_all_and_zip_real,
258
+ inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider],
259
+ outputs=[download_zip_file, download_zip_file, status_log]
260
  )
261
 
262
+ # --- Launch the UI ---
263
+ demo.launch()