SaltProphet commited on
Commit
62a18f2
·
verified ·
1 Parent(s): 973e8b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +228 -229
app.py CHANGED
@@ -1,230 +1,229 @@
1
- # 1. Install all necessary libraries for the full application
2
- !pip install gradio "demucs>=4.0.0" librosa soundfile matplotlib
3
-
4
- # 2. Import libraries
5
- import gradio as gr
6
- import os
7
- import shutil
8
- import asyncio
9
- import librosa
10
- import librosa.display
11
- import soundfile as sf
12
- import numpy as np
13
- import time
14
- import zipfile
15
- import tempfile
16
- import matplotlib.pyplot as plt
17
- import matplotlib
18
- matplotlib.use('Agg') # Use a non-interactive backend for plotting
19
-
20
- # --- Helper/Processing Functions ---
21
-
22
- def update_output_visibility(choice):
23
- if "2 Stems" in choice:
24
- return {
25
- vocals_output: gr.update(visible=True),
26
- drums_output: gr.update(visible=False),
27
- bass_output: gr.update(visible=False),
28
- other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
29
- }
30
- elif "4 Stems" in choice:
31
- return {
32
- vocals_output: gr.update(visible=True),
33
- drums_output: gr.update(visible=True),
34
- bass_output: gr.update(visible=True),
35
- other_output: gr.update(visible=True, label="Other")
36
- }
37
-
38
- async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
39
- if audio_file_path is None: raise gr.Error("No audio file uploaded!")
40
- progress(0, desc="Starting...")
41
- try:
42
- progress(0.05, desc="Preparing audio file...")
43
- original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
44
- stable_input_path = f"stable_input_{original_filename_base}.wav"
45
- shutil.copy(audio_file_path, stable_input_path)
46
-
47
- model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
48
- output_dir = "separated"
49
- if os.path.exists(output_dir): shutil.rmtree(output_dir)
50
-
51
- command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
52
- progress(0.2, desc="Running Demucs (this may take a minute)...")
53
-
54
- process = await asyncio.create_subprocess_shell(
55
- command,
56
- stdout=asyncio.subprocess.PIPE,
57
- stderr=asyncio.subprocess.PIPE)
58
-
59
- stdout, stderr = await process.communicate()
60
-
61
- if process.returncode != 0:
62
- raise gr.Error(f"Demucs failed to run. Error: {stderr.decode()[:500]}")
63
-
64
- progress(0.8, desc="Locating separated stem files...")
65
- stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
66
- model_folder_name = next(os.walk(output_dir))[1][0]
67
- stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
68
-
69
- if not os.path.exists(stems_path):
70
- raise gr.Error(f"Demucs finished, but the output directory was not found!")
71
-
72
- vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
73
- drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
74
- bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
75
- other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
76
- other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
77
-
78
- os.remove(stable_input_path)
79
- return vocals_path, drums_path, bass_path, other_path
80
- except Exception as e:
81
- print(f"An error occurred: {e}")
82
- raise gr.Error(str(e))
83
-
84
- def slice_stem_real(stem_audio_data, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
85
- if stem_audio_data is None:
86
- gr.Warning("This stem is empty. Cannot slice.")
87
- return None, None
88
-
89
- sample_rate, 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
- progress(0.1, desc="Detecting BPM...")
94
- tempo, beats = librosa.beat.beat_track(y=y_mono, sr=sample_rate)
95
- bpm = 120 if tempo is None else np.round(tempo)
96
- bpm_int = int(bpm.item())
97
-
98
- if bpm_int == 0:
99
- bpm_int = 120
100
- gr.Warning("BPM detection failed, defaulting to 120 BPM.")
101
- print(f"Detected BPM: {bpm_int}")
102
-
103
- output_files = []
104
- loops_dir = tempfile.mkdtemp()
105
-
106
- if "One-Shots" in loop_choice:
107
- progress(0.3, desc="Finding transients...")
108
- 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)
109
- onset_samples = librosa.frames_to_samples(onset_frames)
110
-
111
- progress(0.5, desc="Slicing one-shots...")
112
- if len(onset_samples) > 0:
113
- for i, start_sample in enumerate(onset_samples):
114
- end_sample = onset_samples[i+1] if i+1 < len(onset_samples) else len(y)
115
- slice_data = y[start_sample:end_sample]
116
- filename = os.path.join(loops_dir, f"one_shot_{i+1:03d}.wav")
117
- sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
118
- output_files.append(filename)
119
- if len(onset_samples) > 1:
120
- progress(0.5 + (i / (len(onset_samples) - 1) * 0.5), desc=f"Exporting slice {i+1}...")
121
- else: # Handle bar loops
122
- bars = int(loop_choice.split(" ")[0])
123
- seconds_per_beat = 60.0 / bpm_int
124
- seconds_per_bar = seconds_per_beat * 4
125
- loop_duration_seconds = seconds_per_bar * bars
126
- loop_duration_samples = int(loop_duration_seconds * sample_rate)
127
-
128
- progress(0.4, desc=f"Slicing into {bars}-bar loops...")
129
- num_loops = len(y) // loop_duration_samples
130
- if num_loops == 0:
131
- gr.Warning(f"Audio is too short to create a {bars}-bar loop at {bpm_int} BPM.")
132
- return None, None
133
- for i in range(num_loops):
134
- start_sample = i * loop_duration_samples
135
- end_sample = start_sample + loop_duration_samples
136
- slice_data = y[start_sample:end_sample]
137
- filename = os.path.join(loops_dir, f"{bars}bar_loop_{i+1:03d}_{bpm_int}bpm.wav")
138
- sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
139
- output_files.append(filename)
140
- if num_loops > 1:
141
- progress(0.4 + (i / (num_loops - 1) * 0.6), desc=f"Exporting loop {i+1}...")
142
-
143
- if not output_files:
144
- gr.Warning("No loops or one-shots could be generated.")
145
- return None, None
146
-
147
- return output_files, loops_dir
148
-
149
- async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
150
- progress(0, desc="Starting batch slice...")
151
- await asyncio.sleep(0.1)
152
- stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
153
-
154
- zip_path = "Loop_Architect_Pack.zip"
155
-
156
- num_stems = sum(1 for data in stems_to_process.values() if data is not None)
157
- if num_stems == 0: raise gr.Error("No stems to process! Please separate stems first.")
158
-
159
- with zipfile.ZipFile(zip_path, 'w') as zf:
160
- processed_count = 0
161
- for name, data in stems_to_process.items():
162
- if data is not None:
163
- def update_main_progress(p, desc=""):
164
- overall_progress = (processed_count + p) / num_stems
165
- progress(overall_progress, desc=f"Slicing {name}: {desc}")
166
-
167
- sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, progress=update_main_progress)
168
-
169
- if sliced_files:
170
- for loop_file in sliced_files:
171
- arcname = os.path.join(name, os.path.basename(loop_file))
172
- zf.write(loop_file, arcname)
173
-
174
- if temp_dir and os.path.exists(temp_dir):
175
- shutil.rmtree(temp_dir)
176
- processed_count += 1
177
-
178
- progress(1, desc="Pack Ready!")
179
- return zip_path, gr.update(visible=True)
180
-
181
-
182
- # --- Create the full Gradio Interface ---
183
- with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
184
- gr.Markdown("# 🎵 Loop Architect")
185
- with gr.Row():
186
- with gr.Column(scale=1):
187
- gr.Markdown("### 1. Separate Stems")
188
- audio_input = gr.Audio(type="filepath", label="Upload a Track")
189
- stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
190
- submit_button = gr.Button("Separate Stems")
191
- with gr.Accordion("Slicing Options", open=True):
192
- loop_options_radio = gr.Radio(["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"], label="Loop Length", value="One-Shots (All Transients)")
193
- sensitivity_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.05, step=0.01, label="One-Shot Sensitivity")
194
- gr.Markdown("### 3. Create Pack")
195
- slice_all_button = gr.Button("Slice All Stems & Create Pack", variant="primary")
196
- download_zip_file = gr.File(label="Download Your Loop Pack", visible=False)
197
- with gr.Column(scale=2):
198
- with gr.Accordion("Separated Stems", open=True):
199
- with gr.Row():
200
- vocals_output = gr.Audio(label="Vocals", scale=4)
201
- slice_vocals_btn = gr.Button("Slice", scale=1)
202
- with gr.Row():
203
- drums_output = gr.Audio(label="Drums", scale=4)
204
- slice_drums_btn = gr.Button("Slice", scale=1)
205
- with gr.Row():
206
- bass_output = gr.Audio(label="Bass", scale=4)
207
- slice_bass_btn = gr.Button("Slice", scale=1)
208
- with gr.Row():
209
- other_output = gr.Audio(label="Other / Instrumental", scale=4)
210
- slice_other_btn = gr.Button("Slice", scale=1)
211
- gr.Markdown("### Sliced Loops / Samples")
212
- loop_gallery = gr.Gallery(label="Generated Loops", columns=8, object_fit="contain", height="auto")
213
-
214
- # --- Define Event Listeners ---
215
- def slice_and_display(stem_data, loop_choice, sensitivity):
216
- files, _ = slice_stem_real(stem_data, loop_choice, sensitivity)
217
- return files
218
-
219
- submit_button.click(fn=separate_stems, inputs=[audio_input, stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
220
- stem_options.change(fn=update_output_visibility, inputs=[stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
221
- slice_vocals_btn.click(fn=slice_and_display, inputs=[vocals_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
222
- slice_drums_btn.click(fn=slice_and_display, inputs=[drums_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
223
- slice_bass_btn.click(fn=slice_and_display, inputs=[bass_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
224
- slice_other_btn.click(fn=slice_and_display, inputs=[other_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
225
- slice_all_button.click(fn=slice_all_and_zip_real, inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider], outputs=[download_zip_file, download_zip_file])
226
-
227
- # --- Launch the UI ---
228
- # Use demo.launch() for Hugging Face Spaces
229
- # Use demo.launch(debug=True) for local testing
230
  demo.launch()
 
1
+ # 1. Install all necessary libraries for the full application
2
+
3
+ # 2. Import libraries
4
+ import gradio as gr
5
+ import os
6
+ import shutil
7
+ import asyncio
8
+ import librosa
9
+ import librosa.display
10
+ import soundfile as sf
11
+ import numpy as np
12
+ import time
13
+ import zipfile
14
+ import tempfile
15
+ import matplotlib.pyplot as plt
16
+ import matplotlib
17
+ matplotlib.use('Agg') # Use a non-interactive backend for plotting
18
+
19
+ # --- Helper/Processing Functions ---
20
+
21
+ def update_output_visibility(choice):
22
+ if "2 Stems" in choice:
23
+ return {
24
+ vocals_output: gr.update(visible=True),
25
+ drums_output: gr.update(visible=False),
26
+ bass_output: gr.update(visible=False),
27
+ other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
28
+ }
29
+ elif "4 Stems" in choice:
30
+ return {
31
+ vocals_output: gr.update(visible=True),
32
+ drums_output: gr.update(visible=True),
33
+ bass_output: gr.update(visible=True),
34
+ other_output: gr.update(visible=True, label="Other")
35
+ }
36
+
37
+ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
38
+ if audio_file_path is None: raise gr.Error("No audio file uploaded!")
39
+ progress(0, desc="Starting...")
40
+ try:
41
+ progress(0.05, desc="Preparing audio file...")
42
+ original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
43
+ stable_input_path = f"stable_input_{original_filename_base}.wav"
44
+ shutil.copy(audio_file_path, stable_input_path)
45
+
46
+ model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
47
+ output_dir = "separated"
48
+ if os.path.exists(output_dir): shutil.rmtree(output_dir)
49
+
50
+ command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
51
+ progress(0.2, desc="Running Demucs (this may take a minute)...")
52
+
53
+ process = await asyncio.create_subprocess_shell(
54
+ command,
55
+ stdout=asyncio.subprocess.PIPE,
56
+ stderr=asyncio.subprocess.PIPE)
57
+
58
+ stdout, stderr = await process.communicate()
59
+
60
+ if process.returncode != 0:
61
+ raise gr.Error(f"Demucs failed to run. Error: {stderr.decode()[:500]}")
62
+
63
+ progress(0.8, desc="Locating separated stem files...")
64
+ stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
65
+ model_folder_name = next(os.walk(output_dir))[1][0]
66
+ stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
67
+
68
+ if not os.path.exists(stems_path):
69
+ raise gr.Error(f"Demucs finished, but the output directory was not found!")
70
+
71
+ vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
72
+ drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
73
+ bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
74
+ other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
75
+ other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
76
+
77
+ os.remove(stable_input_path)
78
+ return vocals_path, drums_path, bass_path, other_path
79
+ except Exception as e:
80
+ print(f"An error occurred: {e}")
81
+ raise gr.Error(str(e))
82
+
83
+ def slice_stem_real(stem_audio_data, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
84
+ if stem_audio_data is None:
85
+ gr.Warning("This stem is empty. Cannot slice.")
86
+ return None, None
87
+
88
+ sample_rate, y_int = stem_audio_data
89
+ y = librosa.util.buf_to_float(y_int)
90
+ y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
91
+
92
+ progress(0.1, desc="Detecting BPM...")
93
+ tempo, beats = librosa.beat.beat_track(y=y_mono, sr=sample_rate)
94
+ bpm = 120 if tempo is None else np.round(tempo)
95
+ bpm_int = int(bpm.item())
96
+
97
+ if bpm_int == 0:
98
+ bpm_int = 120
99
+ gr.Warning("BPM detection failed, defaulting to 120 BPM.")
100
+ print(f"Detected BPM: {bpm_int}")
101
+
102
+ output_files = []
103
+ loops_dir = tempfile.mkdtemp()
104
+
105
+ if "One-Shots" in loop_choice:
106
+ progress(0.3, desc="Finding transients...")
107
+ 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)
108
+ onset_samples = librosa.frames_to_samples(onset_frames)
109
+
110
+ progress(0.5, desc="Slicing one-shots...")
111
+ if len(onset_samples) > 0:
112
+ for i, start_sample in enumerate(onset_samples):
113
+ end_sample = onset_samples[i+1] if i+1 < len(onset_samples) else len(y)
114
+ slice_data = y[start_sample:end_sample]
115
+ filename = os.path.join(loops_dir, f"one_shot_{i+1:03d}.wav")
116
+ sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
117
+ output_files.append(filename)
118
+ if len(onset_samples) > 1:
119
+ progress(0.5 + (i / (len(onset_samples) - 1) * 0.5), desc=f"Exporting slice {i+1}...")
120
+ else: # Handle bar loops
121
+ bars = int(loop_choice.split(" ")[0])
122
+ seconds_per_beat = 60.0 / bpm_int
123
+ seconds_per_bar = seconds_per_beat * 4
124
+ loop_duration_seconds = seconds_per_bar * bars
125
+ loop_duration_samples = int(loop_duration_seconds * sample_rate)
126
+
127
+ progress(0.4, desc=f"Slicing into {bars}-bar loops...")
128
+ num_loops = len(y) // loop_duration_samples
129
+ if num_loops == 0:
130
+ gr.Warning(f"Audio is too short to create a {bars}-bar loop at {bpm_int} BPM.")
131
+ return None, None
132
+ for i in range(num_loops):
133
+ start_sample = i * loop_duration_samples
134
+ end_sample = start_sample + loop_duration_samples
135
+ slice_data = y[start_sample:end_sample]
136
+ filename = os.path.join(loops_dir, f"{bars}bar_loop_{i+1:03d}_{bpm_int}bpm.wav")
137
+ sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
138
+ output_files.append(filename)
139
+ if num_loops > 1:
140
+ progress(0.4 + (i / (num_loops - 1) * 0.6), desc=f"Exporting loop {i+1}...")
141
+
142
+ if not output_files:
143
+ gr.Warning("No loops or one-shots could be generated.")
144
+ return None, None
145
+
146
+ return output_files, loops_dir
147
+
148
+ async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
149
+ progress(0, desc="Starting batch slice...")
150
+ await asyncio.sleep(0.1)
151
+ stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
152
+
153
+ zip_path = "Loop_Architect_Pack.zip"
154
+
155
+ num_stems = sum(1 for data in stems_to_process.values() if data is not None)
156
+ if num_stems == 0: raise gr.Error("No stems to process! Please separate stems first.")
157
+
158
+ with zipfile.ZipFile(zip_path, 'w') as zf:
159
+ processed_count = 0
160
+ for name, data in stems_to_process.items():
161
+ if data is not None:
162
+ def update_main_progress(p, desc=""):
163
+ overall_progress = (processed_count + p) / num_stems
164
+ progress(overall_progress, desc=f"Slicing {name}: {desc}")
165
+
166
+ sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, progress=update_main_progress)
167
+
168
+ if sliced_files:
169
+ for loop_file in sliced_files:
170
+ arcname = os.path.join(name, os.path.basename(loop_file))
171
+ zf.write(loop_file, arcname)
172
+
173
+ if temp_dir and os.path.exists(temp_dir):
174
+ shutil.rmtree(temp_dir)
175
+ processed_count += 1
176
+
177
+ progress(1, desc="Pack Ready!")
178
+ return zip_path, gr.update(visible=True)
179
+
180
+
181
+ # --- Create the full Gradio Interface ---
182
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
183
+ gr.Markdown("# 🎵 Loop Architect")
184
+ with gr.Row():
185
+ with gr.Column(scale=1):
186
+ gr.Markdown("### 1. Separate Stems")
187
+ audio_input = gr.Audio(type="filepath", label="Upload a Track")
188
+ stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
189
+ submit_button = gr.Button("Separate Stems")
190
+ with gr.Accordion("Slicing Options", open=True):
191
+ loop_options_radio = gr.Radio(["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"], label="Loop Length", value="One-Shots (All Transients)")
192
+ sensitivity_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.05, step=0.01, label="One-Shot Sensitivity")
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
+ with gr.Column(scale=2):
197
+ with gr.Accordion("Separated Stems", open=True):
198
+ with gr.Row():
199
+ vocals_output = gr.Audio(label="Vocals", scale=4)
200
+ slice_vocals_btn = gr.Button("Slice", scale=1)
201
+ with gr.Row():
202
+ drums_output = gr.Audio(label="Drums", scale=4)
203
+ slice_drums_btn = gr.Button("Slice", scale=1)
204
+ with gr.Row():
205
+ bass_output = gr.Audio(label="Bass", scale=4)
206
+ slice_bass_btn = gr.Button("Slice", scale=1)
207
+ with gr.Row():
208
+ other_output = gr.Audio(label="Other / Instrumental", scale=4)
209
+ slice_other_btn = gr.Button("Slice", scale=1)
210
+ gr.Markdown("### Sliced Loops / Samples")
211
+ loop_gallery = gr.Gallery(label="Generated Loops", columns=8, object_fit="contain", height="auto")
212
+
213
+ # --- Define Event Listeners ---
214
+ def slice_and_display(stem_data, loop_choice, sensitivity):
215
+ files, _ = slice_stem_real(stem_data, loop_choice, sensitivity)
216
+ return files
217
+
218
+ submit_button.click(fn=separate_stems, inputs=[audio_input, stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
219
+ stem_options.change(fn=update_output_visibility, inputs=[stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
220
+ slice_vocals_btn.click(fn=slice_and_display, inputs=[vocals_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
221
+ slice_drums_btn.click(fn=slice_and_display, inputs=[drums_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
222
+ slice_bass_btn.click(fn=slice_and_display, inputs=[bass_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
223
+ slice_other_btn.click(fn=slice_and_display, inputs=[other_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
224
+ slice_all_button.click(fn=slice_all_and_zip_real, inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider], outputs=[download_zip_file, download_zip_file])
225
+
226
+ # --- Launch the UI ---
227
+ # Use demo.launch() for Hugging Face Spaces
228
+ # Use demo.launch(debug=True) for local testing
 
229
  demo.launch()