tee342 commited on
Commit
20b0989
Β·
verified Β·
1 Parent(s): 3392353

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +569 -18
app.py CHANGED
@@ -1,16 +1,556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  with gr.Blocks() as demo:
2
  gr.HTML('<h3 style="text-align:center;">Where Your Audio Meets Intelligence</h3>')
3
  gr.Markdown('### Upload, edit, export β€” powered by AI!')
4
 
 
 
 
 
 
5
  # --- Single File Studio Tab ---
6
  with gr.Tab("🎡 Single File Studio"):
7
  with gr.Row():
8
  with gr.Column():
9
  input_audio = gr.Audio(label="Upload Audio", type="filepath")
10
- effect_checkbox = gr.CheckboxGroup(
11
- choices=list({e for effects in preset_choices.values() for e in effects}),
12
- label="Apply Effects in Order"
13
- )
14
  preset_dropdown = gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0])
15
  export_format = gr.Dropdown(choices=["WAV", "MP3"], label="Export Format", value="WAV")
16
  isolate_vocals = gr.Checkbox(label="Isolate Vocals After Effects")
@@ -23,17 +563,18 @@ with gr.Blocks() as demo:
23
  genre_out = gr.Textbox(label="Detected Genre")
24
  status_box = gr.Textbox(label="Status", lines=1, value="Ready")
25
 
26
- def update_effects_for_preset(preset_name):
27
- return preset_choices.get(preset_name, [])
28
-
29
- preset_dropdown.change(update_effects_for_preset, inputs=preset_dropdown, outputs=effect_checkbox)
 
30
 
31
- def execute_processing(audio_path, effects, isolate, preset, fmt):
32
  effs = preset_choices.get(preset, []) if preset in preset_choices else effects
33
- return process_audio(audio_path, effs, isolate, preset, fmt)
34
 
35
  process_btn.click(
36
- fn=execute_processing,
37
  inputs=[input_audio, effect_checkbox, isolate_vocals, preset_dropdown, export_format],
38
  outputs=[output_audio, waveform_img, session_log_out, genre_out, status_box]
39
  )
@@ -51,7 +592,7 @@ with gr.Blocks() as demo:
51
  other_file = gr.File(label="Other")
52
 
53
  split_button.click(
54
- fn=stem_split,
55
  inputs=remix_input,
56
  outputs=[vocals_file, drums_file, bass_file, other_file]
57
  )
@@ -63,8 +604,10 @@ with gr.Blocks() as demo:
63
  remaster_status = gr.Textbox(label="Status", value="Ready", interactive=False)
64
  remaster_btn = gr.Button("Remaster")
65
 
66
- remaster_btn.click(fn=ai_remaster, inputs=remaster_input, outputs=remaster_output)
67
- remaster_btn.click(lambda _: "Done!", remaster_btn, remaster_status)
 
 
68
 
69
  # --- Harmonic Saturation Tab ---
70
  with gr.Tab("🧬 Harmonic Saturation"):
@@ -74,7 +617,11 @@ with gr.Blocks() as demo:
74
  saturation_out = gr.Audio(label="Warm Output", type="numpy")
75
  saturation_btn = gr.Button("Apply Saturation")
76
 
77
- saturation_btn.click(fn=harmonic_saturation, inputs=[saturation_in, saturation_type, saturation_intensity], outputs=saturation_out)
 
 
 
 
78
 
79
  # --- Vocal Doubler / Harmonizer Tab ---
80
  with gr.Tab("🎧 Vocal Doubler / Harmonizer"):
@@ -83,12 +630,16 @@ with gr.Blocks() as demo:
83
  vocal_status = gr.Textbox(label="Status", interactive=False)
84
  vocal_btn = gr.Button("Add Vocal Doubling / Harmony")
85
 
86
- vocal_btn.click(fn=run_harmony, inputs=vocal_in, outputs=[vocal_out, vocal_status])
 
 
 
 
87
 
88
  # --- Batch Processing Tab ---
89
  with gr.Tab("πŸ”Š Batch Processing"):
90
  batch_files = gr.File(label="Upload Multiple Files", file_count="multiple")
91
- batch_effects = gr.CheckboxGroup(choices=list({e for effects in preset_choices.values() for e in effects}), label="Apply Effects in Order")
92
  batch_isolate = gr.Checkbox(label="Isolate Vocals After Effects")
93
  batch_preset = gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0])
94
  batch_export_format = gr.Dropdown(choices=["MP3", "WAV"], label="Export Format", value="MP3")
@@ -97,7 +648,7 @@ with gr.Blocks() as demo:
97
  batch_status = gr.Textbox(label="Status", interactive=False)
98
 
99
  batch_process_btn.click(
100
- fn=batch_process_audio,
101
  inputs=[batch_files, batch_effects, batch_isolate, batch_preset, batch_export_format],
102
  outputs=[batch_download, batch_status]
103
  )
 
1
+ import gradio as gr
2
+ from pydub import AudioSegment
3
+ import numpy as np
4
+ import tempfile
5
+ import os
6
+ import noisereduce as nr
7
+ import torch
8
+ from demucs import pretrained
9
+ from demucs.apply import apply_model
10
+ import torchaudio
11
+ from pathlib import Path
12
+ import matplotlib.pyplot as plt
13
+ from io import BytesIO
14
+ from PIL import Image
15
+ import zipfile
16
+ import datetime
17
+ import librosa
18
+ import warnings
19
+ from TTS.api import TTS
20
+ import base64
21
+ import pickle
22
+ import json
23
+ import soundfile as sf
24
+
25
+ warnings.filterwarnings("ignore")
26
+
27
+ # ==============================
28
+ # Helper Functions
29
+ # ==============================
30
+
31
+ def audiosegment_to_array(audio):
32
+ return np.array(audio.get_array_of_samples()), audio.frame_rate
33
+
34
+ def array_to_audiosegment(samples, frame_rate, channels=1):
35
+ return AudioSegment(
36
+ samples.tobytes(),
37
+ frame_rate=int(frame_rate),
38
+ sample_width=samples.dtype.itemsize,
39
+ channels=channels
40
+ )
41
+
42
+ def save_audiosegment_to_temp(audio: AudioSegment, suffix=".wav"):
43
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as f:
44
+ audio.export(f.name, format=suffix.lstrip('.'))
45
+ return f.name
46
+
47
+ def load_audiofile_to_numpy(path):
48
+ samples, sr = sf.read(path, dtype="int16")
49
+ if samples.ndim > 1 and samples.shape[1] > 2:
50
+ samples = samples[:, :2]
51
+ return samples, sr
52
+
53
+ def show_waveform(audio_file):
54
+ try:
55
+ audio = AudioSegment.from_file(audio_file)
56
+ samples = np.array(audio.get_array_of_samples())
57
+ plt.figure(figsize=(10, 2))
58
+ plt.plot(samples[:10000], color="skyblue")
59
+ plt.axis('off')
60
+ buf = BytesIO()
61
+ plt.savefig(buf, format="png", bbox_inches="tight", dpi=100)
62
+ plt.close()
63
+ buf.seek(0)
64
+ return Image.open(buf)
65
+ except Exception:
66
+ return None
67
+
68
+ # ==============================
69
+ # Effect Functions (as per original)
70
+ # ==============================
71
+
72
+ def apply_normalize(audio):
73
+ return audio.normalize()
74
+
75
+ def apply_noise_reduction(audio):
76
+ samples, sr = audiosegment_to_array(audio)
77
+ reduced = nr.reduce_noise(y=samples, sr=sr)
78
+ return array_to_audiosegment(reduced, sr, channels=audio.channels)
79
+
80
+ def apply_compression(audio):
81
+ return audio.compress_dynamic_range()
82
+
83
+ def apply_reverb(audio):
84
+ reverb = audio - 10
85
+ return audio.overlay(reverb, position=1000)
86
+
87
+ def apply_pitch_shift(audio, semitones=-2):
88
+ new_frame_rate = int(audio.frame_rate * (2 ** (semitones / 12)))
89
+ shifted = audio._spawn(audio.raw_data, overrides={"frame_rate": new_frame_rate}).set_frame_rate(audio.frame_rate)
90
+ return shifted
91
+
92
+ def apply_echo(audio, delay_ms=500, decay=0.5):
93
+ echo = audio - 10
94
+ return audio.overlay(echo, position=delay_ms)
95
+
96
+ def apply_stereo_widen(audio, pan_amount=0.3):
97
+ left = audio.pan(-pan_amount)
98
+ right = audio.pan(pan_amount)
99
+ return AudioSegment.from_mono_audiosegments(left, right)
100
+
101
+ def apply_bass_boost(audio, gain=10):
102
+ return audio.low_pass_filter(100).apply_gain(gain)
103
+
104
+ def apply_treble_boost(audio, gain=10):
105
+ return audio.high_pass_filter(4000).apply_gain(gain)
106
+
107
+ def apply_limiter(audio, limit_dB=-1):
108
+ limiter = audio._spawn(audio.raw_data, overrides={"frame_rate": audio.frame_rate})
109
+ return limiter.apply_gain(limit_dB)
110
+
111
+ def apply_auto_gain(audio, target_dB=-20):
112
+ change = target_dB - audio.dBFS
113
+ return audio.apply_gain(change)
114
+
115
+ def apply_vocal_distortion(audio, intensity=0.3):
116
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32)
117
+ distorted = samples + intensity * np.sin(samples * 2 * np.pi / 32768)
118
+ return array_to_audiosegment(distorted.astype(np.int16), audio.frame_rate, channels=audio.channels)
119
+
120
+ def apply_harmony(audio, shift_semitones=4):
121
+ shifted_up = apply_pitch_shift(audio, shift_semitones)
122
+ shifted_down = apply_pitch_shift(audio, -shift_semitones)
123
+ return audio.overlay(shifted_up).overlay(shifted_down)
124
+
125
+ def apply_stage_mode(audio):
126
+ processed = apply_reverb(audio)
127
+ processed = apply_bass_boost(processed, gain=6)
128
+ return apply_limiter(processed, limit_dB=-2)
129
+
130
+ def apply_bitcrush(audio, bit_depth=8):
131
+ samples = np.array(audio.get_array_of_samples())
132
+ max_val = 2 ** bit_depth - 1
133
+ downsampled = np.round(samples / (32768 / max_val)).astype(np.int16)
134
+ return array_to_audiosegment(downsampled, audio.frame_rate // 2, channels=audio.channels)
135
+
136
+ # ==============================
137
+ # Loudness Matching (EBU R128)
138
+ # ==============================
139
+
140
+ try:
141
+ import pyloudnorm as pyln
142
+ except ImportError:
143
+ import subprocess
144
+ subprocess.run(["pip", "install", "pyloudnorm"])
145
+ import pyloudnorm as pyln
146
+
147
+ def match_loudness(audio_path, target_lufs=-14.0):
148
+ meter = pyln.Meter(44100)
149
+ wav = AudioSegment.from_file(audio_path).set_frame_rate(44100)
150
+ samples = np.array(wav.get_array_of_samples()).astype(np.float64) / 32768.0
151
+ loudness = meter.integrated_loudness(samples)
152
+ gain_db = target_lufs - loudness
153
+ adjusted = wav + gain_db
154
+ out_path = save_audiosegment_to_temp(adjusted, ".wav")
155
+ return out_path
156
+
157
+ # ==============================
158
+ # EQ Map and Function
159
+ # ==============================
160
+
161
+ eq_map = {
162
+ "Pop": [(200, 500, -3), (2000, 4000, +4)],
163
+ # (Add other genres as needed)
164
+ }
165
+
166
+ def auto_eq(audio, genre="Pop"):
167
+ from scipy.signal import butter, sosfilt
168
+ samples, sr = audiosegment_to_array(audio)
169
+ samples = samples.astype(np.float64)
170
+
171
+ def band_eq(samples, sr, lowcut, highcut, gain):
172
+ sos = butter(10, [lowcut, highcut], btype='band', output='sos', fs=sr)
173
+ filtered = sosfilt(sos, samples)
174
+ return samples + gain * filtered
175
+
176
+ for low, high, gain in eq_map.get(genre, []):
177
+ samples = band_eq(samples, sr, low, high, gain)
178
+ return array_to_audiosegment(samples.astype(np.int16), sr, channels=audio.channels)
179
+
180
+ # ==============================
181
+ # Load & Save Track for Models
182
+ # ==============================
183
+
184
+ def load_track_local(path, sample_rate, channels=2):
185
+ sig, rate = torchaudio.load(path)
186
+ if rate != sample_rate:
187
+ sig = torchaudio.functional.resample(sig, rate, sample_rate)
188
+ if channels == 1:
189
+ sig = sig.mean(0)
190
+ return sig
191
+
192
+ def save_track(path, wav, sample_rate):
193
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
194
+ torchaudio.save(str(path), wav, sample_rate)
195
+
196
+ # ==============================
197
+ # Vocal isolation and stem splitting
198
+ # ==============================
199
+
200
+ def apply_vocal_isolation(audio_path):
201
+ model = pretrained.get_model(name='htdemucs')
202
+ wav = load_track_local(audio_path, model.samplerate, channels=2)
203
+ ref = wav.mean(0)
204
+ wav -= ref[:, None]
205
+ sources = apply_model(model, wav[None])[0]
206
+ wav += ref[:, None]
207
+ vocal_track = sources[3].cpu()
208
+ out_path = os.path.join(tempfile.gettempdir(), "vocals.wav")
209
+ save_track(out_path, vocal_track, model.samplerate)
210
+ return out_path
211
+
212
+ def stem_split(audio_path):
213
+ model = pretrained.get_model(name='htdemucs')
214
+ wav = load_track_local(audio_path, model.samplerate, channels=2)
215
+ sources = apply_model(model, wav[None])[0]
216
+ output_dir = tempfile.mkdtemp()
217
+ file_paths = []
218
+ for i, name in enumerate(['drums', 'bass', 'other', 'vocals']):
219
+ path = os.path.join(output_dir, f"{name}.wav")
220
+ save_track(path, sources[i].cpu(), model.samplerate)
221
+ file_paths.append(path)
222
+ # Return in order vocals, drums, bass, other per your UI
223
+ return file_paths[3], file_paths[0], file_paths[1], file_paths[2]
224
+
225
+ # ==============================
226
+ # Full Audio Processor with Effects Map
227
+ # ==============================
228
+
229
+ def process_audio(audio_file, selected_effects, isolate_vocals, preset_name, export_format):
230
+ try:
231
+ audio = AudioSegment.from_file(audio_file)
232
+ effect_map = {
233
+ "Noise Reduction": apply_noise_reduction,
234
+ "Compress Dynamic Range": apply_compression,
235
+ "Add Reverb": apply_reverb,
236
+ "Pitch Shift": apply_pitch_shift,
237
+ "Echo": apply_echo,
238
+ "Stereo Widening": apply_stereo_widen,
239
+ "Bass Boost": apply_bass_boost,
240
+ "Treble Boost": apply_treble_boost,
241
+ "Normalize": apply_normalize,
242
+ "Limiter": lambda a: apply_limiter(a, limit_dB=-1),
243
+ "Auto Gain": lambda a: apply_auto_gain(a, target_dB=-20),
244
+ "Vocal Distortion": apply_vocal_distortion,
245
+ "Stage Mode": apply_stage_mode,
246
+ "Harmony": apply_harmony,
247
+ "Bitcrusher": apply_bitcrush,
248
+ }
249
+ for eff in selected_effects:
250
+ if eff in effect_map:
251
+ audio = effect_map[eff](audio)
252
+ if isolate_vocals:
253
+ temp_path = save_audiosegment_to_temp(audio, suffix=".wav")
254
+ vocal_path = apply_vocal_isolation(temp_path)
255
+ audio = AudioSegment.from_file(vocal_path)
256
+ output_path = save_audiosegment_to_temp(audio, suffix=f".{export_format.lower()}")
257
+ samples, sr = load_audiofile_to_numpy(output_path)
258
+ waveform = show_waveform(output_path)
259
+ session_log = json.dumps({
260
+ "timestamp": str(datetime.datetime.now()),
261
+ "filename": os.path.basename(audio_file),
262
+ "effects_applied": selected_effects,
263
+ "isolate_vocals": isolate_vocals,
264
+ "export_format": export_format,
265
+ "detected_genre": "Unknown"
266
+ }, indent=2)
267
+ return (samples, sr), waveform, session_log, "Unknown", "πŸŽ‰ Done!"
268
+ except Exception as e:
269
+ return None, None, f"❌ Error: {e}", "", f"❌ Error: {e}"
270
+
271
+ # ==============================
272
+ # Batch Processing
273
+ # ==============================
274
+
275
+ def batch_process_audio(files, selected_effects, isolate_vocals, preset_name, export_format):
276
+ try:
277
+ output_dir = tempfile.mkdtemp()
278
+ paths = []
279
+ logs = []
280
+ for i, file in enumerate(files):
281
+ result, _, log, _, _ = process_audio(file.name, selected_effects, isolate_vocals, preset_name, export_format)
282
+ if result is None:
283
+ continue
284
+ samples, sr = result
285
+ filepath = os.path.join(output_dir, f"processed_{i}.{export_format.lower()}")
286
+ sf.write(filepath, samples, sr)
287
+ paths.append(filepath)
288
+ logs.append(log)
289
+ zip_path = os.path.join(tempfile.gettempdir(), "batch_output.zip")
290
+ with zipfile.ZipFile(zip_path, 'w') as zf:
291
+ for i, p in enumerate(paths):
292
+ zf.write(p, os.path.basename(p))
293
+ zf.writestr(f"session_log_{i}.json", logs[i])
294
+ return zip_path, "πŸ“¦ Batch processing complete!"
295
+ except Exception as e:
296
+ return None, f"❌ Batch processing failed: {e}"
297
+
298
+ # ==============================
299
+ # AI Remastering
300
+ # ==============================
301
+
302
+ def ai_remaster(audio_path):
303
+ try:
304
+ audio = AudioSegment.from_file(audio_path)
305
+ samples, sr = audiosegment_to_array(audio)
306
+ reduced = nr.reduce_noise(y=samples, sr=sr)
307
+ cleaned = array_to_audiosegment(reduced, sr, channels=audio.channels)
308
+ cleaned_path = save_audiosegment_to_temp(cleaned, ".wav")
309
+ isolated_path = apply_vocal_isolation(cleaned_path)
310
+ final_path = ai_mastering_chain(isolated_path, genre="Pop", target_lufs=-14.0)
311
+ samples, sr = load_audiofile_to_numpy(final_path)
312
+ return (samples, sr)
313
+ except Exception as e:
314
+ print(f"Remastering error: {e}")
315
+ return None
316
+
317
+ def ai_mastering_chain(audio_path, genre="Pop", target_lufs=-14.0):
318
+ audio = AudioSegment.from_file(audio_path)
319
+ audio = auto_eq(audio, genre=genre)
320
+ loud_adj_path = match_loudness(audio_path, target_lufs)
321
+ audio = AudioSegment.from_file(loud_adj_path)
322
+ audio = apply_stereo_widen(audio, pan_amount=0.3)
323
+ out_path = save_audiosegment_to_temp(audio, ".wav")
324
+ return out_path
325
+
326
+ def apply_stereo_widen(audio, pan_amount=0.3):
327
+ left = audio.pan(-pan_amount)
328
+ right = audio.pan(pan_amount)
329
+ return AudioSegment.from_mono_audiosegments(left, right)
330
+
331
+ # ==============================
332
+ # Harmonic Saturation
333
+ # ==============================
334
+
335
+ def harmonic_saturation(audio_path, saturation_type="Tube", intensity=0.2):
336
+ audio = AudioSegment.from_file(audio_path)
337
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32)
338
+ if saturation_type == "Tube":
339
+ saturated = np.tanh(intensity * samples)
340
+ elif saturation_type == "Tape":
341
+ saturated = np.where(samples > 0, 1 - np.exp(-intensity * samples), -1 + np.exp(intensity * samples))
342
+ elif saturation_type == "Console":
343
+ saturated = np.clip(samples, -32768, 32768) * intensity
344
+ elif saturation_type == "Mix Bus":
345
+ saturated = np.log1p(np.abs(samples)) * np.sign(samples) * intensity
346
+ else:
347
+ saturated = samples
348
+ saturated_audio = array_to_audiosegment(saturated.astype(np.int16), audio.frame_rate, audio.channels)
349
+ out_path = save_audiosegment_to_temp(saturated_audio, ".wav")
350
+ samples, sr = load_audiofile_to_numpy(out_path)
351
+ return (samples, sr)
352
+
353
+ # ==============================
354
+ # Vocal Doubler Harmony
355
+ # ==============================
356
+
357
+ def run_harmony(audio_file):
358
+ if not audio_file:
359
+ return None, "❌ Upload a vocal clip first."
360
+ try:
361
+ audio = AudioSegment.from_file(audio_file)
362
+ out_audio = apply_harmony(audio)
363
+ tmp_path = save_audiosegment_to_temp(out_audio, ".wav")
364
+ samples, sr = load_audiofile_to_numpy(tmp_path)
365
+ return (samples, sr), "βœ… Success"
366
+ except Exception as e:
367
+ return None, f"❌ Error: {e}"
368
+
369
+ # ==============================
370
+ # Auto-Tune helper
371
+ # ==============================
372
+
373
+ def key_to_semitone(key="C"):
374
+ mapping = {"C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5,
375
+ "F#": 6, "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11}
376
+ return mapping.get(key, 0)
377
+
378
+ def auto_tune_vocal(audio_file, target_key="C"):
379
+ try:
380
+ audio = AudioSegment.from_file(audio_file.name)
381
+ semitones = key_to_semitone(target_key)
382
+ tuned_audio = apply_pitch_shift(audio, semitones)
383
+ tmp_path = save_audiosegment_to_temp(tuned_audio, ".wav")
384
+ samples, sr = load_audiofile_to_numpy(tmp_path)
385
+ return (samples, sr)
386
+ except Exception as e:
387
+ print(f"Auto-Tune Error: {e}")
388
+ return None
389
+
390
+ # ==============================
391
+ # Loop Section Utility
392
+ # ==============================
393
+
394
+ def loop_section(audio_file, start_ms, end_ms, loops=2):
395
+ audio = AudioSegment.from_file(audio_file)
396
+ section = audio[start_ms:end_ms]
397
+ looped = section * loops
398
+ tmp_path = save_audiosegment_to_temp(looped, ".wav")
399
+ samples, sr = load_audiofile_to_numpy(tmp_path)
400
+ return (samples, sr)
401
+
402
+ # ==============================
403
+ # Frequency Spectrum Visualization
404
+ # ==============================
405
+
406
+ def visualize_spectrum(audio_file):
407
+ y, sr = torchaudio.load(audio_file)
408
+ import librosa.display
409
+ y_np = y.numpy().flatten()
410
+ stft = librosa.stft(y_np)
411
+ db = librosa.amplitude_to_db(abs(stft))
412
+ plt.figure(figsize=(10, 4))
413
+ img = librosa.display.specshow(db, sr=sr, x_axis="time", y_axis="hz", cmap="magma")
414
+ plt.colorbar(img, format="%+2.0f dB")
415
+ plt.title("Frequency Spectrum")
416
+ plt.tight_layout()
417
+ buf = BytesIO()
418
+ plt.savefig(buf, format="png")
419
+ plt.close()
420
+ buf.seek(0)
421
+ return Image.open(buf)
422
+
423
+ # ==============================
424
+ # A/B Compare Function
425
+ # ==============================
426
+
427
+ def compare_ab(track1_path, track2_path):
428
+ return track1_path, track2_path
429
+
430
+ # ==============================
431
+ # DAW Template Generation
432
+ # ==============================
433
+
434
+ def generate_ableton_template(stem_files):
435
+ template = {
436
+ "format": "Ableton Live",
437
+ "stems": [os.path.basename(s.name) for s in stem_files],
438
+ "effects": ["Reverb", "EQ", "Compression"],
439
+ "tempo": 128,
440
+ "title": "Studio Pulse Project"
441
+ }
442
+ out_path = os.path.join(tempfile.gettempdir(), "ableton_template.json")
443
+ with open(out_path, "w") as f:
444
+ json.dump(template, f, indent=2)
445
+ return out_path
446
+
447
+ # ==============================
448
+ # Export Full Mix as ZIP
449
+ # ==============================
450
+
451
+ def export_full_mix(stem_files, final_mix_file):
452
+ zip_path = os.path.join(tempfile.gettempdir(), "full_export.zip")
453
+ with zipfile.ZipFile(zip_path, "w") as zipf:
454
+ for i, stem in enumerate(stem_files):
455
+ zipf.write(stem.name, f"stem_{i}.wav")
456
+ zipf.write(final_mix_file.name, "final_mix.wav")
457
+ return zip_path
458
+
459
+ # ==============================
460
+ # Save/Load Project Functions
461
+ # ==============================
462
+
463
+ def save_project(audio_file, preset, effects):
464
+ audio = AudioSegment.from_file(audio_file.name)
465
+ project_data = {
466
+ "audio": audio.raw_data,
467
+ "preset": preset,
468
+ "effects": effects
469
+ }
470
+ out_path = os.path.join(tempfile.gettempdir(), "project.aiproj")
471
+ with open(out_path, "wb") as f:
472
+ pickle.dump(project_data, f)
473
+ return out_path
474
+
475
+ def load_project(project_file):
476
+ with open(project_file.name, "rb") as f:
477
+ data = pickle.load(f)
478
+ return data.get("preset", ""), data.get("effects", [])
479
+
480
+ # ==============================
481
+ # Prompt-based editing
482
+ # ==============================
483
+
484
+ def process_prompt(audio_file, prompt):
485
+ audio = AudioSegment.from_file(audio_file)
486
+ processed_audio = apply_noise_reduction(audio) # Example; real model integration can be added here
487
+ tmp_path = save_audiosegment_to_temp(processed_audio, ".wav")
488
+ samples, sr = load_audiofile_to_numpy(tmp_path)
489
+ return (samples, sr)
490
+
491
+ # ==============================
492
+ # Voice Swap
493
+ # ==============================
494
+
495
+ def clone_voice(source_audio_file, reference_audio_file):
496
+ source = AudioSegment.from_file(source_audio_file.name)
497
+ ref = AudioSegment.from_file(reference_audio_file.name)
498
+ mixed = source.overlay(ref - 10)
499
+ tmp_path = save_audiosegment_to_temp(mixed, ".wav")
500
+ return tmp_path
501
+
502
+ # ==============================
503
+ # Presets dictionary (use your full original content)
504
+ # ==============================
505
+
506
+ preset_choices = {
507
+ "Default": [],
508
+ "Clean Podcast": ["Noise Reduction", "Normalize"],
509
+ "Podcast Mastered": ["Noise Reduction", "Normalize", "Compress Dynamic Range"],
510
+ "Radio Ready": ["Bass Boost", "Treble Boost", "Limiter"],
511
+ "Music Production": ["Reverb", "Stereo Widening", "Pitch Shift"],
512
+ "ASMR Creator": ["Noise Gate", "Auto Gain", "Low-Pass Filter"],
513
+ "Voiceover Pro": ["Vocal Isolation", "TTS", "EQ Match"],
514
+ "8-bit Retro": ["Bitcrusher", "Echo", "Mono Downmix"],
515
+ "πŸŽ™ Clean Vocal": ["Noise Reduction", "Normalize", "High Pass Filter (80Hz)"],
516
+ "πŸ§ͺ Vocal Distortion": ["Vocal Distortion", "Reverb", "Compress Dynamic Range"],
517
+ "🎢 Singer's Harmony": ["Harmony", "Stereo Widening", "Pitch Shift"],
518
+ "🌫 ASMR Vocal": ["Auto Gain", "Low-Pass Filter (3000Hz)", "Noise Gate"],
519
+ "🎼 Stage Mode": ["Reverb", "Bass Boost", "Limiter"],
520
+ "🎡 Auto-Tune Style": ["Pitch Shift (+1 semitone)", "Normalize", "Treble Boost"],
521
+ "🎀 R&B Vocal": ["Noise Reduction", "Bass Boost (100-300Hz)", "Treble Boost (2000-4000Hz)"],
522
+ "πŸ’ƒ Soul Vocal": ["Noise Reduction", "Bass Boost (80-200Hz)", "Treble Boost (1500-3500Hz)"],
523
+ "πŸ•Ί Funk Groove": ["Bass Boost (80-200Hz)", "Treble Boost (1000-3000Hz)"],
524
+ "Studio Master": ["Noise Reduction", "Normalize", "Bass Boost", "Treble Boost", "Limiter"],
525
+ "Podcast Voice": ["Noise Reduction", "Auto Gain", "High Pass Filter (85Hz)"],
526
+ "Lo-Fi Chill": ["Noise Gate", "Low-Pass Filter (3000Hz)", "Mono Downmix", "Bitcrusher"],
527
+ # Add other presets from your original exactly
528
+ }
529
+
530
+ preset_names = list(preset_choices.keys())
531
+
532
+ # ==============================
533
+ # Gradio UI Construction with Explicit Components + Clicks
534
+ # ==============================
535
+
536
  with gr.Blocks() as demo:
537
  gr.HTML('<h3 style="text-align:center;">Where Your Audio Meets Intelligence</h3>')
538
  gr.Markdown('### Upload, edit, export β€” powered by AI!')
539
 
540
+ # === Single File Studio Tab ===
541
+ with gr.Blocks() as demo:
542
+ gr.HTML('<h3 style="text-align:center;">Where Your Audio Meets Intelligence</h3>')
543
+ gr.Markdown('### Upload, edit, export β€” powered by AI!')
544
+
545
  # --- Single File Studio Tab ---
546
  with gr.Tab("🎡 Single File Studio"):
547
  with gr.Row():
548
  with gr.Column():
549
  input_audio = gr.Audio(label="Upload Audio", type="filepath")
550
+
551
+ effect_choices = list({eff for effects in preset_choices.values() for eff in effects})
552
+ effect_checkbox = gr.CheckboxGroup(choices=effect_choices, label="Apply Effects in Order")
553
+
554
  preset_dropdown = gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0])
555
  export_format = gr.Dropdown(choices=["WAV", "MP3"], label="Export Format", value="WAV")
556
  isolate_vocals = gr.Checkbox(label="Isolate Vocals After Effects")
 
563
  genre_out = gr.Textbox(label="Detected Genre")
564
  status_box = gr.Textbox(label="Status", lines=1, value="Ready")
565
 
566
+ preset_dropdown.change(
567
+ lambda x: preset_choices.get(x, []),
568
+ inputs=preset_dropdown,
569
+ outputs=effect_checkbox
570
+ )
571
 
572
+ def process_wrapper(audio_file, effects, isolate, preset, fmt):
573
  effs = preset_choices.get(preset, []) if preset in preset_choices else effects
574
+ return process_audio(audio_file, effs, isolate, preset, fmt)
575
 
576
  process_btn.click(
577
+ process_wrapper,
578
  inputs=[input_audio, effect_checkbox, isolate_vocals, preset_dropdown, export_format],
579
  outputs=[output_audio, waveform_img, session_log_out, genre_out, status_box]
580
  )
 
592
  other_file = gr.File(label="Other")
593
 
594
  split_button.click(
595
+ stem_split,
596
  inputs=remix_input,
597
  outputs=[vocals_file, drums_file, bass_file, other_file]
598
  )
 
604
  remaster_status = gr.Textbox(label="Status", value="Ready", interactive=False)
605
  remaster_btn = gr.Button("Remaster")
606
 
607
+ remaster_btn.click(remaster_audio := ai_remaster,
608
+ inputs=remaster_input,
609
+ outputs=remaster_output)
610
+ remaster_btn.click(lambda _: "βœ… Done!", remaster_btn, remaster_status)
611
 
612
  # --- Harmonic Saturation Tab ---
613
  with gr.Tab("🧬 Harmonic Saturation"):
 
617
  saturation_out = gr.Audio(label="Warm Output", type="numpy")
618
  saturation_btn = gr.Button("Apply Saturation")
619
 
620
+ saturation_btn.click(
621
+ harmonic_saturation,
622
+ inputs=[saturation_in, saturation_type, saturation_intensity],
623
+ outputs=saturation_out
624
+ )
625
 
626
  # --- Vocal Doubler / Harmonizer Tab ---
627
  with gr.Tab("🎧 Vocal Doubler / Harmonizer"):
 
630
  vocal_status = gr.Textbox(label="Status", interactive=False)
631
  vocal_btn = gr.Button("Add Vocal Doubling / Harmony")
632
 
633
+ vocal_btn.click(
634
+ run_harmony,
635
+ inputs=vocal_in,
636
+ outputs=[vocal_out, vocal_status]
637
+ )
638
 
639
  # --- Batch Processing Tab ---
640
  with gr.Tab("πŸ”Š Batch Processing"):
641
  batch_files = gr.File(label="Upload Multiple Files", file_count="multiple")
642
+ batch_effects = gr.CheckboxGroup(choices=effect_choices, label="Apply Effects in Order")
643
  batch_isolate = gr.Checkbox(label="Isolate Vocals After Effects")
644
  batch_preset = gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0])
645
  batch_export_format = gr.Dropdown(choices=["MP3", "WAV"], label="Export Format", value="MP3")
 
648
  batch_status = gr.Textbox(label="Status", interactive=False)
649
 
650
  batch_process_btn.click(
651
+ batch_process_audio,
652
  inputs=[batch_files, batch_effects, batch_isolate, batch_preset, batch_export_format],
653
  outputs=[batch_download, batch_status]
654
  )