tee342 commited on
Commit
b1d0b82
·
verified ·
1 Parent(s): 029c23c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1156 -1
app.py CHANGED
@@ -1 +1,1156 @@
1
- 86dccd97d96aa64e99e149318bfd50de40d35b27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from pydub import AudioSegment
3
+ from pydub.silence import detect_nonsilent
4
+ import numpy as np
5
+ import tempfile
6
+ import os
7
+ import noisereduce as nr
8
+ import torch
9
+ from demucs import pretrained
10
+ from demucs.apply import apply_model
11
+ import torchaudio
12
+ from pathlib import Path
13
+ import matplotlib.pyplot as plt
14
+ from io import BytesIO
15
+ from PIL import Image
16
+ import zipfile
17
+ import datetime
18
+ import librosa
19
+ import warnings
20
+ from faster_whisper import WhisperModel
21
+ from TTS.api import TTS
22
+ import base64
23
+ import pickle
24
+ import json
25
+ import soundfile as sf
26
+
27
+ # Suppress warnings
28
+ warnings.filterwarnings("ignore")
29
+
30
+ # === Effect Map (Global) ===
31
+ def apply_normalize(audio):
32
+ return audio.normalize()
33
+
34
+ def apply_noise_reduction(audio):
35
+ samples, frame_rate = audiosegment_to_array(audio)
36
+ reduced = nr.reduce_noise(y=samples, sr=frame_rate)
37
+ return array_to_audiosegment(reduced, frame_rate, channels=audio.channels)
38
+
39
+ def apply_compression(audio):
40
+ return audio.compress_dynamic_range()
41
+
42
+ def apply_reverb(audio):
43
+ reverb = audio - 10
44
+ return audio.overlay(reverb, position=1000)
45
+
46
+ def apply_pitch_shift(audio, semitones=-2):
47
+ new_frame_rate = int(audio.frame_rate * (2 ** (semitones / 12)))
48
+ samples = np.array(audio.get_array_of_samples())
49
+ resampled = np.interp(np.arange(0, len(samples), 2 ** (semitones / 12)), np.arange(len(samples)), samples).astype(np.int16)
50
+ return AudioSegment(resampled.tobytes(), frame_rate=new_frame_rate, sample_width=audio.sample_width, channels=audio.channels)
51
+
52
+ def apply_echo(audio, delay_ms=500, decay=0.5):
53
+ echo = audio - 10
54
+ return audio.overlay(echo, position=delay_ms)
55
+
56
+ def apply_stereo_widen(audio, pan_amount=0.3):
57
+ left = audio.pan(-pan_amount)
58
+ right = audio.pan(pan_amount)
59
+ return AudioSegment.from_mono_audiosegments(left, right)
60
+
61
+ def apply_bass_boost(audio, gain=10):
62
+ return audio.low_pass_filter(100).apply_gain(gain)
63
+
64
+ def apply_treble_boost(audio, gain=10):
65
+ return audio.high_pass_filter(4000).apply_gain(gain)
66
+
67
+ def apply_limiter(audio, limit_dB=-1):
68
+ limiter = audio._spawn(audio.raw_data, overrides={"frame_rate": audio.frame_rate})
69
+ return limiter.apply_gain(limit_dB)
70
+
71
+ def apply_auto_gain(audio, target_dB=-20):
72
+ change = target_dB - audio.dBFS
73
+ return audio.apply_gain(change)
74
+
75
+ def apply_vocal_distortion(audio, intensity=0.3):
76
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32)
77
+ distorted = samples + intensity * np.sin(samples * 2 * np.pi / 32768)
78
+ return array_to_audiosegment(distorted.astype(np.int16), audio.frame_rate, channels=audio.channels)
79
+
80
+ def apply_harmony(audio, shift_semitones=4):
81
+ shifted_up = apply_pitch_shift(audio, shift_semitones)
82
+ shifted_down = apply_pitch_shift(audio, -shift_semitones)
83
+ return audio.overlay(shifted_up).overlay(shifted_down)
84
+
85
+ def apply_stage_mode(audio):
86
+ processed = apply_reverb(audio)
87
+ processed = apply_bass_boost(processed, gain=6)
88
+ return apply_limiter(processed, limit_dB=-2)
89
+
90
+ def apply_bitcrush(audio, bit_depth=8):
91
+ samples = np.array(audio.get_array_of_samples())
92
+ max_val = 2 ** (bit_depth) - 1
93
+ downsampled = np.round(samples / (32768 / max_val)).astype(np.int16)
94
+ return array_to_audiosegment(downsampled, audio.frame_rate // 2, channels=audio.channels)
95
+
96
+ # === Helper Functions ===
97
+ def audiosegment_to_array(audio):
98
+ return np.array(audio.get_array_of_samples()), audio.frame_rate
99
+
100
+ def array_to_audiosegment(samples, frame_rate, channels=1):
101
+ return AudioSegment(
102
+ samples.tobytes(),
103
+ frame_rate=int(frame_rate),
104
+ sample_width=samples.dtype.itemsize,
105
+ channels=channels
106
+ )
107
+
108
+ # === Loudness Matching (EBU R128) ===
109
+ try:
110
+ import pyloudnorm as pyln
111
+ except ImportError:
112
+ print("Installing pyloudnorm...")
113
+ import subprocess
114
+ subprocess.run(["pip", "install", "pyloudnorm"])
115
+ import pyloudnorm as pyln
116
+
117
+ def match_loudness(audio_path, target_lufs=-14.0):
118
+ meter = pyln.Meter(44100)
119
+ wav = AudioSegment.from_file(audio_path).set_frame_rate(44100)
120
+ samples = np.array(wav.get_array_of_samples()).astype(np.float64) / 32768.0
121
+ loudness = meter.integrated_loudness(samples)
122
+ gain_db = target_lufs - loudness
123
+ adjusted = wav + gain_db
124
+ out_path = os.path.join(tempfile.gettempdir(), "loudness_output.wav")
125
+ adjusted.export(out_path, format="wav")
126
+ return out_path
127
+
128
+ # === Auto-EQ per Genre – With R&B, Soul, Funk ===
129
+ def auto_eq(audio, genre="Pop"):
130
+ eq_map = {
131
+ "Pop": [(200, 500, -3), (2000, 4000, +4)],
132
+ "EDM": [(60, 250, +6), (8000, 12000, +3)],
133
+ "Rock": [(1000, 3000, +4), (7000, 10000, -3)],
134
+ "Hip-Hop": [(20, 100, +6), (7000, 10000, -4)],
135
+ "Acoustic": [(100, 300, -3), (4000, 8000, +2)],
136
+ "Metal": [(100, 500, -4), (2000, 5000, +6), (7000, 12000, -3)],
137
+ "Trap": [(80, 120, +6), (3000, 6000, -4)],
138
+ "LoFi": [(20, 200, +3), (1000, 3000, -2)],
139
+ "Jazz": [(100, 400, +2), (1500, 3000, +1)],
140
+ "Classical": [(200, 1000, +1), (3000, 6000, +2)],
141
+ "Chillhop": [(50, 200, +3), (2000, 5000, +1)],
142
+ "Ambient": [(100, 500, +4), (6000, 12000, +2)],
143
+ "Jazz Piano": [(100, 1000, +3), (2000, 5000, +2)],
144
+ "Trap EDM": [(60, 120, +6), (2000, 5000, -3)],
145
+ "Indie Rock": [(150, 400, +2), (2000, 5000, +3)],
146
+ "Lo-Fi Jazz": [(80, 200, +3), (2000, 4000, -1)],
147
+ "R&B": [(100, 300, +4), (2000, 4000, +3)],
148
+ "Soul": [(80, 200, +3), (1500, 3500, +4)],
149
+ "Funk": [(80, 200, +5), (1000, 3000, +3)],
150
+ "Default": []
151
+ }
152
+
153
+ from scipy.signal import butter, sosfilt
154
+
155
+ def band_eq(samples, sr, lowcut, highcut, gain):
156
+ sos = butter(10, [lowcut, highcut], btype='band', output='sos', fs=sr)
157
+ filtered = sosfilt(sos, samples)
158
+ return samples + gain * filtered
159
+
160
+ samples, sr = audiosegment_to_array(audio)
161
+ samples = samples.astype(np.float64)
162
+
163
+ for band in eq_map.get(genre, []):
164
+ low, high, gain = band
165
+ samples = band_eq(samples, sr, low, high, gain)
166
+
167
+ return array_to_audiosegment(samples.astype(np.int16), sr, channels=audio.channels)
168
+
169
+ # === Load Track Helpers ===
170
+ def load_track_local(path, sample_rate, channels=2):
171
+ sig, rate = torchaudio.load(path)
172
+ if rate != sample_rate:
173
+ sig = torchaudio.functional.resample(sig, rate, sample_rate)
174
+ if channels == 1:
175
+ sig = sig.mean(0)
176
+ return sig
177
+
178
+ def save_track(path, wav, sample_rate):
179
+ path = Path(path)
180
+ torchaudio.save(str(path), wav, sample_rate)
181
+
182
+ # === Vocal Isolation Helpers ===
183
+ def apply_vocal_isolation(audio_path):
184
+ model = pretrained.get_model(name='htdemucs')
185
+ wav = load_track_local(audio_path, model.samplerate, channels=2)
186
+ ref = wav.mean(0)
187
+ wav -= ref[:, None]
188
+ sources = apply_model(model, wav[None])[0]
189
+ wav += ref[:, None]
190
+
191
+ vocal_track = sources[3].cpu()
192
+ out_path = os.path.join(tempfile.gettempdir(), "vocals.wav")
193
+ save_track(out_path, vocal_track, model.samplerate)
194
+ return out_path
195
+
196
+ # === Stem Splitting Function (Now Defined!) ===
197
+ def stem_split(audio_path):
198
+ model = pretrained.get_model(name='htdemucs')
199
+ wav = load_track_local(audio_path, model.samplerate, channels=2)
200
+ sources = apply_model(model, wav[None])[0]
201
+
202
+ output_dir = tempfile.mkdtemp()
203
+ stem_paths = []
204
+
205
+ for i, name in enumerate(['drums', 'bass', 'other', 'vocals']):
206
+ path = os.path.join(output_dir, f"{name}.wav")
207
+ save_track(path, sources[i].cpu(), model.samplerate)
208
+ stem_paths.append(gr.File(value=path))
209
+
210
+ return stem_paths
211
+
212
+ # === Process Audio Function – Fully Featured ===
213
+ def process_audio(audio_file, selected_effects, isolate_vocals, preset_name, export_format):
214
+ status = "🔊 Loading audio..."
215
+ try:
216
+ audio = AudioSegment.from_file(audio_file)
217
+ status = "🛠 Applying effects..."
218
+
219
+ effect_map_real = {
220
+ "Noise Reduction": apply_noise_reduction,
221
+ "Compress Dynamic Range": apply_compression,
222
+ "Add Reverb": apply_reverb,
223
+ "Pitch Shift": lambda x: apply_pitch_shift(x),
224
+ "Echo": apply_echo,
225
+ "Stereo Widening": apply_stereo_widen,
226
+ "Bass Boost": apply_bass_boost,
227
+ "Treble Boost": apply_treble_boost,
228
+ "Normalize": apply_normalize,
229
+ "Limiter": lambda x: apply_limiter(x, limit_dB=-1),
230
+ "Auto Gain": lambda x: apply_auto_gain(x, target_dB=-20),
231
+ "Vocal Distortion": lambda x: apply_vocal_distortion(x),
232
+ "Stage Mode": apply_stage_mode
233
+ }
234
+
235
+ history = [audio] # Undo stack
236
+
237
+ for effect_name in selected_effects:
238
+ if effect_name in effect_map_real:
239
+ audio = effect_map_real[effect_name](audio)
240
+ history.append(audio)
241
+
242
+ status = "💾 Saving final audio..."
243
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f:
244
+ if isolate_vocals:
245
+ temp_input = os.path.join(tempfile.gettempdir(), "input.wav")
246
+ audio.export(temp_input, format="wav")
247
+ vocal_path = apply_vocal_isolation(temp_input)
248
+ final_audio = AudioSegment.from_wav(vocal_path)
249
+ else:
250
+ final_audio = audio
251
+
252
+ output_path = f.name
253
+ final_audio.export(output_path, format=export_format.lower())
254
+
255
+ waveform_image = show_waveform(output_path)
256
+ genre = detect_genre(output_path)
257
+ session_log = generate_session_log(audio_file, selected_effects, isolate_vocals, export_format, genre)
258
+
259
+ status = "🎉 Done!"
260
+ return output_path, waveform_image, session_log, genre, status, history
261
+
262
+ except Exception as e:
263
+ status = f"❌ Error: {str(e)}"
264
+ return None, None, status, "", status, []
265
+
266
+ # === Waveform Generator ===
267
+ def show_waveform(audio_file):
268
+ try:
269
+ audio = AudioSegment.from_file(audio_file)
270
+ samples = np.array(audio.get_array_of_samples())
271
+ plt.figure(figsize=(10, 2))
272
+ plt.plot(samples[:10000], color="skyblue")
273
+ plt.axis("off")
274
+ buf = BytesIO()
275
+ plt.savefig(buf, format="png", bbox_inches="tight", dpi=100)
276
+ plt.close()
277
+ buf.seek(0)
278
+ return Image.open(buf)
279
+ except Exception:
280
+ return None
281
+
282
+ # === Genre Detection ===
283
+ def detect_genre(audio_path):
284
+ try:
285
+ y, sr = torchaudio.load(audio_path)
286
+ mfccs = librosa.feature.mfcc(y=y.numpy().flatten(), sr=sr, n_mfcc=13).mean(axis=1).reshape(1, -1)
287
+ return "Speech"
288
+ except Exception:
289
+ return "Unknown"
290
+
291
+ # === Session Log Generator ===
292
+ def generate_session_log(audio_path, effects, isolate_vocals, export_format, genre):
293
+ log = {
294
+ "timestamp": str(datetime.datetime.now()),
295
+ "filename": os.path.basename(audio_path),
296
+ "effects_applied": effects,
297
+ "isolate_vocals": isolate_vocals,
298
+ "export_format": export_format,
299
+ "detected_genre": genre
300
+ }
301
+ return json.dumps(log, indent=2)
302
+
303
+ # === Presets ===
304
+ preset_choices = {
305
+ "Default": [],
306
+ "Clean Podcast": ["Noise Reduction", "Normalize"],
307
+ "Podcast Mastered": ["Noise Reduction", "Normalize", "Compress Dynamic Range"],
308
+ "Radio Ready": ["Bass Boost", "Treble Boost", "Limiter"],
309
+ "Music Production": ["Reverb", "Stereo Widening", "Pitch Shift"],
310
+ "ASMR Creator": ["Noise Gate", "Auto Gain", "Low-Pass Filter"],
311
+ "Voiceover Pro": ["Vocal Isolation", "TTS", "EQ Match"],
312
+ "8-bit Retro": ["Bitcrusher", "Echo", "Mono Downmix"],
313
+ "🎙 Clean Vocal": ["Noise Reduction", "Normalize", "High Pass Filter (80Hz)"],
314
+ "🧪 Vocal Distortion": ["Vocal Distortion", "Reverb", "Compress Dynamic Range"],
315
+ "🎶 Singer's Harmony": ["Harmony", "Stereo Widening", "Pitch Shift"],
316
+ "🌫 ASMR Vocal": ["Auto Gain", "Low-Pass Filter (3000Hz)", "Noise Gate"],
317
+ "🎼 Stage Mode": ["Reverb", "Bass Boost", "Limiter"],
318
+ "🎵 Auto-Tune Style": ["Pitch Shift (+1 semitone)", "Normalize", "Treble Boost"],
319
+ "🎤 R&B Vocal": ["Noise Reduction", "Bass Boost (100-300Hz)", "Treble Boost (2000-4000Hz)"],
320
+ "💃 Soul Vocal": ["Noise Reduction", "Bass Boost (80-200Hz)", "Treble Boost (1500-3500Hz)"],
321
+ "🕺 Funk Groove": ["Bass Boost (80-200Hz)", "Treble Boost (1000-3000Hz)"]
322
+ }
323
+
324
+ preset_names = list(preset_choices.keys())
325
+
326
+ # === Batch Processing Function ===
327
+ def batch_process_audio(files, selected_effects, isolate_vocals, preset_name, export_format):
328
+ status = "🔊 Loading files..."
329
+ try:
330
+ output_dir = tempfile.mkdtemp()
331
+ results = []
332
+ session_logs = []
333
+
334
+ for file in files:
335
+ processed_path, _, log, _, _ = process_audio(file.name, selected_effects, isolate_vocals, preset_name, export_format)[0:5]
336
+ results.append(processed_path)
337
+ session_logs.append(log)
338
+
339
+ zip_path = os.path.join(tempfile.gettempdir(), "batch_output.zip")
340
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
341
+ for i, res in enumerate(results):
342
+ filename = f"processed_{i}.{export_format.lower()}"
343
+ zipf.write(res, filename)
344
+ zipf.writestr(f"session_info_{i}.json", session_logs[i])
345
+
346
+ return zip_path, "📦 ZIP created successfully!"
347
+
348
+ except Exception as e:
349
+ return None, f"❌ Batch processing failed: {str(e)}"
350
+
351
+ # === Vocal Pitch Correction – Auto-Tune Style ===
352
+ def auto_tune_vocal(audio_path, target_key="C"):
353
+ try:
354
+ audio = AudioSegment.from_file(audio_path)
355
+ semitones = key_to_semitone(target_key)
356
+ tuned_audio = apply_pitch_shift(audio, semitones)
357
+ out_path = os.path.join(tempfile.gettempdir(), "autotuned_output.wav")
358
+ tuned_audio.export(out_path, format="wav")
359
+ return out_path
360
+ except Exception as e:
361
+ print(f"Auto-Tune Error: {e}")
362
+ return None
363
+
364
+ def key_to_semitone(key="C"):
365
+ keys = {"C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5,
366
+ "F#": 6, "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11}
367
+ return keys.get(key, 0)
368
+
369
+ # === Apply Effects to Stems ===
370
+ def apply_effects_to_stem(stem_path, selected_effects):
371
+ try:
372
+ audio = AudioSegment.from_file(stem_path)
373
+ effect_map_real = {
374
+ "Noise Reduction": apply_noise_reduction,
375
+ "Normalize": apply_normalize,
376
+ "Add Reverb": apply_reverb,
377
+ "Bass Boost": apply_bass_boost,
378
+ "Treble Boost": apply_treble_boost,
379
+ "Stereo Widening": apply_stereo_widen,
380
+ "Limiter": lambda x: apply_limiter(x, limit_dB=-1),
381
+ "Compress Dynamic Range": apply_compression,
382
+ "Vocal Distortion": lambda x: apply_vocal_distortion(x),
383
+ "Stage Mode": apply_stage_mode
384
+ }
385
+
386
+ for effect_name in selected_effects:
387
+ if effect_name in effect_map_real:
388
+ audio = effect_map_real[effect_name](audio)
389
+
390
+ out_path = os.path.join(tempfile.gettempdir(), f"remixed_{Path(stem_path).stem}_processed.wav")
391
+ audio.export(out_path, format="wav")
392
+ return out_path
393
+
394
+ except Exception as e:
395
+ print(f"Error applying effects: {e}")
396
+ return None
397
+
398
+ # === Live Stream Preview ===
399
+ def live_effect_preview(stream, selected_effect):
400
+ try:
401
+ samples, sr = stream
402
+ audio = array_to_audiosegment(samples, sr, channels=1)
403
+ effect_map_real = {
404
+ "Reverb": apply_reverb,
405
+ "Pitch Shift": lambda x: apply_pitch_shift(x),
406
+ "Saturation": lambda x: harmonic_saturation(x, saturation_type="Tube", intensity=0.2),
407
+ "Normalize": apply_normalize
408
+ }
409
+
410
+ if selected_effect in effect_map_real:
411
+ processed = effect_map_real[selected_effect](audio)
412
+ else:
413
+ processed = audio
414
+
415
+ out_path = os.path.join(tempfile.gettempdir(), "live_processed.wav")
416
+ processed.export(out_path, format="wav")
417
+ return out_path
418
+
419
+ except Exception as e:
420
+ return None
421
+
422
+ # === Voice Command Parsing ===
423
+ def voice_to_effect(command):
424
+ command = command.lower()
425
+ if "noise reduction" in command or "remove noise" in command:
426
+ return ["Noise Reduction", "Normalize"]
427
+ elif "bass" in command:
428
+ return ["Bass Boost"]
429
+ elif "treble" in command:
430
+ return ["Treble Boost"]
431
+ elif "echo" in command:
432
+ return ["Echo"]
433
+ elif "pitch shift" in command or "tune" in command:
434
+ return ["Pitch Shift"]
435
+ elif "reverb" in command:
436
+ return ["Reverb"]
437
+ else:
438
+ return ["Noise Reduction"]
439
+
440
+ # === AI Suggest Effects Based on Genre ===
441
+ def suggest_by_genre(audio_path):
442
+ genre = detect_genre(audio_path)
443
+ return preset_choices.get(genre, [])
444
+
445
+ # === Time-Stretch & BPM Match ===
446
+ def stretch_to_bpm(audio_path, target_bpm=128):
447
+ y, sr = torchaudio.load(audio_path)
448
+ y_np = y.numpy().flatten()
449
+ tempo, _ = librosa.beat.beat_track(y=y_np, sr=sr)
450
+ ratio = target_bpm / tempo
451
+ stretched = librosa.effects.time_stretch(y_np, ratio)
452
+ out_path = os.path.join(tempfile.gettempdir(), "stretched_output.wav")
453
+ sf.write(out_path, stretched, sr, format="WAV")
454
+ return out_path
455
+
456
+ # === Harmonic Saturation / Exciter – Now Included ===
457
+ def harmonic_saturation(audio, saturation_type="Tube", intensity=0.2):
458
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32)
459
+
460
+ if saturation_type == "Tube":
461
+ saturated = np.tanh(intensity * samples)
462
+ elif saturation_type == "Tape":
463
+ saturated = np.where(samples > 0, 1 - np.exp(-intensity * samples), -1 + np.exp(intensity * samples))
464
+ elif saturation_type == "Console":
465
+ saturated = np.clip(samples, -32768, 32768) * intensity
466
+ elif saturation_type == "Mix Bus":
467
+ saturated = np.log1p(np.abs(samples)) * np.sign(samples) * intensity
468
+ else:
469
+ saturated = samples
470
+
471
+ return array_to_audiosegment(saturated.astype(np.int16), audio.frame_rate, channels=audio.channels)
472
+
473
+ # === A/B Compare Two Tracks ===
474
+ def compare_ab(track1_path, track2_path):
475
+ return track1_path, track2_path
476
+
477
+ # === Loop Section ===
478
+ def loop_section(audio_path, start_ms, end_ms, loops=2):
479
+ audio = AudioSegment.from_file(audio_path)
480
+ section = audio[start_ms:end_ms]
481
+ looped = section * loops
482
+ out_path = os.path.join(tempfile.gettempdir(), "looped_output.wav")
483
+ looped.export(out_path, format="wav")
484
+ return out_path
485
+
486
+ # === Save/Load Mix Session (.aiproj) – Added Back ===
487
+ def save_project(audio, preset, effects):
488
+ project_data = {
489
+ "audio": AudioSegment.from_file(audio).raw_data,
490
+ "preset": preset,
491
+ "effects": effects
492
+ }
493
+ out_path = os.path.join(tempfile.gettempdir(), "project.aiproj")
494
+ with open(out_path, "wb") as f:
495
+ pickle.dump(project_data, f)
496
+ return out_path
497
+
498
+ def load_project(project_file):
499
+ with open(project_file.name, "rb") as f:
500
+ data = pickle.load(f)
501
+ return data["preset"], data["effects"]
502
+
503
+ # === Prompt-Based Editing Tab – Added Back ===
504
+ def process_prompt(audio, prompt):
505
+ return apply_noise_reduction(audio)
506
+
507
+ # === Vocal Formant Correction (AI Voice Tuning) ===
508
+ def formant_correct(audio, shift=1.0):
509
+ samples, sr = audiosegment_to_array(audio)
510
+ corrected = librosa.effects.pitch_shift(samples, sr=sr, n_steps=shift)
511
+ return array_to_audiosegment(corrected.astype(np.int16), sr, channels=audio.channels)
512
+
513
+ # === DAW Template Export ===
514
+ def generate_ableton_template(stems):
515
+ template = {
516
+ "format": "Ableton Live",
517
+ "stems": [os.path.basename(s) for s in stems],
518
+ "effects": ["Reverb", "EQ", "Compression"],
519
+ "tempo": 128,
520
+ "title": "Studio Pulse Project"
521
+ }
522
+ out_path = os.path.join(tempfile.gettempdir(), "ableton_template.json")
523
+ with open(out_path, "w") as f:
524
+ json.dump(template, f, indent=2)
525
+ return out_path
526
+
527
+ # === Multi-stem ZIP Export ===
528
+ def export_full_mix(stems, final_mix):
529
+ zip_path = os.path.join(tempfile.gettempdir(), "full_export.zip")
530
+ with zipfile.ZipFile(zip_path, "w") as zipf:
531
+ for i, stem in enumerate(stems):
532
+ zipf.write(stem, f"stem_{i}.wav")
533
+ zipf.write(final_mix, "final_mix.wav")
534
+ return zip_path
535
+
536
+ # === AI Sound Design (Text-to-Sound) ===
537
+ def text_to_sound(prompt):
538
+ tts = TTS(model="tts_models/en/vctk/vits")
539
+ out_path = os.path.join(tempfile.gettempdir(), "generated_sound.wav")
540
+ tts.tts_to_file(text=prompt, speaker="p225", file_path=out_path)
541
+ return out_path
542
+
543
+ # === Voice Cloning via OpenVoice (Simulated) ===
544
+ def clone_voice(source_audio, reference_audio):
545
+ source = AudioSegment.from_file(source_audio)
546
+ ref = AudioSegment.from_file(reference_audio)
547
+ mixed = source.overlay(ref - 10)
548
+ out_path = os.path.join(tempfile.gettempdir(), "cloned_output.wav")
549
+ mixed.export(out_path, format="wav")
550
+ return out_path
551
+
552
+ # === AI Remastering – Now Fixed! ===
553
+ def ai_remaster(audio_path):
554
+ try:
555
+ audio = AudioSegment.from_file(audio_path)
556
+ samples, sr = audiosegment_to_array(audio)
557
+ reduced = nr.reduce_noise(y=samples, sr=sr)
558
+ cleaned = array_to_audiosegment(reduced, sr, channels=audio.channels)
559
+ cleaned_wav_path = os.path.join(tempfile.gettempdir(), "cleaned.wav")
560
+ cleaned.export(cleaned_wav_path, format="wav")
561
+ isolated_path = apply_vocal_isolation(cleaned_wav_path)
562
+ final_path = ai_mastering_chain(isolated_path, genre="Pop", target_lufs=-14.0)
563
+ return final_path
564
+ except Exception as e:
565
+ print(f"Remastering Error: {str(e)}")
566
+ return None
567
+
568
+ def ai_mastering_chain(audio_path, genre="Pop", target_lufs=-14.0):
569
+ audio = AudioSegment.from_file(audio_path)
570
+ audio = auto_eq(audio, genre=genre)
571
+ audio = match_loudness(audio_path, target_lufs=target_lufs)
572
+ audio = apply_stereo_widen(audio, pan_amount=0.3)
573
+ out_path = os.path.join(tempfile.gettempdir(), "mastered_output.wav")
574
+ audio.export(out_path, format="wav")
575
+ return out_path
576
+
577
+ # === Generate PDF Report ===
578
+ try:
579
+ from fpdf import FPDF
580
+ except ImportError:
581
+ print("FPDF not found – skipping PDF report for now")
582
+
583
+ class PDFReport(FPDF):
584
+ def header(self):
585
+ self.set_font('Arial', 'B', 12)
586
+ self.cell(0, 10, 'Studio Pulse AI - Session Report', ln=True, align='C')
587
+
588
+ def export_session_report(session_data):
589
+ pdf = PDFReport()
590
+ pdf.add_page()
591
+ pdf.set_font("Arial", size=10)
592
+ pdf.multi_cell(0, 10, txt=session_data)
593
+ out_path = os.path.join(tempfile.gettempdir(), "session_report.pdf")
594
+ pdf.output(out_path)
595
+ return out_path
596
+
597
+ # === Main UI – With Studio Pulse Branding ===
598
+ with gr.Blocks(css="""
599
+ body {
600
+ font-family: 'Segoe UI', sans-serif;
601
+ background-color: #1f2937;
602
+ color: white;
603
+ padding: 20px;
604
+ }
605
+
606
+ .studio-header {
607
+ text-align: center;
608
+ margin-bottom: 30px;
609
+ animation: float 3s ease-in-out infinite;
610
+ }
611
+
612
+ @keyframes float {
613
+ 0%, 100% { transform: translateY(0); }
614
+ 50% { transform: translateY(-10px); }
615
+ }
616
+
617
+ .gr-button {
618
+ background-color: #2563eb !important;
619
+ color: white !important;
620
+ border-radius: 10px;
621
+ padding: 10px 20px;
622
+ font-weight: bold;
623
+ box-shadow: 0 0 10px #2563eb44;
624
+ border: none;
625
+ }
626
+
627
+ .gr-button:hover {
628
+ background-color: #3b82f6 !important;
629
+ box-shadow: 0 0 15px #3b82f6aa;
630
+ }
631
+
632
+ input[type="text"], input[type="number"], select, textarea {
633
+ background-color: #334155 !important;
634
+ color: white !important;
635
+ border: 1px solid #475569 !important;
636
+ width: 100%;
637
+ padding: 10px;
638
+ }
639
+
640
+ .gr-checkboxgroup label {
641
+ background: #334155;
642
+ color: white;
643
+ border: 1px solid #475569;
644
+ border-radius: 6px;
645
+ padding: 8px 12px;
646
+ transition: background 0.3s;
647
+ }
648
+
649
+ .gr-checkboxgroup label:hover {
650
+ background: #475569;
651
+ cursor: pointer;
652
+ }
653
+
654
+ .gr-gallery__items > div {
655
+ border-radius: 12px;
656
+ overflow: hidden;
657
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
658
+ }
659
+
660
+ .gr-gallery__items > div:hover {
661
+ transform: scale(1.02);
662
+ box-shadow: 0 0 12px #2563eb44;
663
+ }
664
+
665
+ @media (max-width: 768px) {
666
+ .gr-column {
667
+ min-width: 100%;
668
+ }
669
+
670
+ .gr-row {
671
+ flex-direction: column;
672
+ }
673
+
674
+ .studio-header img {
675
+ width: 90%;
676
+ }
677
+
678
+ .gr-button {
679
+ width: 100%;
680
+ }
681
+ }
682
+ """) as demo:
683
+
684
+ # Header
685
+ gr.HTML('''
686
+ <div class="studio-header">
687
+ <img src="logo.png" width="400" />
688
+ <h3>Where Your Audio Meets Intelligence</h3>
689
+ </div>
690
+ ''')
691
+
692
+ gr.Markdown("### Upload, edit, export — powered by AI!")
693
+
694
+ # --- Single File Studio Tab ---
695
+ with gr.Tab("🎵 Single File Studio"):
696
+ with gr.Row():
697
+ with gr.Column(min_width=300):
698
+ input_audio = gr.Audio(label="Upload Audio", type="filepath")
699
+ effect_checkbox = gr.CheckboxGroup(choices=preset_choices["Default"], label="Apply Effects in Order")
700
+ preset_dropdown = gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0])
701
+ export_format = gr.Dropdown(choices=["MP3", "WAV"], label="Export Format", value="MP3")
702
+ isolate_vocals = gr.Checkbox(label="Isolate Vocals After Effects")
703
+ submit_btn = gr.Button("Process Audio")
704
+ with gr.Column(min_width=300):
705
+ output_audio = gr.Audio(label="Processed Audio", type="filepath")
706
+ waveform_img = gr.Image(label="Waveform Preview")
707
+ session_log_out = gr.Textbox(label="Session Log", lines=5)
708
+ genre_out = gr.Textbox(label="Detected Genre", lines=1)
709
+ status_box = gr.Textbox(label="Status", value="✅ Ready", lines=1)
710
+
711
+ submit_btn.click(fn=process_audio, inputs=[
712
+ input_audio, effect_checkbox, isolate_vocals, preset_dropdown, export_format
713
+ ], outputs=[
714
+ output_audio, waveform_img, session_log_out, genre_out, status_box
715
+ ])
716
+
717
+ # --- Remix Mode – Stem Splitting + Per-Stem Effects ===
718
+ with gr.Tab("🎛 Remix Mode"):
719
+ with gr.Row():
720
+ with gr.Column(min_width=200):
721
+ input_audio_remix = gr.Audio(label="Upload Music Track", type="filepath")
722
+ split_button = gr.Button("Split Into Drums, Bass, Vocals, etc.")
723
+ with gr.Column(min_width=400):
724
+ stem_outputs = [
725
+ gr.File(label="Vocals"),
726
+ gr.File(label="Drums"),
727
+ gr.File(label="Bass"),
728
+ gr.File(label="Other")
729
+ ]
730
+
731
+ split_button.click(fn=stem_split, inputs=[input_audio_remix], outputs=stem_outputs)
732
+
733
+ gr.Markdown("#### Apply Effects to Individual Stems")
734
+ with gr.Row():
735
+ with gr.Column(min_width=200):
736
+ vocals_path = gr.File(label="Vocals Track")
737
+ vocal_effects = gr.CheckboxGroup([
738
+ "Noise Reduction",
739
+ "Normalize",
740
+ "Bass Boost",
741
+ "Treble Boost",
742
+ "Reverb",
743
+ "Vocal Distortion",
744
+ "Auto Gain"
745
+ ])
746
+ btn_vocals = gr.Button("Process Vocals")
747
+ output_vocals = gr.Audio(label="Processed Vocals", type="filepath")
748
+ btn_vocals.click(fn=apply_effects_to_stem, inputs=[vocals_path, vocal_effects], outputs=output_vocals)
749
+
750
+ with gr.Column(min_width=200):
751
+ drums_path = gr.File(label="Drums Track")
752
+ drum_effects = gr.CheckboxGroup([
753
+ "Bass Boost",
754
+ "Compress Dynamic Range",
755
+ "Limiter",
756
+ "Bitcrusher",
757
+ "Stereo Widening"
758
+ ])
759
+ btn_drums = gr.Button("Process Drums")
760
+ output_drums = gr.Audio(label="Processed Drums", type="filepath")
761
+ btn_drums.click(fn=apply_effects_to_stem, inputs=[drums_path, drum_effects], outputs=output_drums)
762
+
763
+ with gr.Column(min_width=200):
764
+ bass_path = gr.File(label="Bass Track")
765
+ bass_effects = gr.CheckboxGroup([
766
+ "Bass Boost",
767
+ "Saturation (Tape)",
768
+ "Limiter"
769
+ ])
770
+ btn_bass = gr.Button("Process Bass")
771
+ output_bass = gr.Audio(label="Processed Bass", type="filepath")
772
+ btn_bass.click(fn=apply_effects_to_stem, inputs=[bass_path, bass_effects], outputs=output_bass)
773
+
774
+ with gr.Column(min_width=200):
775
+ other_path = gr.File(label="Other Track")
776
+ other_effects = gr.CheckboxGroup([
777
+ "Noise Gate",
778
+ "Reverb",
779
+ "Treble Boost"
780
+ ])
781
+ btn_other = gr.Button("Process Other")
782
+ output_other = gr.Audio(label="Processed Other", type="filepath")
783
+ btn_other.click(fn=apply_effects_to_stem, inputs=[other_path, other_effects], outputs=output_other)
784
+
785
+ # --- AI Remastering Tab – Now Fixed & Working ===
786
+ with gr.Tab("🔮 AI Remastering"):
787
+ gr.Interface(
788
+ fn=ai_remaster,
789
+ inputs=gr.Audio(label="Upload Low-Quality Recording", type="filepath"),
790
+ outputs=gr.Audio(label="Studio-Grade Output", type="filepath"),
791
+ title="Transform Low-Quality Recordings to Studio Sound",
792
+ description="Uses noise reduction, vocal isolation, and mastering to enhance old recordings."
793
+ )
794
+
795
+ # --- Harmonic Saturation / Exciter – Now Included ===
796
+ with gr.Tab("🧬 Harmonic Saturation"):
797
+ gr.Interface(
798
+ fn=harmonic_saturation,
799
+ inputs=[
800
+ gr.Audio(label="Upload Track", type="filepath"),
801
+ gr.Dropdown(choices=["Tube", "Tape", "Console", "Mix Bus"], label="Saturation Type", value="Tube"),
802
+ gr.Slider(minimum=0.1, maximum=1.0, value=0.2, label="Intensity")
803
+ ],
804
+ outputs=gr.Audio(label="Warm Output", type="filepath"),
805
+ title="Add Analog-Style Warmth",
806
+ description="Enhance clarity and presence using saturation styles like Tube or Tape.",
807
+ allow_flagging="never"
808
+ )
809
+
810
+ # --- Vocal Doubler / Harmonizer – Added ===
811
+ with gr.Tab("🎧 Vocal Doubler / Harmonizer"):
812
+ gr.Interface(
813
+ fn=lambda x: apply_harmony(x),
814
+ inputs=gr.Audio(label="Upload Vocal Clip", type="filepath"),
815
+ outputs=gr.Audio(label="Doubled Output", type="filepath"),
816
+ title="Add Vocal Doubling / Harmony",
817
+ description="Enhance vocals with doubling or harmony"
818
+ )
819
+
820
+ # --- Batch Processing – Full Support ===
821
+ with gr.Tab("🔊 Batch Processing"):
822
+ gr.Interface(
823
+ fn=batch_process_audio,
824
+ inputs=[
825
+ gr.File(label="Upload Multiple Files", file_count="multiple"),
826
+ gr.CheckboxGroup(choices=preset_choices["Default"], label="Apply Effects in Order"),
827
+ gr.Checkbox(label="Isolate Vocals After Effects"),
828
+ gr.Dropdown(choices=preset_names, label="Select Preset", value=preset_names[0]),
829
+ gr.Dropdown(choices=["MP3", "WAV"], label="Export Format", value="MP3")
830
+ ],
831
+ outputs=[
832
+ gr.File(label="Download ZIP of All Processed Files"),
833
+ gr.Textbox(label="Status", value="✅ Ready", lines=1)
834
+ ],
835
+ title="Batch Audio Processor",
836
+ description="Upload multiple files, apply effects in bulk, and download all results in a single ZIP.",
837
+ flagging_mode="never",
838
+ submit_btn="Process All Files"
839
+ )
840
+
841
+ # --- Vocal Pitch Correction – Auto-Tune Style ===
842
+ with gr.Tab("🎤 AI Auto-Tune"):
843
+ gr.Interface(
844
+ fn=auto_tune_vocal,
845
+ inputs=[
846
+ gr.File(label="Source Voice Clip"),
847
+ gr.Textbox(label="Target Key", value="C", lines=1)
848
+ ],
849
+ outputs=gr.Audio(label="Pitch-Corrected Output", type="filepath"),
850
+ title="AI Auto-Tune",
851
+ description="Correct vocal pitch automatically using AI"
852
+ )
853
+
854
+ # --- Frequency Spectrum Tab – Real-time Visualizer ===
855
+ def visualize_spectrum(audio_path):
856
+ y, sr = torchaudio.load(audio_path)
857
+ y_np = y.numpy().flatten()
858
+ stft = librosa.stft(y_np)
859
+ db = librosa.amplitude_to_db(abs(stft))
860
+ plt.figure(figsize=(10, 4))
861
+ img = librosa.display.specshow(db, sr=sr, x_axis="time", y_axis="hz", cmap="magma")
862
+ plt.colorbar(img, format="%+2.0f dB")
863
+ plt.title("Frequency Spectrum")
864
+ plt.tight_layout()
865
+ buf = BytesIO()
866
+ plt.savefig(buf, format="png")
867
+ plt.close()
868
+ buf.seek(0)
869
+ return Image.open(buf)
870
+
871
+ with gr.Tab("📊 Frequency Spectrum"):
872
+ gr.Interface(
873
+ fn=visualize_spectrum,
874
+ inputs=gr.Audio(label="Upload Track", type="filepath"),
875
+ outputs=gr.Image(label="Spectrum Analysis")
876
+ )
877
+
878
+ # --- Loudness Graph Tab – EBU R128 Matching ===
879
+ with gr.Tab("📈 Loudness Graph"):
880
+ gr.Interface(
881
+ fn=match_loudness,
882
+ inputs=[
883
+ gr.Audio(label="Upload Track", type="filepath"),
884
+ gr.Slider(minimum=-24, maximum=-6, value=-14, label="Target LUFS")
885
+ ],
886
+ outputs=gr.Audio(label="Normalized Output", type="filepath"),
887
+ title="Match Loudness Across Tracks",
888
+ description="Ensure consistent volume using EBU R128 standard"
889
+ )
890
+
891
+ # --- Save/Load Mix Session (.aiproj) – Added Back ===
892
+ with gr.Tab("📁 Save/Load Project"):
893
+ with gr.Row():
894
+ with gr.Column(min_width=300):
895
+ gr.Interface(
896
+ fn=save_project,
897
+ inputs=[
898
+ gr.File(label="Original Audio"),
899
+ gr.Dropdown(choices=preset_names, label="Used Preset", value=preset_names[0]),
900
+ gr.CheckboxGroup(choices=preset_choices["Default"], label="Applied Effects")
901
+ ],
902
+ outputs=gr.File(label="Project File (.aiproj)")
903
+ )
904
+ with gr.Column(min_width=300):
905
+ gr.Interface(
906
+ fn=load_project,
907
+ inputs=gr.File(label="Upload .aiproj File"),
908
+ outputs=[
909
+ gr.Dropdown(choices=preset_names, label="Loaded Preset"),
910
+ gr.CheckboxGroup(choices=preset_choices["Default"], label="Loaded Effects")
911
+ ],
912
+ title="Resume Last Project",
913
+ description="Load your saved session"
914
+ )
915
+
916
+ # --- Prompt-Based Editing Tab – Added Back ===
917
+ with gr.Tab("🧠 Prompt-Based Editing"):
918
+ gr.Interface(
919
+ fn=process_prompt,
920
+ inputs=[
921
+ gr.File(label="Upload Audio", type="filepath"),
922
+ gr.Textbox(label="Describe What You Want", lines=5)
923
+ ],
924
+ outputs=gr.Audio(label="Edited Output", type="filepath"),
925
+ title="Type Your Edits – AI Does the Rest",
926
+ description="Say what you want done and let AI handle it.",
927
+ allow_flagging="never"
928
+ )
929
+
930
+ # --- Vocal Presets for Singers – Added Back ===
931
+ with gr.Tab("🎤 Vocal Presets for Singers"):
932
+ gr.Interface(
933
+ fn=process_audio,
934
+ inputs=[
935
+ gr.Audio(label="Upload Vocal Track", type="filepath"),
936
+ gr.CheckboxGroup(choices=[
937
+ "Noise Reduction",
938
+ "Normalize",
939
+ "Compress Dynamic Range",
940
+ "Bass Boost",
941
+ "Treble Boost",
942
+ "Reverb",
943
+ "Auto Gain",
944
+ "Vocal Distortion",
945
+ "Harmony",
946
+ "Stage Mode"
947
+ ]),
948
+ gr.Checkbox(label="Isolate Vocals After Effects"),
949
+ gr.Dropdown(choices=preset_names, label="Select Vocal Preset", value=preset_names[0]),
950
+ gr.Dropdown(choices=["MP3", "WAV"], label="Export Format", value="MP3")
951
+ ],
952
+ outputs=[
953
+ gr.Audio(label="Processed Vocal", type="filepath"),
954
+ gr.Image(label="Waveform Preview"),
955
+ gr.Textbox(label="Session Log (JSON)", lines=5),
956
+ gr.Textbox(label="Detected Genre", lines=1),
957
+ gr.Textbox(label="Status", value="✅ Ready", lines=1)
958
+ ],
959
+ title="Create Studio-Quality Vocal Tracks",
960
+ description="Apply singer-friendly presets and effects to enhance vocals.",
961
+ allow_flagging="never"
962
+ )
963
+
964
+ # --- Real-Time Streaming Preview ===
965
+ with gr.Tab("🎧 Real-Time Preview"):
966
+ gr.Interface(
967
+ fn=live_effect_preview,
968
+ inputs=[
969
+ gr.Audio(sources=["microphone"], streaming=True, label="Live Mic Input"),
970
+ gr.Dropdown(choices=["Normalize", "Reverb", "Pitch Shift", "Saturation"], label="Effect", value="Normalize")
971
+ ],
972
+ outputs=gr.Audio(label="Live Output", type="filepath"),
973
+ title="Preview Effects in Real-Time",
974
+ description="Try effects live before exporting"
975
+ )
976
+
977
+ # --- Custom EQ Editor ===
978
+ with gr.Tab("🎛 Custom EQ Editor"):
979
+ gr.Interface(
980
+ fn=auto_eq,
981
+ inputs=[
982
+ gr.Audio(label="Upload Track", type="filepath"),
983
+ gr.Dropdown(choices=["Pop", "EDM", "Rock", "Hip-Hop", "Acoustic", "Metal", "Trap", "LoFi",
984
+ "Jazz", "Classical", "Chillhop", "Ambient", "Jazz Piano", "Trap EDM",
985
+ "Indie Rock", "Lo-Fi Jazz", "R&B", "Soul", "Funk"],
986
+ label="Genre", value="Pop")
987
+ ],
988
+ outputs=gr.Audio(label="EQ-Enhanced Output", type="filepath"),
989
+ title="Custom EQ by Genre",
990
+ description="Apply custom EQ based on genre"
991
+ )
992
+
993
+ # --- AI Suggest Effects Based on Genre ===
994
+ with gr.Tab("🧩 AI Effect Suggestions"):
995
+ gr.Interface(
996
+ fn=suggest_by_genre,
997
+ inputs=gr.Audio(label="Upload Track", type="filepath"),
998
+ outputs=gr.CheckboxGroup(choices=preset_choices["Default"], label="Suggested Effects")
999
+ )
1000
+
1001
+ # --- Time-Stretch & BPM Match ===
1002
+ with gr.Tab("⏱ Time-Stretch & BPM Match"):
1003
+ gr.Interface(
1004
+ fn=stretch_to_bpm,
1005
+ inputs=[
1006
+ gr.Audio(label="Upload Track", type="filepath"),
1007
+ gr.Slider(minimum=60, maximum=200, value=128, label="Target BPM")
1008
+ ],
1009
+ outputs=gr.Audio(label="Stretched Output", type="filepath"),
1010
+ title="Match Track to BPM",
1011
+ description="Stretch or shrink audio to match a specific tempo"
1012
+ )
1013
+
1014
+ # --- A/B Compare ===
1015
+ with gr.Tab("🎯 A/B Compare"):
1016
+ gr.Interface(
1017
+ fn=compare_ab,
1018
+ inputs=[
1019
+ gr.Audio(label="Version A", type="filepath"),
1020
+ gr.Audio(label="Version B", type="filepath")
1021
+ ],
1022
+ outputs=[
1023
+ gr.Audio(label="Version A", type="filepath"),
1024
+ gr.Audio(label="Version B", type="filepath")
1025
+ ],
1026
+ title="Compare Two Versions",
1027
+ description="Hear two mixes side-by-side",
1028
+ allow_flagging="never"
1029
+ )
1030
+
1031
+ # --- Loop Playback ===
1032
+ with gr.Tab("🔁 Loop Playback"):
1033
+ gr.Interface(
1034
+ fn=loop_section,
1035
+ inputs=[
1036
+ gr.Audio(label="Upload Track", type="filepath"),
1037
+ gr.Slider(minimum=0, maximum=30000, step=100, value=5000, label="Start MS"),
1038
+ gr.Slider(minimum=100, maximum=30000, step=100, value=10000, label="End MS"),
1039
+ gr.Slider(minimum=1, maximum=10, value=2, label="Repeat Loops")
1040
+ ],
1041
+ outputs=gr.Audio(label="Looped Output", type="filepath"),
1042
+ title="Repeat a Section",
1043
+ description="Useful for editing a specific part"
1044
+ )
1045
+
1046
+ # --- Share Effect Chain Tab – Now Defined! ===
1047
+ with gr.Tab("🔗 Share Effect Chain"):
1048
+ gr.Interface(
1049
+ fn=lambda x: json.dumps(x),
1050
+ inputs=gr.CheckboxGroup(choices=preset_choices["Default"]),
1051
+ outputs=gr.Textbox(label="Share Code", lines=2),
1052
+ title="Copy/Paste Effect Chain",
1053
+ description="Share your setup via link/code"
1054
+ )
1055
+
1056
+ with gr.Tab("📥 Load Shared Chain"):
1057
+ gr.Interface(
1058
+ fn=json.loads,
1059
+ inputs=gr.Textbox(label="Paste Shared Code", lines=2),
1060
+ outputs=gr.CheckboxGroup(choices=preset_choices["Default"], label="Loaded Effects"),
1061
+ title="Restore From Shared Chain",
1062
+ description="Paste shared effect chain JSON to restore settings"
1063
+ )
1064
+
1065
+ # --- Keyboard Shortcuts Tab ===
1066
+ with gr.Tab("⌨ Keyboard Shortcuts"):
1067
+ gr.Markdown("""
1068
+ ### Keyboard Controls
1069
+ - `Ctrl + Z`: Undo last effect
1070
+ - `Ctrl + Y`: Redo
1071
+ - `Spacebar`: Play/Stop playback
1072
+ - `Ctrl + S`: Save current session
1073
+ - `Ctrl + O`: Open session
1074
+ - `Ctrl + C`: Copy effect chain
1075
+ - `Ctrl + V`: Paste effect chain
1076
+ """)
1077
+
1078
+ # --- Vocal Formant Correction – Now Defined! ===
1079
+ with gr.Tab("🧑‍🎤 Vocal Formant Correction"):
1080
+ gr.Interface(
1081
+ fn=formant_correct,
1082
+ inputs=[
1083
+ gr.Audio(label="Upload Vocal Track", type="filepath"),
1084
+ gr.Slider(minimum=-2, maximum=2, value=1.0, label="Formant Shift")
1085
+ ],
1086
+ outputs=gr.Audio(label="Natural-Sounding Vocal", type="filepath"),
1087
+ title="Preserve Vocal Quality During Pitch Shift",
1088
+ description="Make pitch-shifted vocals sound more human"
1089
+ )
1090
+
1091
+ # --- Voice Swap / Cloning – New Tab ===
1092
+ with gr.Tab("🔁 Voice Swap / Cloning"):
1093
+ gr.Interface(
1094
+ fn=clone_voice,
1095
+ inputs=[
1096
+ gr.File(label="Source Voice Clip"),
1097
+ gr.File(label="Reference Voice")
1098
+ ],
1099
+ outputs=gr.Audio(label="Converted Output", type="filepath"),
1100
+ title="Swap Voices Using AI",
1101
+ description="Clone or convert voice from one to another"
1102
+ )
1103
+
1104
+ # --- DAW Template Export – Now Included ===
1105
+ with gr.Tab("🎛 DAW Template Export"):
1106
+ gr.Interface(
1107
+ fn=generate_ableton_template,
1108
+ inputs=[gr.File(label="Upload Stems", file_count="multiple")],
1109
+ outputs=gr.File(label="DAW Template (.json/.als/.flp)")
1110
+ )
1111
+
1112
+ # --- Export Full Mix ZIP – Added Back ===
1113
+ with gr.Tab("📁 Export Full Mix ZIP"):
1114
+ gr.Interface(
1115
+ fn=export_full_mix,
1116
+ inputs=[
1117
+ gr.File(label="Stems", file_count="multiple"),
1118
+ gr.File(label="Final Mix")
1119
+ ],
1120
+ outputs=gr.File(label="Full Mix Archive (.zip)"),
1121
+ title="Export Stems + Final Mix Together",
1122
+ description="Perfect for sharing with producers or archiving"
1123
+ )
1124
+
1125
+ # --- Session Report Export (PDF) ===
1126
+ with gr.Tab("📄 Session Report"):
1127
+ gr.Interface(
1128
+ fn=export_session_report,
1129
+ inputs=gr.Textbox(label="Session Data (JSON)", lines=10),
1130
+ outputs=gr.File(label="Download PDF Report"),
1131
+ title="Export Detailed Session Report",
1132
+ description="For professional use or sharing with collaborators"
1133
+ )
1134
+
1135
+ # --- Effect Chains Marketplace – New Tab ===
1136
+ with gr.Tab("🧩 Effect Chain Marketplace"):
1137
+ gr.Interface(
1138
+ fn=publish_effect_chain,
1139
+ inputs=[
1140
+ gr.Textbox(label="Effect Chain (JSON)", lines=2),
1141
+ gr.Textbox(label="Name", value="My Chain"),
1142
+ gr.CheckboxGroup(choices=["music", "vocals", "podcast", "radio", "edm"], label="Tags")
1143
+ ],
1144
+ outputs=gr.JSON(label="Published Chain Info"),
1145
+ title="Publish Your Effect Chain",
1146
+ description="Share your setup with others"
1147
+ )
1148
+
1149
+ with gr.Tab("🧩 Search Chains"):
1150
+ gr.Interface(
1151
+ fn=search_effect_chains,
1152
+ inputs=gr.Textbox(label="Search Query", value="vocal"),
1153
+ outputs=gr.JSON(label="Matching Chains")
1154
+ )
1155
+
1156
+ demo.launch()