ghostai1 commited on
Commit
aaae0c3
·
verified ·
1 Parent(s): 4bc8fbd

Create stable12gblg30sec.py

Browse files
Files changed (1) hide show
  1. stable12gblg30sec.py +1251 -0
stable12gblg30sec.py ADDED
@@ -0,0 +1,1251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import os
5
+ import sys
6
+ import gc
7
+ import re
8
+ import json
9
+ import time
10
+ import mmap
11
+ import math
12
+ import torch
13
+ import random
14
+ import logging
15
+ import warnings
16
+ import traceback
17
+ import subprocess
18
+ import numpy as np
19
+ import torchaudio
20
+ import gradio as gr
21
+ import gradio_client.utils
22
+ from pydub import AudioSegment
23
+ from datetime import datetime
24
+ from pathlib import Path
25
+ from typing import Optional, Tuple, Dict, Any, List
26
+ from torch.cuda.amp import autocast
27
+
28
+ from fastapi import FastAPI, HTTPException, Body
29
+ from fastapi.middleware.cors import CORSMiddleware
30
+ from pydantic import BaseModel
31
+ import uvicorn
32
+ import threading
33
+
34
+ # ======================================================================================
35
+ # PATCHES & RUNTIME SETUP
36
+ # ======================================================================================
37
+
38
+ # Gradio schema bool patch (prevents crash for boolean schemas)
39
+ _original_get_type = gradio_client.utils.get_type
40
+ def _patched_get_type(schema):
41
+ if isinstance(schema, bool):
42
+ return "boolean"
43
+ return _original_get_type(schema)
44
+ gradio_client.utils.get_type = _patched_get_type
45
+
46
+ # Warnings
47
+ warnings.filterwarnings("ignore")
48
+
49
+ # Allocator for CUDA 12.x
50
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:128"
51
+
52
+ # Determinism/Benchmark settings
53
+ torch.backends.cudnn.benchmark = False
54
+ torch.backends.cudnn.deterministic = True
55
+
56
+ # Logging
57
+ LOG_DIR = "logs"
58
+ os.makedirs(LOG_DIR, exist_ok=True)
59
+ LOG_FILE = os.path.join(LOG_DIR, f"musicgen_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
60
+ logging.basicConfig(
61
+ level=logging.DEBUG,
62
+ format="%(asctime)s [%(levelname)s] %(message)s",
63
+ handlers=[logging.FileHandler(LOG_FILE), logging.StreamHandler(sys.stdout)]
64
+ )
65
+ logger = logging.getLogger("ghostai-musicgen")
66
+
67
+ # Device
68
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
69
+ if DEVICE != "cuda":
70
+ logger.error("CUDA is required. Exiting.")
71
+ sys.exit(1)
72
+ logger.info(f"GPU: {torch.cuda.get_device_name(0)}")
73
+ logger.info("Precision: fp16 model, fp32 CPU audio ops")
74
+
75
+ # ======================================================================================
76
+ # SETTINGS PERSISTENCE
77
+ # ======================================================================================
78
+
79
+ SETTINGS_FILE = "settings.json"
80
+
81
+ DEFAULT_SETTINGS: Dict[str, Any] = {
82
+ "cfg_scale": 5.8,
83
+ "top_k": 250, # more creative search space
84
+ "top_p": 0.95, # user requested higher probability cap
85
+ "temperature": 0.90, # user requested ~0.9
86
+ "total_duration": 60, # default to 1 minute
87
+ "bpm": 120,
88
+ "drum_beat": "none",
89
+ "synthesizer": "none",
90
+ "rhythmic_steps": "none",
91
+ "bass_style": "none",
92
+ "guitar_style": "none",
93
+ "target_volume": -23.0,
94
+ "preset": "default",
95
+ "max_steps": 1500, # keep for UI, chunking now fixed to 30s
96
+ "bitrate": "192k",
97
+ "output_sample_rate": "48000",
98
+ "bit_depth": "16",
99
+ "instrumental_prompt": ""
100
+ }
101
+
102
+ def load_settings_from_file() -> Dict[str, Any]:
103
+ try:
104
+ if os.path.exists(SETTINGS_FILE):
105
+ with open(SETTINGS_FILE, "r") as f:
106
+ data = json.load(f)
107
+ # ensure all defaults present
108
+ for k, v in DEFAULT_SETTINGS.items():
109
+ data.setdefault(k, v)
110
+ logger.info(f"Loaded settings from {SETTINGS_FILE}")
111
+ return data
112
+ except Exception as e:
113
+ logger.error(f"Failed reading {SETTINGS_FILE}: {e}")
114
+ return DEFAULT_SETTINGS.copy()
115
+
116
+ def save_settings_to_file(settings: Dict[str, Any]) -> None:
117
+ try:
118
+ with open(SETTINGS_FILE, "w") as f:
119
+ json.dump(settings, f, indent=2)
120
+ logger.info(f"Saved settings to {SETTINGS_FILE}")
121
+ except Exception as e:
122
+ logger.error(f"Failed saving {SETTINGS_FILE}: {e}")
123
+
124
+ CURRENT_SETTINGS = load_settings_from_file()
125
+
126
+ # ======================================================================================
127
+ # VRAM / DISK / MEMORY
128
+ # ======================================================================================
129
+
130
+ def clean_memory() -> Optional[float]:
131
+ try:
132
+ torch.cuda.empty_cache()
133
+ gc.collect()
134
+ torch.cuda.ipc_collect()
135
+ torch.cuda.synchronize()
136
+ vram_mb = torch.cuda.memory_allocated() / 1024**2
137
+ logger.info(f"Memory cleaned. VRAM={vram_mb:.2f} MB")
138
+ return vram_mb
139
+ except Exception as e:
140
+ logger.error(f"clean_memory failed: {e}")
141
+ logger.error(traceback.format_exc())
142
+ return None
143
+
144
+ def check_vram():
145
+ try:
146
+ r = subprocess.run(
147
+ ['nvidia-smi', '--query-gpu=memory.used,memory.total', '--format=csv'],
148
+ capture_output=True, text=True
149
+ )
150
+ lines = r.stdout.splitlines()
151
+ if len(lines) > 1:
152
+ used_mb, total_mb = map(int, re.findall(r'\d+', lines[1]))
153
+ free_mb = total_mb - used_mb
154
+ logger.info(f"VRAM: used {used_mb} MiB | free {free_mb} MiB | total {total_mb} MiB")
155
+ if free_mb < 5000:
156
+ logger.warning(f"Low free VRAM ({free_mb} MiB). Running processes:")
157
+ procs = subprocess.run(
158
+ ['nvidia-smi', '--query-compute-apps=pid,used_memory', '--format=csv'],
159
+ capture_output=True, text=True
160
+ )
161
+ logger.info(f"\n{procs.stdout}")
162
+ return free_mb
163
+ except Exception as e:
164
+ logger.error(f"check_vram failed: {e}")
165
+ return None
166
+
167
+ def check_disk_space(path=".") -> bool:
168
+ try:
169
+ stat = os.statvfs(path)
170
+ free_gb = stat.f_bavail * stat.f_frsize / (1024**3)
171
+ if free_gb < 1.0:
172
+ logger.warning(f"Low disk space: {free_gb:.2f} GB")
173
+ return free_gb >= 1.0
174
+ except Exception as e:
175
+ logger.error(f"Disk space check failed: {e}")
176
+ return False
177
+
178
+ # ======================================================================================
179
+ # AUDIO UTILS (CPU)
180
+ # ======================================================================================
181
+
182
+ def ensure_stereo(audio_segment: AudioSegment, sample_rate=48000, sample_width=2) -> AudioSegment:
183
+ try:
184
+ if audio_segment.channels != 2:
185
+ audio_segment = audio_segment.set_channels(2)
186
+ if audio_segment.frame_rate != sample_rate:
187
+ audio_segment = audio_segment.set_frame_rate(sample_rate)
188
+ return audio_segment
189
+ except Exception as e:
190
+ logger.error(f"ensure_stereo failed: {e}")
191
+ return audio_segment
192
+
193
+ def calculate_rms(segment: AudioSegment) -> float:
194
+ try:
195
+ samples = np.array(segment.get_array_of_samples(), dtype=np.float32)
196
+ rms = float(np.sqrt(np.mean(samples**2)))
197
+ return rms
198
+ except Exception as e:
199
+ logger.error(f"calculate_rms failed: {e}")
200
+ return 0.0
201
+
202
+ def hard_limit(audio_segment: AudioSegment, limit_db=-3.0, sample_rate=48000) -> AudioSegment:
203
+ try:
204
+ audio_segment = ensure_stereo(audio_segment, sample_rate, audio_segment.sample_width)
205
+ limit = 10 ** (limit_db / 20.0) * (2**23 if audio_segment.sample_width == 3 else 32767)
206
+ samples = np.array(audio_segment.get_array_of_samples(), dtype=np.float32)
207
+ samples = np.clip(samples, -limit, limit).astype(np.int32 if audio_segment.sample_width == 3 else np.int16)
208
+ if len(samples) % 2 != 0:
209
+ samples = samples[:-1]
210
+ return AudioSegment(
211
+ samples.tobytes(),
212
+ frame_rate=sample_rate,
213
+ sample_width=audio_segment.sample_width,
214
+ channels=2
215
+ )
216
+ except Exception as e:
217
+ logger.error(f"hard_limit failed: {e}")
218
+ return audio_segment
219
+
220
+ def rms_normalize(segment: AudioSegment, target_rms_db=-23.0, peak_limit_db=-3.0, sample_rate=48000) -> AudioSegment:
221
+ try:
222
+ segment = ensure_stereo(segment, sample_rate, segment.sample_width)
223
+ target_rms = 10 ** (target_rms_db / 20) * (2**23 if segment.sample_width == 3 else 32767)
224
+ current_rms = calculate_rms(segment)
225
+ if current_rms > 0:
226
+ gain_factor = target_rms / current_rms
227
+ segment = segment.apply_gain(20 * np.log10(max(gain_factor, 1e-6)))
228
+ segment = hard_limit(segment, limit_db=peak_limit_db, sample_rate=sample_rate)
229
+ return segment
230
+ except Exception as e:
231
+ logger.error(f"rms_normalize failed: {e}")
232
+ return segment
233
+
234
+ def balance_stereo(audio_segment: AudioSegment, noise_threshold=-40, sample_rate=48000) -> AudioSegment:
235
+ try:
236
+ audio_segment = ensure_stereo(audio_segment, sample_rate, audio_segment.sample_width)
237
+ samples = np.array(audio_segment.get_array_of_samples(), dtype=np.float32)
238
+ if audio_segment.channels != 2:
239
+ return audio_segment
240
+ stereo = samples.reshape(-1, 2)
241
+ db = 20 * np.log10(np.abs(stereo) + 1e-10)
242
+ mask = db > noise_threshold
243
+ stereo = stereo * mask
244
+ left = stereo[:, 0]
245
+ right = stereo[:, 1]
246
+ l_rms = np.sqrt(np.mean(left[left != 0] ** 2)) if np.any(left != 0) else 0
247
+ r_rms = np.sqrt(np.mean(right[right != 0] ** 2)) if np.any(right != 0) else 0
248
+ if l_rms > 0 and r_rms > 0:
249
+ avg = (l_rms + r_rms) / 2
250
+ stereo[:, 0] *= (avg / l_rms)
251
+ stereo[:, 1] *= (avg / r_rms)
252
+ out = stereo.flatten().astype(np.int32 if audio_segment.sample_width == 3 else np.int16)
253
+ if len(out) % 2 != 0:
254
+ out = out[:-1]
255
+ return AudioSegment(
256
+ out.tobytes(),
257
+ frame_rate=sample_rate,
258
+ sample_width=audio_segment.sample_width,
259
+ channels=2
260
+ )
261
+ except Exception as e:
262
+ logger.error(f"balance_stereo failed: {e}")
263
+ return audio_segment
264
+
265
+ def apply_noise_gate(audio_segment: AudioSegment, threshold_db=-80, sample_rate=48000) -> AudioSegment:
266
+ try:
267
+ audio_segment = ensure_stereo(audio_segment, sample_rate, audio_segment.sample_width)
268
+ samples = np.array(audio_segment.get_array_of_samples(), dtype=np.float32)
269
+ if audio_segment.channels != 2:
270
+ return audio_segment
271
+ stereo = samples.reshape(-1, 2)
272
+ for _ in range(2):
273
+ db = 20 * np.log10(np.abs(stereo) + 1e-10)
274
+ mask = db > threshold_db
275
+ stereo = stereo * mask
276
+ out = stereo.flatten().astype(np.int32 if audio_segment.sample_width == 3 else np.int16)
277
+ if len(out) % 2 != 0:
278
+ out = out[:-1]
279
+ return AudioSegment(
280
+ out.tobytes(),
281
+ frame_rate=sample_rate,
282
+ sample_width=audio_segment.sample_width,
283
+ channels=2
284
+ )
285
+ except Exception as e:
286
+ logger.error(f"apply_noise_gate failed: {e}")
287
+ return audio_segment
288
+
289
+ def apply_eq(segment: AudioSegment, sample_rate=48000) -> AudioSegment:
290
+ try:
291
+ segment = ensure_stereo(segment, sample_rate, segment.sample_width)
292
+ segment = segment.high_pass_filter(20)
293
+ segment = segment.low_pass_filter(8000)
294
+ segment = segment - 3
295
+ segment = segment - 3
296
+ segment = segment - 10
297
+ return segment
298
+ except Exception as e:
299
+ logger.error(f"apply_eq failed: {e}")
300
+ return segment
301
+
302
+ def apply_fade(segment: AudioSegment, fade_in_duration=500, fade_out_duration=500) -> AudioSegment:
303
+ try:
304
+ segment = ensure_stereo(segment, segment.frame_rate, segment.sample_width)
305
+ segment = segment.fade_in(fade_in_duration).fade_out(fade_out_duration)
306
+ return segment
307
+ except Exception as e:
308
+ logger.error(f"apply_fade failed: {e}")
309
+ return segment
310
+
311
+ # ======================================================================================
312
+ # PROMPTS
313
+ # ======================================================================================
314
+
315
+ def set_red_hot_chili_peppers_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, chunk_num):
316
+ try:
317
+ bpm_range = (90, 130)
318
+ bpm = random.randint(*bpm_range) if bpm == 120 else bpm
319
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ", standard rock drums with funk fills"
320
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
321
+ bass = f", {bass_style} bass" if bass_style != "none" else ", funky slap bass"
322
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ", energetic guitar riffs"
323
+ base = f"Instrumental alternative rock by Red Hot Chili Peppers{guitar}{bass}{drum}{synth}, funk-rock energy at {bpm} BPM"
324
+ if chunk_num == 1:
325
+ return base + ", dynamic intro and expressive verse."
326
+ return base + ", powerful chorus and energetic outro."
327
+ except Exception:
328
+ return ""
329
+
330
+ def set_nirvana_grunge_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
331
+ try:
332
+ bpm_range = (100, 130)
333
+ bpm = random.randint(*bpm_range) if bpm == 120 else bpm
334
+ drum = f", {drum_beat} drums, punk energy" if drum_beat != "none" else ", standard rock drums, punk energy"
335
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
336
+ chosen_bass = random.choice(['deep bass', 'melodic bass']) if bass_style == "none" else bass_style
337
+ bass = f", {chosen_bass}"
338
+ chosen_guitar = random.choice(['distorted guitar', 'clean guitar']) if guitar_style == "none" else guitar_style
339
+ guitar = f", {chosen_guitar}"
340
+ chosen_rhythm = random.choice(['steady steps', 'dynamic shifts']) if rhythmic_steps == "none" else rhythmic_steps
341
+ rhythm = f", {chosen_rhythm}"
342
+ return f"Instrumental grunge by Nirvana{guitar}{bass}{drum}{synth}, raw lo-fi production{rhythm} at {bpm} BPM."
343
+ except Exception:
344
+ return ""
345
+
346
+ def set_pearl_jam_grunge_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
347
+ try:
348
+ bpm_range = (100, 140)
349
+ bpm = random.randint(*bpm_range) if bpm == 120 else bpm
350
+ drum = f", {drum_beat} drums, driving rhythm" if drum_beat != "none" else ", standard rock drums, driving rhythm"
351
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
352
+ bass = f", {bass_style}, emotional tone" if bass_style != "none" else ", melodic bass, emotional tone"
353
+ chosen_guitar = random.choice(['clean guitar', 'distorted guitar']) if guitar_style == "none" else guitar_style
354
+ guitar = f", {chosen_guitar}, soulful leads"
355
+ chosen_rhythm = random.choice(['steady steps', 'syncopated steps']) if rhythmic_steps == "none" else rhythmic_steps
356
+ rhythm = f", {chosen_rhythm}"
357
+ return f"Instrumental grunge by Pearl Jam{guitar}{bass}{drum}{synth}, classic rock influences{rhythm} at {bpm} BPM."
358
+ except Exception:
359
+ return ""
360
+
361
+ def set_soundgarden_grunge_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
362
+ try:
363
+ bpm_range = (90, 140)
364
+ bpm = random.randint(*bpm_range) if bpm == 120 else bpm
365
+ drum = f", {drum_beat} drums, heavy rhythm" if drum_beat != "none" else ", standard rock drums, heavy rhythm"
366
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
367
+ bass = f", {bass_style}, sludgy tone" if bass_style != "none" else ", deep bass, sludgy tone"
368
+ guitar = f", {guitar_style}, downtuned riffs, psychedelic vibe" if guitar_style != "none" else ", distorted guitar, downtuned riffs, psychedelic vibe"
369
+ rhythm = f", {rhythmic_steps}" if rhythmic_steps != "none" else ", complex steps"
370
+ return f"Instrumental grunge with heavy metal influences by Soundgarden{guitar}{bass}{drum}{synth}{rhythm} at {bpm} BPM."
371
+ except Exception:
372
+ return ""
373
+
374
+ def set_foo_fighters_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
375
+ try:
376
+ bpm_range = (110, 150)
377
+ bpm = random.randint(*bpm_range) if bpm == 120 else bpm
378
+ drum = f", {drum_beat} drums, powerful drive" if drum_beat != "none" else ", standard rock drums, powerful drive"
379
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
380
+ bass = f", {bass_style}, supportive tone" if bass_style != "none" else ", melodic bass, supportive tone"
381
+ chosen_guitar = random.choice(['distorted guitar', 'clean guitar']) if guitar_style == "none" else guitar_style
382
+ guitar = f", {chosen_guitar}, anthemic quality"
383
+ chosen_rhythm = random.choice(['steady steps', 'driving rhythm']) if rhythmic_steps == "none" else rhythmic_steps
384
+ rhythm = f", {chosen_rhythm}"
385
+ return f"Instrumental alternative rock with post-grunge influences by Foo Fighters{guitar}, stadium-ready hooks{bass}{drum}{synth}{rhythm} at {bpm} BPM."
386
+ except Exception:
387
+ return ""
388
+
389
+ def set_classic_rock_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
390
+ try:
391
+ bpm_range = (120, 180)
392
+ bpm = random.randint(*bpm_range) if bpm == 120 else bpm
393
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ", double bass drums"
394
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
395
+ bass = f", {bass_style}" if bass_style != "none" else ", aggressive bass"
396
+ guitar = f", {guitar_style}, blazing fast riffs" if guitar_style != "none" else ", distorted guitar, blazing fast riffs"
397
+ rhythm = f", {rhythmic_steps}" if rhythmic_steps != "none" else ", complex steps"
398
+ return f"Instrumental thrash metal by Metallica{guitar}{bass}{drum}{synth}{rhythm} at {bpm} BPM."
399
+ except Exception:
400
+ return ""
401
+
402
+ def set_smashing_pumpkins_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
403
+ try:
404
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ""
405
+ synth = f", {synthesizer}" if synthesizer != "none" else ", lush synths"
406
+ bass = f", {bass_style} bass" if bass_style != "none" else ""
407
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ", dreamy guitar"
408
+ return f"Instrumental alternative rock by Smashing Pumpkins{guitar}{synth}{drum}{bass} at {bpm} BPM."
409
+ except Exception:
410
+ return ""
411
+
412
+ def set_radiohead_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
413
+ try:
414
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ""
415
+ synth = f", {synthesizer}" if synthesizer != "none" else ", atmospheric synths"
416
+ bass = f", {bass_style} bass" if bass_style != "none" else ", hypnotic bass"
417
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ""
418
+ return f"Instrumental experimental rock by Radiohead{synth}{bass}{drum}{guitar} at {bpm} BPM."
419
+ except Exception:
420
+ return ""
421
+
422
+ def set_alternative_rock_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
423
+ try:
424
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ""
425
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
426
+ bass = f", {bass_style} bass" if bass_style != "none" else ", melodic bass"
427
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ", distorted guitar"
428
+ return f"Instrumental alternative rock by Pixies{guitar}{bass}{drum}{synth} at {bpm} BPM."
429
+ except Exception:
430
+ return ""
431
+
432
+ def set_post_punk_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
433
+ try:
434
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ", precise drums"
435
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
436
+ bass = f", {bass_style} bass" if bass_style != "none" else ", driving bass"
437
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ", jangly guitar"
438
+ return f"Instrumental post-punk by Joy Division{guitar}{bass}{drum}{synth} at {bpm} BPM."
439
+ except Exception:
440
+ return ""
441
+
442
+ def set_indie_rock_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
443
+ try:
444
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ""
445
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
446
+ bass = f", {bass_style} bass" if bass_style != "none" else ", groovy bass"
447
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ", jangly guitar"
448
+ return f"Instrumental indie rock by Arctic Monkeys{guitar}{bass}{drum}{synth} at {bpm} BPM."
449
+ except Exception:
450
+ return ""
451
+
452
+ def set_funk_rock_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
453
+ try:
454
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ", heavy drums"
455
+ synth = f", {synthesizer}" if synthesizer != "none" else ""
456
+ bass = f", {bass_style} bass" if bass_style != "none" else ", slap bass"
457
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ", funky guitar"
458
+ return f"Instrumental funk rock by Rage Against the Machine{guitar}{bass}{drum}{synth} at {bpm} BPM."
459
+ except Exception:
460
+ return ""
461
+
462
+ def set_detroit_techno_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
463
+ try:
464
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ", four-on-the-floor drums"
465
+ synth = f", {synthesizer}" if synthesizer != "none" else ", pulsing synths"
466
+ bass = f", {bass_style} bass" if bass_style != "none" else ", driving bass"
467
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ""
468
+ return f"Instrumental Detroit techno by Juan Atkins{synth}{bass}{drum}{guitar} at {bpm} BPM."
469
+ except Exception:
470
+ return ""
471
+
472
+ def set_deep_house_prompt(bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style):
473
+ try:
474
+ drum = f", {drum_beat} drums" if drum_beat != "none" else ", steady kick drums"
475
+ synth = f", {synthesizer}" if synthesizer != "none" else ", warm synths"
476
+ bass = f", {bass_style} bass" if bass_style != "none" else ", deep bass"
477
+ guitar = f", {guitar_style} guitar" if guitar_style != "none" else ""
478
+ return f"Instrumental deep house by Larry Heard{synth}{bass}{drum}{guitar} at {bpm} BPM."
479
+ except Exception:
480
+ return ""
481
+
482
+ PRESETS = {
483
+ "default": {"cfg_scale": 5.8, "top_k": 250, "top_p": 0.95, "temperature": 0.90},
484
+ "rock": {"cfg_scale": 5.8, "top_k": 250, "top_p": 0.95, "temperature": 0.90},
485
+ "techno": {"cfg_scale": 5.2, "top_k": 300, "top_p": 0.96, "temperature": 0.95},
486
+ "grunge": {"cfg_scale": 6.2, "top_k": 220, "top_p": 0.94, "temperature": 0.90},
487
+ "indie": {"cfg_scale": 5.5, "top_k": 240, "top_p": 0.95, "temperature": 0.92},
488
+ "funk_rock": {"cfg_scale": 5.8, "top_k": 260, "top_p": 0.96, "temperature": 0.94},
489
+ }
490
+
491
+ # ======================================================================================
492
+ # MODEL LOAD
493
+ # ======================================================================================
494
+
495
+ try:
496
+ from audiocraft.models import MusicGen
497
+ except Exception as e:
498
+ logger.error("audiocraft is required. pip install audiocraft")
499
+ raise
500
+
501
+ def load_model():
502
+ free_vram = check_vram()
503
+ if free_vram is not None and free_vram < 5000:
504
+ logger.warning("Low free VRAM; consider closing other apps.")
505
+ clean_memory()
506
+ local_model_path = "./models/musicgen-large"
507
+ if not os.path.exists(local_model_path):
508
+ logger.error(f"Model path missing: {local_model_path}")
509
+ sys.exit(1)
510
+ logger.info("Loading MusicGen (large)...")
511
+ with autocast(dtype=torch.float16):
512
+ model = MusicGen.get_pretrained(local_model_path, device=DEVICE)
513
+ # base params get overridden per-call
514
+ model.set_generation_params(duration=30, two_step_cfg=False)
515
+ logger.info("MusicGen loaded.")
516
+ return model
517
+
518
+ musicgen_model = load_model()
519
+
520
+ # ======================================================================================
521
+ # GENERATION PIPELINE (30s CHUNKING, SEAMLESS MERGE)
522
+ # ======================================================================================
523
+
524
+ def get_latest_log() -> str:
525
+ try:
526
+ files = sorted(Path(LOG_DIR).glob("musicgen_log_*.log"), key=os.path.getmtime, reverse=True)
527
+ if not files:
528
+ return "No log files found."
529
+ return files[0].read_text()
530
+ except Exception as e:
531
+ return f"Error reading log: {e}"
532
+
533
+ def set_bitrate_128(): return "128k"
534
+ def set_bitrate_192(): return "192k"
535
+ def set_bitrate_320(): return "320k"
536
+ def set_sample_rate_22050(): return "22050"
537
+ def set_sample_rate_44100(): return "44100"
538
+ def set_sample_rate_48000(): return "48000"
539
+ def set_bit_depth_16(): return "16"
540
+ def set_bit_depth_24(): return "24"
541
+
542
+ def generate_music_wrapper(*args):
543
+ try:
544
+ return generate_music(*args)
545
+ finally:
546
+ clean_memory()
547
+
548
+ def _export_torch_to_segment(audio_tensor: torch.Tensor, sample_rate: int, bit_depth_int: int) -> Optional[AudioSegment]:
549
+ """Helper: save torch stereo float32 to WAV -> load with pydub as segment."""
550
+ temp = f"temp_audio_{int(time.time()*1000)}.wav"
551
+ try:
552
+ torchaudio.save(temp, audio_tensor, sample_rate, bits_per_sample=bit_depth_int)
553
+ with open(temp, "rb") as f:
554
+ mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
555
+ seg = AudioSegment.from_wav(temp)
556
+ mm.close()
557
+ return seg
558
+ except Exception as e:
559
+ logger.error(f"_export_torch_to_segment failed: {e}")
560
+ logger.error(traceback.format_exc())
561
+ return None
562
+ finally:
563
+ try:
564
+ if os.path.exists(temp):
565
+ os.remove(temp)
566
+ except OSError:
567
+ pass
568
+
569
+ def _crossfade_segments(seg_a: AudioSegment, seg_b: AudioSegment, overlap_ms: int, sample_rate: int, bit_depth_int: int) -> AudioSegment:
570
+ """Blend tail of seg_a with head of seg_b using hann window for seamless merge."""
571
+ try:
572
+ seg_a = ensure_stereo(seg_a, sample_rate, seg_a.sample_width)
573
+ seg_b = ensure_stereo(seg_b, sample_rate, seg_b.sample_width)
574
+ if overlap_ms <= 0 or len(seg_a) < overlap_ms or len(seg_b) < overlap_ms:
575
+ return seg_a + seg_b
576
+
577
+ # export overlaps
578
+ prev_wav = f"tmp_prev_{int(time.time()*1000)}.wav"
579
+ curr_wav = f"tmp_curr_{int(time.time()*1000)}.wav"
580
+ try:
581
+ seg_a[-overlap_ms:].export(prev_wav, format="wav")
582
+ seg_b[:overlap_ms].export(curr_wav, format="wav")
583
+ a_audio, sr_a = torchaudio.load(prev_wav)
584
+ b_audio, sr_b = torchaudio.load(curr_wav)
585
+ if sr_a != sample_rate:
586
+ a_audio = torchaudio.functional.resample(a_audio, sr_a, sample_rate, lowpass_filter_width=64)
587
+ if sr_b != sample_rate:
588
+ b_audio = torchaudio.functional.resample(b_audio, sr_b, sample_rate, lowpass_filter_width=64)
589
+ n = min(a_audio.shape[1], b_audio.shape[1])
590
+ n = n - (n % 2)
591
+ if n <= 0:
592
+ return seg_a + seg_b
593
+ a = a_audio[:, :n]
594
+ b = b_audio[:, :n]
595
+ hann = torch.hann_window(n, periodic=False)
596
+ fade_in = hann
597
+ fade_out = hann.flip(0)
598
+ blended = (a * fade_out + b * fade_in).to(torch.float32)
599
+ blended = torch.clamp(blended, -1.0, 1.0)
600
+
601
+ # scale to PCM and save
602
+ scale = (2**23 if bit_depth_int == 24 else 32767)
603
+ blended_i = (blended * scale).to(torch.int32 if bit_depth_int == 24 else torch.int16)
604
+ temp_x = f"tmp_cross_{int(time.time()*1000)}.wav"
605
+ torchaudio.save(temp_x, blended_i, sample_rate, bits_per_sample=bit_depth_int)
606
+ blended_seg = AudioSegment.from_wav(temp_x)
607
+ blended_seg = ensure_stereo(blended_seg, sample_rate, blended_seg.sample_width)
608
+
609
+ # combine
610
+ result = seg_a[:-overlap_ms] + blended_seg + seg_b[overlap_ms:]
611
+ try:
612
+ if os.path.exists(temp_x):
613
+ os.remove(temp_x)
614
+ except OSError:
615
+ pass
616
+ return result
617
+ finally:
618
+ for p in [prev_wav, curr_wav]:
619
+ try:
620
+ if os.path.exists(p):
621
+ os.remove(p)
622
+ except OSError:
623
+ pass
624
+ except Exception as e:
625
+ logger.error(f"_crossfade_segments failed: {e}")
626
+ return seg_a + seg_b
627
+
628
+ def generate_music(
629
+ instrumental_prompt: str,
630
+ cfg_scale: float,
631
+ top_k: int,
632
+ top_p: float,
633
+ temperature: float,
634
+ total_duration: int,
635
+ bpm: int,
636
+ drum_beat: str,
637
+ synthesizer: str,
638
+ rhythmic_steps: str,
639
+ bass_style: str,
640
+ guitar_style: str,
641
+ target_volume: float,
642
+ preset: str,
643
+ max_steps: str, # kept for UI parity
644
+ vram_status_text: str,
645
+ bitrate: str,
646
+ output_sample_rate: str,
647
+ bit_depth: str
648
+ ) -> Tuple[Optional[str], str, str]:
649
+ global musicgen_model
650
+
651
+ if not instrumental_prompt or not instrumental_prompt.strip():
652
+ return None, "⚠️ Please enter a valid instrumental prompt!", vram_status_text
653
+
654
+ try:
655
+ # Apply preset if not default
656
+ if preset != "default":
657
+ p = PRESETS.get(preset, PRESETS["default"])
658
+ cfg_scale, top_k, top_p, temperature = p["cfg_scale"], p["top_k"], p["top_p"], p["temperature"]
659
+ logger.info(f"Preset '{preset}' applied: cfg={cfg_scale} top_k={top_k} top_p={top_p} temp={temperature}")
660
+
661
+ # Validate numerics
662
+ try:
663
+ output_sr_int = int(output_sample_rate)
664
+ except:
665
+ return None, "❌ Invalid output sampling rate; choose 22050/44100/48000", vram_status_text
666
+ try:
667
+ bit_depth_int = int(bit_depth)
668
+ sample_width = 3 if bit_depth_int == 24 else 2
669
+ except:
670
+ return None, "❌ Invalid bit depth; choose 16 or 24", vram_status_text
671
+
672
+ if not check_disk_space():
673
+ return None, "⚠️ Low disk space (<1GB).", vram_status_text
674
+
675
+ # Chunking: EXACT 30s per chunk (unify stepping -> always 30s). Two chunks => full 60s song.
676
+ CHUNK_SEC = 30
677
+ total_duration = max(30, min(int(total_duration), 120))
678
+ num_chunks = math.ceil(total_duration / CHUNK_SEC)
679
+
680
+ # Internal processing rate (resample to this for DSP)
681
+ PROCESS_SR = 48000
682
+ OVERLAP_SEC = 0.20 # 200ms crossfade/prompt tail
683
+ channels = 2
684
+
685
+ # Seed & params
686
+ seed = random.randint(0, 2**31 - 1)
687
+ random.seed(seed)
688
+ torch.manual_seed(seed)
689
+ np.random.seed(seed)
690
+ torch.cuda.manual_seed_all(seed)
691
+
692
+ musicgen_model.set_generation_params(
693
+ duration=CHUNK_SEC,
694
+ use_sampling=True,
695
+ top_k=int(top_k),
696
+ top_p=float(top_p),
697
+ temperature=float(temperature),
698
+ cfg_coef=float(cfg_scale),
699
+ two_step_cfg=False,
700
+ )
701
+
702
+ vram_status_text = f"Start VRAM: {torch.cuda.memory_allocated() / 1024**2:.2f} MB"
703
+
704
+ segments: List[AudioSegment] = []
705
+ start_time = time.time()
706
+
707
+ for idx in range(num_chunks):
708
+ chunk_idx = idx + 1
709
+ dur = CHUNK_SEC if (idx < num_chunks - 1) else (total_duration - CHUNK_SEC * (num_chunks - 1) or CHUNK_SEC)
710
+ logger.info(f"Generating chunk {chunk_idx}/{num_chunks} ({dur}s)")
711
+
712
+ # Prompt per chunk (variable for RHCP only)
713
+ if "Red Hot Chili Peppers" in instrumental_prompt:
714
+ prompt_text = set_red_hot_chili_peppers_prompt(
715
+ bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, chunk_idx
716
+ )
717
+ else:
718
+ prompt_text = instrumental_prompt
719
+
720
+ try:
721
+ with torch.no_grad():
722
+ with autocast(dtype=torch.float16):
723
+ clean_memory()
724
+ if idx == 0:
725
+ audio = musicgen_model.generate([prompt_text], progress=True)[0].cpu()
726
+ else:
727
+ # Use tail of previous segment as continuation prompt
728
+ prev_seg = segments[-1]
729
+ prev_seg = apply_noise_gate(prev_seg, threshold_db=-80, sample_rate=PROCESS_SR)
730
+ prev_seg = balance_stereo(prev_seg, noise_threshold=-40, sample_rate=PROCESS_SR)
731
+ temp_prev = f"prev_{int(time.time()*1000)}.wav"
732
+ try:
733
+ prev_seg.export(temp_prev, format="wav")
734
+ prev_audio, prev_sr = torchaudio.load(temp_prev)
735
+ if prev_sr != PROCESS_SR:
736
+ prev_audio = torchaudio.functional.resample(prev_audio, prev_sr, PROCESS_SR, lowpass_filter_width=64)
737
+ if prev_audio.shape[0] != 2:
738
+ prev_audio = prev_audio.repeat(2, 1)[:, :prev_audio.shape[1]]
739
+ prev_audio = prev_audio.to(DEVICE)
740
+ tail = prev_audio[:, -int(PROCESS_SR * OVERLAP_SEC):]
741
+
742
+ audio = musicgen_model.generate_continuation(
743
+ prompt=tail,
744
+ prompt_sample_rate=PROCESS_SR,
745
+ descriptions=[prompt_text],
746
+ progress=True
747
+ )[0].cpu()
748
+ del prev_audio, tail
749
+ finally:
750
+ try:
751
+ if os.path.exists(temp_prev):
752
+ os.remove(temp_prev)
753
+ except OSError:
754
+ pass
755
+ clean_memory()
756
+ except Exception as e:
757
+ logger.error(f"Chunk {chunk_idx} generation failed: {e}")
758
+ logger.error(traceback.format_exc())
759
+ return None, f"❌ Failed to generate chunk {chunk_idx}: {e}", vram_status_text
760
+
761
+ try:
762
+ # Ensure stereo & resample to PROCESS_SR for DSP
763
+ if audio.shape[0] != 2:
764
+ audio = audio.repeat(2, 1)[:, :audio.shape[1]]
765
+ audio = audio.to(dtype=torch.float32)
766
+ audio = torchaudio.functional.resample(audio, 32000, PROCESS_SR, lowpass_filter_width=64)
767
+ seg = _export_torch_to_segment(audio, PROCESS_SR, bit_depth_int)
768
+ if seg is None:
769
+ return None, f"❌ Failed to convert audio for chunk {chunk_idx}", vram_status_text
770
+ seg = ensure_stereo(seg, PROCESS_SR, sample_width)
771
+ seg = seg - 15
772
+ seg = apply_noise_gate(seg, threshold_db=-80, sample_rate=PROCESS_SR)
773
+ seg = balance_stereo(seg, noise_threshold=-40, sample_rate=PROCESS_SR)
774
+ seg = rms_normalize(seg, target_rms_db=target_volume, peak_limit_db=-3.0, sample_rate=PROCESS_SR)
775
+ seg = apply_eq(seg, sample_rate=PROCESS_SR)
776
+ # Trim exactly to 'dur' seconds for last chunk
777
+ seg = seg[:dur * 1000]
778
+ segments.append(seg)
779
+ del audio
780
+ clean_memory()
781
+ vram_status_text = f"VRAM after chunk {chunk_idx}: {torch.cuda.memory_allocated() / 1024**2:.2f} MB"
782
+ except Exception as e:
783
+ logger.error(f"Post-processing failed (chunk {chunk_idx}): {e}")
784
+ logger.error(traceback.format_exc())
785
+ return None, f"❌ Failed to process chunk {chunk_idx}: {e}", vram_status_text
786
+
787
+ if not segments:
788
+ return None, "❌ No audio generated.", vram_status_text
789
+
790
+ # Seamless join with crossfades
791
+ logger.info("Combining chunks...")
792
+ final_seg = segments[0]
793
+ overlap_ms = int(OVERLAP_SEC * 1000)
794
+ for i in range(1, len(segments)):
795
+ final_seg = _crossfade_segments(final_seg, segments[i], overlap_ms, PROCESS_SR, bit_depth_int)
796
+
797
+ # Final length clamp
798
+ final_seg = final_seg[:total_duration * 1000]
799
+
800
+ # Final polish
801
+ final_seg = apply_noise_gate(final_seg, threshold_db=-80, sample_rate=PROCESS_SR)
802
+ final_seg = balance_stereo(final_seg, noise_threshold=-40, sample_rate=PROCESS_SR)
803
+ final_seg = rms_normalize(final_seg, target_rms_db=target_volume, peak_limit_db=-3.0, sample_rate=PROCESS_SR)
804
+ final_seg = apply_eq(final_seg, sample_rate=PROCESS_SR)
805
+ final_seg = apply_fade(final_seg, 500, 800)
806
+ final_seg = final_seg - 10
807
+ final_seg = final_seg.set_frame_rate(output_sr_int)
808
+
809
+ # Export MP3
810
+ mp3_path = f"ghostai_music_{int(time.time())}.mp3"
811
+ try:
812
+ clean_memory()
813
+ final_seg.export(mp3_path, format="mp3", bitrate=bitrate, tags={"title": "GhostAI Instrumental", "artist": "GhostAI"})
814
+ except Exception as e:
815
+ logger.error(f"MP3 export failed ({bitrate}): {e}")
816
+ fb = f"ghostai_music_fallback_{int(time.time())}.mp3"
817
+ try:
818
+ final_seg.export(fb, format="mp3", bitrate="128k")
819
+ mp3_path = fb
820
+ except Exception as ee:
821
+ return None, f"❌ Failed to export MP3: {ee}", vram_status_text
822
+
823
+ elapsed = time.time() - start_time
824
+ vram_status_text = f"Final VRAM: {torch.cuda.memory_allocated() / 1024**2:.2f} MB"
825
+ logger.info(f"Done in {elapsed:.2f}s -> {mp3_path}")
826
+ return mp3_path, "✅ Done! 30s chunking unified seamlessly. Check output loudness/quality.", vram_status_text
827
+
828
+ except Exception as e:
829
+ logger.error(f"Generation failed: {e}")
830
+ logger.error(traceback.format_exc())
831
+ return None, f"❌ Generation failed: {e}", vram_status_text
832
+ finally:
833
+ clean_memory()
834
+
835
+ def clear_inputs():
836
+ s = DEFAULT_SETTINGS.copy()
837
+ return (
838
+ s["instrumental_prompt"], s["cfg_scale"], s["top_k"], s["top_p"], s["temperature"],
839
+ s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
840
+ s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
841
+ s["bitrate"], s["output_sample_rate"], s["bit_depth"]
842
+ )
843
+
844
+ # ======================================================================================
845
+ # SERVER STATUS (BUSY/IDLE) & RENDER API
846
+ # ======================================================================================
847
+
848
+ BUSY_LOCK = threading.Lock()
849
+ BUSY_FLAG = False
850
+ BUSY_FILE = "/tmp/musicgen_busy.lock"
851
+ CURRENT_JOB: Dict[str, Any] = {"id": None, "start": None}
852
+
853
+ def set_busy(val: bool, job_id: Optional[str] = None):
854
+ global BUSY_FLAG, CURRENT_JOB
855
+ with BUSY_LOCK:
856
+ BUSY_FLAG = val
857
+ if val:
858
+ CURRENT_JOB["id"] = job_id or f"job_{int(time.time())}"
859
+ CURRENT_JOB["start"] = time.time()
860
+ try:
861
+ Path(BUSY_FILE).write_text(CURRENT_JOB["id"])
862
+ except Exception:
863
+ pass
864
+ else:
865
+ CURRENT_JOB["id"] = None
866
+ CURRENT_JOB["start"] = None
867
+ try:
868
+ if os.path.exists(BUSY_FILE):
869
+ os.remove(BUSY_FILE)
870
+ except Exception:
871
+ pass
872
+
873
+ def is_busy() -> bool:
874
+ with BUSY_LOCK:
875
+ return BUSY_FLAG
876
+
877
+ def job_elapsed() -> float:
878
+ with BUSY_LOCK:
879
+ if CURRENT_JOB["start"] is None:
880
+ return 0.0
881
+ return time.time() - CURRENT_JOB["start"]
882
+
883
+ class RenderRequest(BaseModel):
884
+ instrumental_prompt: str
885
+ cfg_scale: Optional[float] = None
886
+ top_k: Optional[int] = None
887
+ top_p: Optional[float] = None
888
+ temperature: Optional[float] = None
889
+ total_duration: Optional[int] = None
890
+ bpm: Optional[int] = None
891
+ drum_beat: Optional[str] = None
892
+ synthesizer: Optional[str] = None
893
+ rhythmic_steps: Optional[str] = None
894
+ bass_style: Optional[str] = None
895
+ guitar_style: Optional[str] = None
896
+ target_volume: Optional[float] = None
897
+ preset: Optional[str] = None
898
+ max_steps: Optional[int] = None
899
+ bitrate: Optional[str] = None
900
+ output_sample_rate: Optional[str] = None
901
+ bit_depth: Optional[str] = None
902
+
903
+ class SettingsUpdate(BaseModel):
904
+ settings: Dict[str, Any]
905
+
906
+ fastapp = FastAPI(title="GhostAI Music Server", version="1.0")
907
+ fastapp.add_middleware(
908
+ CORSMiddleware,
909
+ allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],
910
+ )
911
+
912
+ @fastapp.get("/health")
913
+ def health():
914
+ return {"ok": True, "ts": int(time.time())}
915
+
916
+ @fastapp.get("/status")
917
+ def status():
918
+ busy = is_busy()
919
+ return {
920
+ "busy": busy,
921
+ "job_id": CURRENT_JOB["id"],
922
+ "since": CURRENT_JOB["start"],
923
+ "elapsed": job_elapsed(),
924
+ "lockfile": os.path.exists(BUSY_FILE)
925
+ }
926
+
927
+ @fastapp.get("/config")
928
+ def get_config():
929
+ return {"defaults": CURRENT_SETTINGS}
930
+
931
+ @fastapp.post("/settings")
932
+ def set_settings(payload: SettingsUpdate):
933
+ try:
934
+ s = CURRENT_SETTINGS.copy()
935
+ s.update(payload.settings or {})
936
+ save_settings_to_file(s)
937
+ for k, v in s.items():
938
+ CURRENT_SETTINGS[k] = v
939
+ return {"ok": True, "saved": s}
940
+ except Exception as e:
941
+ raise HTTPException(status_code=400, detail=str(e))
942
+
943
+ @fastapp.post("/render")
944
+ def render(req: RenderRequest):
945
+ if is_busy():
946
+ raise HTTPException(status_code=409, detail="Server busy")
947
+ job_id = f"render_{int(time.time())}"
948
+ set_busy(True, job_id)
949
+ try:
950
+ s = CURRENT_SETTINGS.copy()
951
+ # apply overrides
952
+ for k, v in req.dict().items():
953
+ if v is not None:
954
+ s[k] = v
955
+ mp3, msg, vram = generate_music(
956
+ s.get("instrumental_prompt", req.instrumental_prompt),
957
+ float(s.get("cfg_scale", DEFAULT_SETTINGS["cfg_scale"])),
958
+ int(s.get("top_k", DEFAULT_SETTINGS["top_k"])),
959
+ float(s.get("top_p", DEFAULT_SETTINGS["top_p"])),
960
+ float(s.get("temperature", DEFAULT_SETTINGS["temperature"])),
961
+ int(s.get("total_duration", DEFAULT_SETTINGS["total_duration"])),
962
+ int(s.get("bpm", DEFAULT_SETTINGS["bpm"])),
963
+ str(s.get("drum_beat", DEFAULT_SETTINGS["drum_beat"])),
964
+ str(s.get("synthesizer", DEFAULT_SETTINGS["synthesizer"])),
965
+ str(s.get("rhythmic_steps", DEFAULT_SETTINGS["rhythmic_steps"])),
966
+ str(s.get("bass_style", DEFAULT_SETTINGS["bass_style"])),
967
+ str(s.get("guitar_style", DEFAULT_SETTINGS["guitar_style"])),
968
+ float(s.get("target_volume", DEFAULT_SETTINGS["target_volume"])),
969
+ str(s.get("preset", DEFAULT_SETTINGS["preset"])),
970
+ str(s.get("max_steps", DEFAULT_SETTINGS["max_steps"])),
971
+ "",
972
+ str(s.get("bitrate", DEFAULT_SETTINGS["bitrate"])),
973
+ str(s.get("output_sample_rate", DEFAULT_SETTINGS["output_sample_rate"])),
974
+ str(s.get("bit_depth", DEFAULT_SETTINGS["bit_depth"]))
975
+ )
976
+ if not mp3:
977
+ raise HTTPException(status_code=500, detail=msg)
978
+ return {"ok": True, "job_id": job_id, "path": mp3, "status": msg, "vram": vram}
979
+ finally:
980
+ set_busy(False, None)
981
+
982
+ def _start_fastapi():
983
+ uvicorn.run(fastapp, host="0.0.0.0", port=8555, log_level="info")
984
+
985
+ api_thread = threading.Thread(target=_start_fastapi, daemon=True)
986
+ api_thread.start()
987
+ logger.info("FastAPI server started on http://0.0.0.0:8555")
988
+
989
+ # ======================================================================================
990
+ # GRADIO UI (HIGH CONTRAST / WHITE TEXT)
991
+ # ======================================================================================
992
+
993
+ CSS = """
994
+ :root { color-scheme: dark; }
995
+ body, .gradio-container, .block, .tabs, .panel, .form, .wrap { background: #0B0B0D !important; color: #FFFFFF !important; }
996
+ * { color: #FFFFFF !important; }
997
+ label, p, span, h1, h2, h3, h4, h5, h6 { color: #FFFFFF !important; }
998
+ input, textarea, select { background: #15151A !important; color: #FFFFFF !important; border: 1px solid #2B2B33 !important; }
999
+ button { background: #1F6FEB !important; color: #FFFFFF !important; border: 2px solid transparent !important; border-radius: 8px !important; padding: 10px 16px !important; font-weight: 700 !important; }
1000
+ button:hover { background: #2D7BFF !important; }
1001
+ button:focus { outline: 3px solid #00C853 !important; }
1002
+ .slider > input { accent-color: #FFD600 !important; }
1003
+ .group-container { border: 1px solid #2B2B33; border-radius: 10px; padding: 16px; }
1004
+ .header { text-align:center; padding: 12px 16px; border-bottom: 2px solid #00C853; }
1005
+ .header h1 { font-size: 28px; margin: 6px 0 0 0; }
1006
+ .header .logo { font-size: 44px; }
1007
+ """
1008
+
1009
+ loaded = CURRENT_SETTINGS
1010
+
1011
+ logger.info("Building Gradio UI...")
1012
+ with gr.Blocks(css=CSS, analytics_enabled=False, title="GhostAI Music Generator") as demo:
1013
+ gr.Markdown(f"""
1014
+ <div class="header" role="banner" aria-label="GhostAI Music Generator">
1015
+ <div class="logo">👻</div>
1016
+ <h1>GhostAI Music Generator</h1>
1017
+ <p>30s chunking, seamless joins, 1-minute ready, API status & settings save</p>
1018
+ </div>
1019
+ """)
1020
+
1021
+ with gr.Column(elem_classes="input-container"):
1022
+ gr.Markdown("### Prompt")
1023
+ instrumental_prompt = gr.Textbox(
1024
+ label="Instrumental Prompt",
1025
+ placeholder="Type your instrumental prompt or use genre buttons below",
1026
+ lines=4,
1027
+ value=loaded.get("instrumental_prompt", ""),
1028
+ )
1029
+ with gr.Row():
1030
+ rhcp_btn = gr.Button("Red Hot Chili Peppers 🌶️")
1031
+ nirvana_btn = gr.Button("Nirvana 🎸")
1032
+ pearl_jam_btn = gr.Button("Pearl Jam 🦪")
1033
+ soundgarden_btn = gr.Button("Soundgarden 🌑")
1034
+ foo_fighters_btn = gr.Button("Foo Fighters 🤘")
1035
+ with gr.Row():
1036
+ smashing_pumpkins_btn = gr.Button("Smashing Pumpkins 🎃")
1037
+ radiohead_btn = gr.Button("Radiohead 🧠")
1038
+ classic_rock_btn = gr.Button("Metallica Heavy Metal 🎸")
1039
+ alternative_rock_btn = gr.Button("Alternative Rock 🎵")
1040
+ post_punk_btn = gr.Button("Post-Punk 🖤")
1041
+ with gr.Row():
1042
+ indie_rock_btn = gr.Button("Indie Rock 🎤")
1043
+ funk_rock_btn = gr.Button("Funk Rock 🕺")
1044
+ detroit_techno_btn = gr.Button("Detroit Techno 🎛️")
1045
+ deep_house_btn = gr.Button("Deep House 🏠")
1046
+
1047
+ with gr.Column(elem_classes="settings-container"):
1048
+ gr.Markdown("### Settings")
1049
+ with gr.Group(elem_classes="group-container"):
1050
+ cfg_scale = gr.Slider(1.0, 10.0, step=0.1, value=float(loaded.get("cfg_scale", DEFAULT_SETTINGS["cfg_scale"])), label="CFG Scale")
1051
+ top_k = gr.Slider(10, 500, step=10, value=int(loaded.get("top_k", DEFAULT_SETTINGS["top_k"])), label="Top-K")
1052
+ top_p = gr.Slider(0.0, 1.0, step=0.01, value=float(loaded.get("top_p", DEFAULT_SETTINGS["top_p"])), label="Top-P")
1053
+ temperature = gr.Slider(0.1, 2.0, step=0.01, value=float(loaded.get("temperature", DEFAULT_SETTINGS["temperature"])), label="Temperature")
1054
+ total_duration = gr.Dropdown(choices=[30, 60, 90, 120], value=int(loaded.get("total_duration", 60)), label="Song Length (seconds)")
1055
+ bpm = gr.Slider(60, 180, step=1, value=int(loaded.get("bpm", 120)), label="Tempo (BPM)")
1056
+ drum_beat = gr.Dropdown(choices=["none", "standard rock", "funk groove", "techno kick", "jazz swing"], value=str(loaded.get("drum_beat", "none")), label="Drum Beat")
1057
+ synthesizer = gr.Dropdown(choices=["none", "analog synth", "digital pad", "arpeggiated synth"], value=str(loaded.get("synthesizer", "none")), label="Synthesizer")
1058
+ rhythmic_steps = gr.Dropdown(choices=["none", "syncopated steps", "steady steps", "complex steps"], value=str(loaded.get("rhythmic_steps", "none")), label="Rhythmic Steps")
1059
+ bass_style = gr.Dropdown(choices=["none", "slap bass", "deep bass", "melodic bass"], value=str(loaded.get("bass_style", "none")), label="Bass Style")
1060
+ guitar_style = gr.Dropdown(choices=["none", "distorted", "clean", "jangle"], value=str(loaded.get("guitar_style", "none")), label="Guitar Style")
1061
+ target_volume = gr.Slider(-30.0, -20.0, step=0.5, value=float(loaded.get("target_volume", -23.0)), label="Target Loudness (dBFS RMS)")
1062
+ preset = gr.Dropdown(choices=["default", "rock", "techno", "grunge", "indie", "funk_rock"], value=str(loaded.get("preset", "default")), label="Preset")
1063
+ max_steps = gr.Dropdown(choices=[1000, 1200, 1300, 1500], value=int(loaded.get("max_steps", 1500)), label="Max Steps (per chunk hint)")
1064
+
1065
+ bitrate_state = gr.State(value=str(loaded.get("bitrate", "192k")))
1066
+ sample_rate_state = gr.State(value=str(loaded.get("output_sample_rate", "48000")))
1067
+ bit_depth_state = gr.State(value=str(loaded.get("bit_depth", "16")))
1068
+
1069
+ with gr.Row():
1070
+ bitrate_128_btn = gr.Button("Bitrate 128k")
1071
+ bitrate_192_btn = gr.Button("Bitrate 192k")
1072
+ bitrate_320_btn = gr.Button("Bitrate 320k")
1073
+ with gr.Row():
1074
+ sample_rate_22050_btn = gr.Button("SR 22.05k")
1075
+ sample_rate_44100_btn = gr.Button("SR 44.1k")
1076
+ sample_rate_48000_btn = gr.Button("SR 48k")
1077
+ with gr.Row():
1078
+ bit_depth_16_btn = gr.Button("16-bit")
1079
+ bit_depth_24_btn = gr.Button("24-bit")
1080
+
1081
+ with gr.Row():
1082
+ gen_btn = gr.Button("Generate Music 🚀")
1083
+ clr_btn = gr.Button("Clear 🧹")
1084
+ save_btn = gr.Button("Save Settings 💾")
1085
+ load_btn = gr.Button("Load Settings 📂")
1086
+ reset_btn = gr.Button("Reset Defaults ♻️")
1087
+
1088
+ with gr.Column(elem_classes="output-container"):
1089
+ gr.Markdown("### Output")
1090
+ out_audio = gr.Audio(label="Generated Track", type="filepath")
1091
+ status_box = gr.Textbox(label="Status", interactive=False)
1092
+ vram_box = gr.Textbox(label="VRAM Usage", interactive=False, value="")
1093
+
1094
+ with gr.Column(elem_classes="logs-container"):
1095
+ gr.Markdown("### Logs")
1096
+ log_output = gr.Textbox(label="Last Log File", lines=16, interactive=False)
1097
+ log_btn = gr.Button("View Last Log")
1098
+
1099
+ # Genre buttons -> prompt text (chunk_num fixed = 1 for initial suggestion)
1100
+ rhcp_btn.click(
1101
+ set_red_hot_chili_peppers_prompt,
1102
+ inputs=[bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, gr.State(value=1)],
1103
+ outputs=instrumental_prompt
1104
+ )
1105
+ nirvana_btn.click(set_nirvana_grunge_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1106
+ pearl_jam_btn.click(set_pearl_jam_grunge_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1107
+ soundgarden_btn.click(set_soundgarden_grunge_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1108
+ foo_fighters_btn.click(set_foo_fighters_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1109
+ smashing_pumpkins_btn.click(set_smashing_pumpkins_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1110
+ radiohead_btn.click(set_radiohead_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1111
+ classic_rock_btn.click(set_classic_rock_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1112
+ alternative_rock_btn.click(set_alternative_rock_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1113
+ post_punk_btn.click(set_post_punk_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1114
+ indie_rock_btn.click(set_indie_rock_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1115
+ funk_rock_btn.click(set_funk_rock_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1116
+ detroit_techno_btn.click(set_detroit_techno_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1117
+ deep_house_btn.click(set_deep_house_prompt, [bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style], instrumental_prompt)
1118
+
1119
+ # Bitrate / SR / Bit depth quick-sets
1120
+ bitrate_128_btn.click(set_bitrate_128, outputs=bitrate_state)
1121
+ bitrate_192_btn.click(set_bitrate_192, outputs=bitrate_state)
1122
+ bitrate_320_btn.click(set_bitrate_320, outputs=bitrate_state)
1123
+ sample_rate_22050_btn.click(set_sample_rate_22050, outputs=sample_rate_state)
1124
+ sample_rate_44100_btn.click(set_sample_rate_44100, outputs=sample_rate_state)
1125
+ sample_rate_48000_btn.click(set_sample_rate_48000, outputs=sample_rate_state)
1126
+ bit_depth_16_btn.click(set_bit_depth_16, outputs=bit_depth_state)
1127
+ bit_depth_24_btn.click(set_bit_depth_24, outputs=bit_depth_state)
1128
+
1129
+ # Generate
1130
+ gen_btn.click(
1131
+ generate_music_wrapper,
1132
+ inputs=[
1133
+ instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, bpm,
1134
+ drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, target_volume,
1135
+ preset, max_steps, vram_box, bitrate_state, sample_rate_state, bit_depth_state
1136
+ ],
1137
+ outputs=[out_audio, status_box, vram_box]
1138
+ )
1139
+
1140
+ # Clear
1141
+ clr_btn.click(
1142
+ clear_inputs, outputs=[
1143
+ instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, bpm,
1144
+ drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, target_volume,
1145
+ preset, max_steps, bitrate_state, sample_rate_state, bit_depth_state
1146
+ ]
1147
+ )
1148
+
1149
+ # Save / Load / Reset actions
1150
+ def _save_action(
1151
+ instrumental_prompt_v, cfg_v, top_k_v, top_p_v, temp_v, dur_v, bpm_v,
1152
+ drum_v, synth_v, steps_v, bass_v, guitar_v, vol_v, preset_v, maxsteps_v, br_v, sr_v, bd_v
1153
+ ):
1154
+ s = {
1155
+ "instrumental_prompt": instrumental_prompt_v,
1156
+ "cfg_scale": float(cfg_v),
1157
+ "top_k": int(top_k_v),
1158
+ "top_p": float(top_p_v),
1159
+ "temperature": float(temp_v),
1160
+ "total_duration": int(dur_v),
1161
+ "bpm": int(bpm_v),
1162
+ "drum_beat": str(drum_v),
1163
+ "synthesizer": str(synth_v),
1164
+ "rhythmic_steps": str(steps_v),
1165
+ "bass_style": str(bass_v),
1166
+ "guitar_style": str(guitar_v),
1167
+ "target_volume": float(vol_v),
1168
+ "preset": str(preset_v),
1169
+ "max_steps": int(maxsteps_v),
1170
+ "bitrate": str(br_v),
1171
+ "output_sample_rate": str(sr_v),
1172
+ "bit_depth": str(bd_v)
1173
+ }
1174
+ save_settings_to_file(s)
1175
+ for k, v in s.items():
1176
+ CURRENT_SETTINGS[k] = v
1177
+ return "✅ Settings saved."
1178
+
1179
+ def _load_action():
1180
+ s = load_settings_from_file()
1181
+ for k, v in s.items():
1182
+ CURRENT_SETTINGS[k] = v
1183
+ return (
1184
+ s["instrumental_prompt"], s["cfg_scale"], s["top_k"], s["top_p"], s["temperature"],
1185
+ s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
1186
+ s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
1187
+ s["bitrate"], s["output_sample_rate"], s["bit_depth"],
1188
+ "✅ Settings loaded."
1189
+ )
1190
+
1191
+ def _reset_action():
1192
+ s = DEFAULT_SETTINGS.copy()
1193
+ save_settings_to_file(s)
1194
+ for k, v in s.items():
1195
+ CURRENT_SETTINGS[k] = v
1196
+ return (
1197
+ s["instrumental_prompt"], s["cfg_scale"], s["top_k"], s["top_p"], s["temperature"],
1198
+ s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
1199
+ s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
1200
+ s["bitrate"], s["output_sample_rate"], s["bit_depth"],
1201
+ "✅ Defaults restored."
1202
+ )
1203
+
1204
+ save_btn.click(
1205
+ _save_action,
1206
+ inputs=[
1207
+ instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, bpm,
1208
+ drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, target_volume,
1209
+ preset, max_steps, bitrate_state, sample_rate_state, bit_depth_state
1210
+ ],
1211
+ outputs=status_box
1212
+ )
1213
+
1214
+ load_btn.click(
1215
+ _load_action,
1216
+ outputs=[
1217
+ instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, bpm,
1218
+ drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, target_volume,
1219
+ preset, max_steps, bitrate_state, sample_rate_state, bit_depth_state, status_box
1220
+ ]
1221
+ )
1222
+
1223
+ reset_btn.click(
1224
+ _reset_action,
1225
+ outputs=[
1226
+ instrumental_prompt, cfg_scale, top_k, top_p, temperature, total_duration, bpm,
1227
+ drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, target_volume,
1228
+ preset, max_steps, bitrate_state, sample_rate_state, bit_depth_state, status_box
1229
+ ]
1230
+ )
1231
+
1232
+ # Logs
1233
+ log_btn.click(get_latest_log, outputs=log_output)
1234
+
1235
+ # ======================================================================================
1236
+ # LAUNCH GRADIO
1237
+ # ======================================================================================
1238
+
1239
+ logger.info("Launching Gradio UI at http://0.0.0.0:9999 ...")
1240
+ try:
1241
+ demo.launch(
1242
+ server_name="0.0.0.0",
1243
+ server_port=9999,
1244
+ share=False,
1245
+ inbrowser=False,
1246
+ show_error=True
1247
+ )
1248
+ except Exception as e:
1249
+ logger.error(f"Failed to launch Gradio UI: {e}")
1250
+ logger.error(traceback.format_exc())
1251
+ sys.exit(1)