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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -649
app.py CHANGED
@@ -1,585 +1,41 @@
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")
557
  process_btn = gr.Button("Process Audio")
558
-
559
  with gr.Column():
560
  output_audio = gr.Audio(label="Processed Audio", type="numpy")
561
  waveform_img = gr.Image(label="Waveform Preview")
562
  session_log_out = gr.Textbox(label="Session Log", lines=6)
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
- )
581
 
582
- # --- Remix Mode Tab ---
583
  with gr.Tab("🎛 Remix Mode"):
584
  with gr.Row():
585
  with gr.Column():
@@ -590,138 +46,116 @@ with gr.Blocks() as demo:
590
  drums_file = gr.File(label="Drums")
591
  bass_file = gr.File(label="Bass")
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
- )
599
-
600
- # --- AI Remastering Tab ---
601
  with gr.Tab("🔮 AI Remastering"):
602
  remaster_input = gr.Audio(label="Upload Low-Quality Recording", type="filepath")
603
  remaster_output = gr.Audio(label="Studio-Grade Output", type="numpy")
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"):
614
  saturation_in = gr.Audio(label="Upload Track", type="filepath")
615
  saturation_type = gr.Dropdown(choices=["Tube", "Tape", "Console", "Mix Bus"], label="Saturation Type", value="Tube")
616
  saturation_intensity = gr.Slider(minimum=0.1, maximum=1.0, value=0.2, label="Intensity")
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"):
628
  vocal_in = gr.Audio(label="Upload Vocal Clip", type="filepath")
629
  vocal_out = gr.Audio(label="Doubled Output", type="numpy")
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")
646
- batch_process_btn = gr.Button("Process All Files")
647
- batch_download = gr.File(label="Download ZIP of All Processed Files")
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
- )
655
-
656
- # --- AI Auto-Tune Tab ---
657
  with gr.Tab("🎤 AI Auto-Tune"):
658
  autotune_file = gr.File(label="Source Voice Clip")
659
  autotune_key = gr.Textbox(label="Target Key", value="C", lines=1)
660
  autotune_out = gr.Audio(label="Pitch-Corrected Output", type="numpy")
661
  autotune_btn = gr.Button("Apply Auto-Tune")
662
- autotune_btn.click(fn=auto_tune_vocal, inputs=[autotune_file, autotune_key], outputs=autotune_out)
663
 
664
- # --- Frequency Spectrum Tab ---
665
  with gr.Tab("📊 Frequency Spectrum"):
666
  spectrum_in = gr.Audio(label="Upload Track", type="filepath")
667
  spectrum_out = gr.Image(label="Frequency Spectrum")
668
  spectrum_btn = gr.Button("Visualize Spectrum")
 
669
 
670
- spectrum_btn.click(fn=visualize_spectrum, inputs=spectrum_in, outputs=spectrum_out)
671
-
672
- # --- Loudness Graph Tab ---
673
  with gr.Tab("📈 Loudness Graph"):
674
  loudness_in = gr.Audio(label="Upload Track", type="filepath")
675
- loudness_target = gr.Slider(minimum=-24, maximum=-6, value=-14, step=0.5, label="Target LUFS")
676
  loudness_out = gr.Audio(label="Normalized Output", type="numpy")
677
  loudness_btn = gr.Button("Match Loudness")
 
678
 
679
- loudness_btn.click(fn=match_loudness, inputs=[loudness_in, loudness_target], outputs=loudness_out)
680
-
681
- # --- Save/Load Project Tab ---
682
  with gr.Tab("📁 Save/Load Project"):
683
  with gr.Row():
684
  with gr.Column():
685
- proj_audio = gr.File(label="Original Audio")
686
- proj_preset = gr.Dropdown(choices=preset_names, label="Used Preset", value=preset_names[0])
687
- proj_effects = gr.CheckboxGroup(choices=list({e for effects in preset_choices.values() for e in effects}), label="Applied Effects")
688
  save_proj_btn = gr.Button("Save Project")
689
- saved_proj_file = gr.File(label="Project File (.aiproj)")
690
  with gr.Column():
691
  load_proj_file = gr.File(label="Load .aiproj File")
692
  loaded_preset = gr.Dropdown(choices=preset_names, label="Loaded Preset")
693
- loaded_effects = gr.CheckboxGroup(choices=list({e for effects in preset_choices.values() for e in effects}), label="Loaded Effects")
694
  load_proj_btn = gr.Button("Load Project")
 
 
695
 
696
- save_proj_btn.click(fn=save_project, inputs=[proj_audio, proj_preset, proj_effects], outputs=saved_proj_file)
697
- load_proj_btn.click(fn=load_project, inputs=load_proj_file, outputs=[loaded_preset, loaded_effects])
698
-
699
- # --- Prompt-Based Editing Tab ---
700
  with gr.Tab("🧠 Prompt-Based Editing"):
701
  prompt_audio = gr.File(label="Upload Audio", file_types=[".wav", ".mp3"])
702
  prompt_text = gr.Textbox(label="Describe What You Want", lines=5)
703
  prompt_out = gr.Audio(label="Edited Output", type="numpy")
704
  prompt_btn = gr.Button("Process Prompt")
705
- prompt_btn.click(fn=process_prompt, inputs=[prompt_audio, prompt_text], outputs=prompt_out)
706
 
707
- # --- Custom EQ Editor Tab ---
708
  with gr.Tab("🎛 Custom EQ Editor"):
709
  eq_audio = gr.Audio(label="Upload Track", type="filepath")
710
  eq_genre = gr.Dropdown(choices=list(eq_map.keys()), value="Pop", label="Genre")
711
  eq_output = gr.Audio(label="EQ-Enhanced Output", type="numpy")
712
  eq_btn = gr.Button("Apply EQ")
713
- eq_btn.click(fn=auto_eq, inputs=[eq_audio, eq_genre], outputs=eq_output)
714
 
715
- # --- A/B Compare Tab ---
716
  with gr.Tab("🎯 A/B Compare"):
717
  ab_track1 = gr.Audio(label="Version A", type="filepath")
718
  ab_track2 = gr.Audio(label="Version B", type="filepath")
719
- ab_output1 = gr.Audio(label="Version A", type="filepath")
720
- ab_output2 = gr.Audio(label="Version B", type="filepath")
721
  ab_btn = gr.Button("Compare")
722
- ab_btn.click(fn=compare_ab, inputs=[ab_track1, ab_track2], outputs=[ab_output1, ab_output2])
723
 
724
- # --- Loop Playback Tab ---
725
  with gr.Tab("🔁 Loop Playback"):
726
  loop_audio = gr.Audio(label="Upload Track", type="filepath")
727
  loop_start = gr.Slider(minimum=0, maximum=30000, step=100, value=5000, label="Start MS")
@@ -729,28 +163,23 @@ with gr.Blocks() as demo:
729
  loop_repeat = gr.Slider(minimum=1, maximum=10, value=2, label="Repeat Loops")
730
  loop_out = gr.Audio(label="Looped Output", type="numpy")
731
  loop_btn = gr.Button("Loop Section")
732
- loop_btn.click(fn=loop_section, inputs=[loop_audio, loop_start, loop_end, loop_repeat], outputs=loop_out)
733
 
734
- # --- Share Effect Chain Tab ---
735
  with gr.Tab("🔗 Share Effect Chain"):
736
- share_effects = gr.CheckboxGroup(choices=list({e for effects in preset_choices.values() for e in effects}), label="Select Effects")
737
  share_code = gr.Textbox(label="Share Code", lines=2)
738
  share_btn = gr.Button("Generate Share Code")
739
- share_btn.click(fn=lambda x: json.dumps(sorted(x)), inputs=share_effects, outputs=share_code)
740
 
741
- # --- Load Shared Chain Tab ---
742
  with gr.Tab("📥 Load Shared Chain"):
743
  load_code = gr.Textbox(label="Paste Shared Code", lines=2)
744
- loaded_effects = gr.CheckboxGroup(choices=list({e for effects in preset_choices.values() for e in effects}), label="Loaded Effects")
745
  load_code_btn = gr.Button("Load Effects")
746
- def load_shared_code(code_str):
747
- try:
748
- return json.loads(code_str)
749
- except:
750
- return []
751
- load_code_btn.click(fn=load_shared_code, inputs=load_code, outputs=loaded_effects)
752
 
753
- # --- Keyboard Shortcuts Tab ---
754
  with gr.Tab("⌨ Keyboard Shortcuts"):
755
  gr.Markdown("""
756
  ### Keyboard Controls
@@ -763,37 +192,48 @@ with gr.Blocks() as demo:
763
  - `Ctrl + V`: Paste effect chain
764
  """)
765
 
766
- # --- Vocal Formant Correction Tab ---
767
  with gr.Tab("🧑‍🎤 Vocal Formant Correction"):
768
  formant_audio = gr.Audio(label="Upload Vocal Track", type="filepath")
769
  formant_shift = gr.Slider(minimum=-2, maximum=2, value=1.0, step=0.1, label="Formant Shift")
770
  formant_output = gr.Audio(label="Natural-Sounding Vocal", type="numpy")
771
  formant_btn = gr.Button("Apply Correction")
772
- formant_btn.click(fn=lambda audio, shift: array_to_audiosegment(
773
- librosa.effects.pitch_shift(np.array(AudioSegment.from_file(audio).get_array_of_samples()), sr=AudioSegment.from_file(audio).frame_rate, n_steps=shift).astype(np.int16),
774
- AudioSegment.from_file(audio).frame_rate,
775
- channels=AudioSegment.from_file(audio).channels
776
- ), inputs=[formant_audio, formant_shift], outputs=formant_output)
 
 
 
 
 
 
 
 
777
 
778
- # --- Voice Swap / Cloning Tab ---
779
  with gr.Tab("🔁 Voice Swap / Cloning"):
780
  source_voice = gr.File(label="Source Voice Clip")
781
  reference_voice = gr.File(label="Reference Voice")
782
  clone_output = gr.Audio(label="Converted Output", type="numpy")
783
  clone_btn = gr.Button("Clone Voice")
 
784
  def clone_func(source, ref):
785
  s = AudioSegment.from_file(source.name)
786
  r = AudioSegment.from_file(ref.name)
787
  mixed = s.overlay(r - 10)
788
  tmp = save_audiosegment_to_temp(mixed, ".wav")
789
  return load_audiofile_to_numpy(tmp)
 
790
  clone_btn.click(clone_func, inputs=[source_voice, reference_voice], outputs=clone_output)
791
 
792
- # --- DAW Template Export Tab ---
793
  with gr.Tab("🎛 DAW Template Export"):
794
  daw_stems = gr.File(label="Upload Stems", file_count="multiple")
795
  daw_output = gr.File(label="DAW Template (.json/.als/.flp)")
796
  daw_btn = gr.Button("Generate Template")
 
797
  def generate_template(stems):
798
  template = {
799
  "format": "Ableton Live",
@@ -806,14 +246,16 @@ with gr.Blocks() as demo:
806
  with open(out_path, "w") as f:
807
  json.dump(template, f, indent=2)
808
  return out_path
 
809
  daw_btn.click(generate_template, inputs=daw_stems, outputs=daw_output)
810
 
811
- # --- Export Full Mix ZIP Tab ---
812
  with gr.Tab("📁 Export Full Mix ZIP"):
813
  stems_files = gr.File(label="Stems", file_count="multiple")
814
  final_mix_file = gr.File(label="Final Mix")
815
  full_zip_output = gr.File(label="Full Mix Archive (.zip)")
816
  export_zip_btn = gr.Button("Export ZIP")
 
817
  def export_zip(stems, final_mix):
818
  zip_path = os.path.join(tempfile.gettempdir(), "full_export.zip")
819
  with zipfile.ZipFile(zip_path, "w") as zipf:
@@ -821,6 +263,7 @@ with gr.Blocks() as demo:
821
  zipf.write(stem.name, f"stem_{i}.wav")
822
  zipf.write(final_mix.name, "final_mix.wav")
823
  return zip_path
 
824
  export_zip_btn.click(export_zip, inputs=[stems_files, final_mix_file], outputs=full_zip_output)
825
 
826
  demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # 1. Single File Studio
 
 
 
 
 
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")
17
  process_btn = gr.Button("Process Audio")
 
18
  with gr.Column():
19
  output_audio = gr.Audio(label="Processed Audio", type="numpy")
20
  waveform_img = gr.Image(label="Waveform Preview")
21
  session_log_out = gr.Textbox(label="Session Log", lines=6)
22
  genre_out = gr.Textbox(label="Detected Genre")
23
  status_box = gr.Textbox(label="Status", lines=1, value="Ready")
24
+
25
+ def update_effects_for_preset(preset_name):
26
+ return preset_choices.get(preset_name, [])
27
+
28
+ preset_dropdown.change(update_effects_for_preset, inputs=preset_dropdown, outputs=effect_checkbox)
29
+
30
+ def process_wrapper(audio_path, effects, isolate, preset, fmt):
 
31
  effs = preset_choices.get(preset, []) if preset in preset_choices else effects
32
+ return process_audio(audio_path, effs, isolate, preset, fmt)
33
+
34
+ process_btn.click(process_wrapper,
35
+ inputs=[input_audio, effect_checkbox, isolate_vocals, preset_dropdown, export_format],
36
+ outputs=[output_audio, waveform_img, session_log_out, genre_out, status_box])
 
 
37
 
38
+ # 2. Remix Mode
39
  with gr.Tab("🎛 Remix Mode"):
40
  with gr.Row():
41
  with gr.Column():
 
46
  drums_file = gr.File(label="Drums")
47
  bass_file = gr.File(label="Bass")
48
  other_file = gr.File(label="Other")
49
+ split_button.click(stem_split, inputs=remix_input, outputs=[vocals_file, drums_file, bass_file, other_file])
50
 
51
+ # 3. AI Remastering
 
 
 
 
 
 
52
  with gr.Tab("🔮 AI Remastering"):
53
  remaster_input = gr.Audio(label="Upload Low-Quality Recording", type="filepath")
54
  remaster_output = gr.Audio(label="Studio-Grade Output", type="numpy")
55
  remaster_status = gr.Textbox(label="Status", value="Ready", interactive=False)
56
  remaster_btn = gr.Button("Remaster")
57
+ remaster_btn.click(ai_remaster, inputs=remaster_input, outputs=remaster_output)
58
+ remaster_btn.click(lambda _: "Done!", remaster_btn, remaster_status)
59
 
60
+ # 4. Harmonic Saturation
 
 
 
 
 
61
  with gr.Tab("🧬 Harmonic Saturation"):
62
  saturation_in = gr.Audio(label="Upload Track", type="filepath")
63
  saturation_type = gr.Dropdown(choices=["Tube", "Tape", "Console", "Mix Bus"], label="Saturation Type", value="Tube")
64
  saturation_intensity = gr.Slider(minimum=0.1, maximum=1.0, value=0.2, label="Intensity")
65
  saturation_out = gr.Audio(label="Warm Output", type="numpy")
66
+ sat_btn = gr.Button("Apply Saturation")
67
+ sat_btn.click(harmonic_saturation,
68
+ inputs=[saturation_in, saturation_type, saturation_intensity],
69
+ outputs=saturation_out)
70
 
71
+ # 5. Vocal Doubler / Harmonizer
 
 
 
 
 
 
72
  with gr.Tab("🎧 Vocal Doubler / Harmonizer"):
73
  vocal_in = gr.Audio(label="Upload Vocal Clip", type="filepath")
74
  vocal_out = gr.Audio(label="Doubled Output", type="numpy")
75
  vocal_status = gr.Textbox(label="Status", interactive=False)
76
  vocal_btn = gr.Button("Add Vocal Doubling / Harmony")
77
+ vocal_btn.click(run_harmony, inputs=vocal_in, outputs=[vocal_out, vocal_status])
78
 
79
+ # 6. Batch Processing
 
 
 
 
 
 
80
  with gr.Tab("🔊 Batch Processing"):
81
  batch_files = gr.File(label="Upload Multiple Files", file_count="multiple")
82
+ batch_effects = gr.CheckboxGroup(choices=list({e for effs in preset_choices.values() for e in effs}), label="Apply Effects in Order")
83
  batch_isolate = gr.Checkbox(label="Isolate Vocals After Effects")
84
  batch_preset = gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0])
85
+ batch_export = gr.Dropdown(choices=["MP3", "WAV"], label="Export Format", value="MP3")
86
+ batch_btn = gr.Button("Process All Files")
87
+ batch_zip = gr.File(label="Download All Processed Files (ZIP)")
88
  batch_status = gr.Textbox(label="Status", interactive=False)
89
+ batch_btn.click(batch_process_audio,
90
+ inputs=[batch_files, batch_effects, batch_isolate, batch_preset, batch_export],
91
+ outputs=[batch_zip, batch_status])
92
 
93
+ # 7. AI Auto-Tune
 
 
 
 
 
 
94
  with gr.Tab("🎤 AI Auto-Tune"):
95
  autotune_file = gr.File(label="Source Voice Clip")
96
  autotune_key = gr.Textbox(label="Target Key", value="C", lines=1)
97
  autotune_out = gr.Audio(label="Pitch-Corrected Output", type="numpy")
98
  autotune_btn = gr.Button("Apply Auto-Tune")
99
+ autotune_btn.click(auto_tune_vocal, inputs=[autotune_file, autotune_key], outputs=autotune_out)
100
 
101
+ # 8. Frequency Spectrum
102
  with gr.Tab("📊 Frequency Spectrum"):
103
  spectrum_in = gr.Audio(label="Upload Track", type="filepath")
104
  spectrum_out = gr.Image(label="Frequency Spectrum")
105
  spectrum_btn = gr.Button("Visualize Spectrum")
106
+ spectrum_btn.click(visualize_spectrum, inputs=spectrum_in, outputs=spectrum_out)
107
 
108
+ # 9. Loudness Graph
 
 
109
  with gr.Tab("📈 Loudness Graph"):
110
  loudness_in = gr.Audio(label="Upload Track", type="filepath")
111
+ loudness_target = gr.Slider(minimum=-24, maximum=-6, value=-14, label="Target LUFS")
112
  loudness_out = gr.Audio(label="Normalized Output", type="numpy")
113
  loudness_btn = gr.Button("Match Loudness")
114
+ loudness_btn.click(match_loudness, inputs=[loudness_in, loudness_target], outputs=loudness_out)
115
 
116
+ # 10. Save/Load Project
 
 
117
  with gr.Tab("📁 Save/Load Project"):
118
  with gr.Row():
119
  with gr.Column():
120
+ project_audio = gr.File(label="Original Audio")
121
+ project_preset = gr.Dropdown(choices=preset_names, label="Used Preset", value=preset_names[0])
122
+ project_effects = gr.CheckboxGroup(choices=list({e for effs in preset_choices.values() for e in effs}), label="Applied Effects")
123
  save_proj_btn = gr.Button("Save Project")
124
+ project_file = gr.File(label="Project File (.aiproj)")
125
  with gr.Column():
126
  load_proj_file = gr.File(label="Load .aiproj File")
127
  loaded_preset = gr.Dropdown(choices=preset_names, label="Loaded Preset")
128
+ loaded_effects = gr.CheckboxGroup(choices=list({e for effs in preset_choices.values() for e in effs}), label="Loaded Effects")
129
  load_proj_btn = gr.Button("Load Project")
130
+ save_proj_btn.click(save_project, inputs=[project_audio, project_preset, project_effects], outputs=project_file)
131
+ load_proj_btn.click(load_project, inputs=load_proj_file, outputs=[loaded_preset, loaded_effects])
132
 
133
+ # 11. Prompt-Based Editing
 
 
 
134
  with gr.Tab("🧠 Prompt-Based Editing"):
135
  prompt_audio = gr.File(label="Upload Audio", file_types=[".wav", ".mp3"])
136
  prompt_text = gr.Textbox(label="Describe What You Want", lines=5)
137
  prompt_out = gr.Audio(label="Edited Output", type="numpy")
138
  prompt_btn = gr.Button("Process Prompt")
139
+ prompt_btn.click(process_prompt, inputs=[prompt_audio, prompt_text], outputs=prompt_out)
140
 
141
+ # 12. Custom EQ Editor
142
  with gr.Tab("🎛 Custom EQ Editor"):
143
  eq_audio = gr.Audio(label="Upload Track", type="filepath")
144
  eq_genre = gr.Dropdown(choices=list(eq_map.keys()), value="Pop", label="Genre")
145
  eq_output = gr.Audio(label="EQ-Enhanced Output", type="numpy")
146
  eq_btn = gr.Button("Apply EQ")
147
+ eq_btn.click(auto_eq, inputs=[eq_audio, eq_genre], outputs=eq_output)
148
 
149
+ # 13. A/B Compare
150
  with gr.Tab("🎯 A/B Compare"):
151
  ab_track1 = gr.Audio(label="Version A", type="filepath")
152
  ab_track2 = gr.Audio(label="Version B", type="filepath")
153
+ ab_out1 = gr.Audio(label="Version A", type="filepath")
154
+ ab_out2 = gr.Audio(label="Version B", type="filepath")
155
  ab_btn = gr.Button("Compare")
156
+ ab_btn.click(compare_ab, inputs=[ab_track1, ab_track2], outputs=[ab_out1, ab_out2])
157
 
158
+ # 14. Loop Playback
159
  with gr.Tab("🔁 Loop Playback"):
160
  loop_audio = gr.Audio(label="Upload Track", type="filepath")
161
  loop_start = gr.Slider(minimum=0, maximum=30000, step=100, value=5000, label="Start MS")
 
163
  loop_repeat = gr.Slider(minimum=1, maximum=10, value=2, label="Repeat Loops")
164
  loop_out = gr.Audio(label="Looped Output", type="numpy")
165
  loop_btn = gr.Button("Loop Section")
166
+ loop_btn.click(loop_section, inputs=[loop_audio, loop_start, loop_end, loop_repeat], outputs=loop_out)
167
 
168
+ # 15. Share Effect Chain Tab
169
  with gr.Tab("🔗 Share Effect Chain"):
170
+ share_effects = gr.CheckboxGroup(choices=list({e for effs in preset_choices.values() for e in effs}), label="Select Effects")
171
  share_code = gr.Textbox(label="Share Code", lines=2)
172
  share_btn = gr.Button("Generate Share Code")
173
+ share_btn.click(lambda x: json.dumps(sorted(x)), inputs=share_effects, outputs=share_code)
174
 
175
+ # 16. Load Shared Chain Tab
176
  with gr.Tab("📥 Load Shared Chain"):
177
  load_code = gr.Textbox(label="Paste Shared Code", lines=2)
178
+ loaded_effects = gr.CheckboxGroup(choices=list({e for effs in preset_choices.values() for e in effs}), label="Loaded Effects")
179
  load_code_btn = gr.Button("Load Effects")
180
+ load_code_btn.click(lambda code: json.loads(code) if code else [], inputs=load_code, outputs=loaded_effects)
 
 
 
 
 
181
 
182
+ # 17. Keyboard Shortcuts Tab
183
  with gr.Tab("⌨ Keyboard Shortcuts"):
184
  gr.Markdown("""
185
  ### Keyboard Controls
 
192
  - `Ctrl + V`: Paste effect chain
193
  """)
194
 
195
+ # 18. Vocal Formant Correction Tab
196
  with gr.Tab("🧑‍🎤 Vocal Formant Correction"):
197
  formant_audio = gr.Audio(label="Upload Vocal Track", type="filepath")
198
  formant_shift = gr.Slider(minimum=-2, maximum=2, value=1.0, step=0.1, label="Formant Shift")
199
  formant_output = gr.Audio(label="Natural-Sounding Vocal", type="numpy")
200
  formant_btn = gr.Button("Apply Correction")
201
+ formant_btn.click(
202
+ lambda audio, shift: array_to_audiosegment(
203
+ librosa.effects.pitch_shift(
204
+ np.array(AudioSegment.from_file(audio).get_array_of_samples()),
205
+ sr=AudioSegment.from_file(audio).frame_rate,
206
+ n_steps=shift
207
+ ).astype(np.int16),
208
+ AudioSegment.from_file(audio).frame_rate,
209
+ channels=AudioSegment.from_file(audio).channels
210
+ ),
211
+ inputs=[formant_audio, formant_shift],
212
+ outputs=formant_output
213
+ )
214
 
215
+ # 19. Voice Swap / Cloning Tab
216
  with gr.Tab("🔁 Voice Swap / Cloning"):
217
  source_voice = gr.File(label="Source Voice Clip")
218
  reference_voice = gr.File(label="Reference Voice")
219
  clone_output = gr.Audio(label="Converted Output", type="numpy")
220
  clone_btn = gr.Button("Clone Voice")
221
+
222
  def clone_func(source, ref):
223
  s = AudioSegment.from_file(source.name)
224
  r = AudioSegment.from_file(ref.name)
225
  mixed = s.overlay(r - 10)
226
  tmp = save_audiosegment_to_temp(mixed, ".wav")
227
  return load_audiofile_to_numpy(tmp)
228
+
229
  clone_btn.click(clone_func, inputs=[source_voice, reference_voice], outputs=clone_output)
230
 
231
+ # 20. DAW Template Export Tab
232
  with gr.Tab("🎛 DAW Template Export"):
233
  daw_stems = gr.File(label="Upload Stems", file_count="multiple")
234
  daw_output = gr.File(label="DAW Template (.json/.als/.flp)")
235
  daw_btn = gr.Button("Generate Template")
236
+
237
  def generate_template(stems):
238
  template = {
239
  "format": "Ableton Live",
 
246
  with open(out_path, "w") as f:
247
  json.dump(template, f, indent=2)
248
  return out_path
249
+
250
  daw_btn.click(generate_template, inputs=daw_stems, outputs=daw_output)
251
 
252
+ # 21. Export Full Mix ZIP Tab
253
  with gr.Tab("📁 Export Full Mix ZIP"):
254
  stems_files = gr.File(label="Stems", file_count="multiple")
255
  final_mix_file = gr.File(label="Final Mix")
256
  full_zip_output = gr.File(label="Full Mix Archive (.zip)")
257
  export_zip_btn = gr.Button("Export ZIP")
258
+
259
  def export_zip(stems, final_mix):
260
  zip_path = os.path.join(tempfile.gettempdir(), "full_export.zip")
261
  with zipfile.ZipFile(zip_path, "w") as zipf:
 
263
  zipf.write(stem.name, f"stem_{i}.wav")
264
  zipf.write(final_mix.name, "final_mix.wav")
265
  return zip_path
266
+
267
  export_zip_btn.click(export_zip, inputs=[stems_files, final_mix_file], outputs=full_zip_output)
268
 
269
  demo.launch()