MONX_AI / app.py
teszenofficial's picture
Create app.py
aa3df58 verified
import gradio as gr
import librosa
import numpy as np
from pydub import AudioSegment, effects
import uuid, os, json
# =====================================================
# ESTADO DEL MODELO (MISMA CARPETA QUE app.py)
# =====================================================
STATE_PATH = "monx_dj_state.json"
UPLOAD_DIR = "uploads"
OUTPUT_DIR = "outputs"
os.makedirs(UPLOAD_DIR, exist_ok=True)
os.makedirs(OUTPUT_DIR, exist_ok=True)
# =====================================================
# UTILIDAD: CONVERSIÓN SEGURA A JSON
# =====================================================
def to_python(obj):
if isinstance(obj, dict):
return {k: to_python(v) for k, v in obj.items()}
if isinstance(obj, list):
return [to_python(v) for v in obj]
if isinstance(obj, np.generic):
return obj.item()
return obj
# =====================================================
# ESTADO DEL MODELO (IA REAL)
# =====================================================
DEFAULT_STATE = {
"weights": {
"bpm": 0.35,
"energy": 0.30,
"smooth": 0.20,
"drop_penalty": 0.15
},
"avg_score": 0.5,
"runs": 0
}
def load_state():
if os.path.exists(STATE_PATH):
try:
with open(STATE_PATH, "r") as f:
return json.load(f)
except:
pass
return DEFAULT_STATE.copy()
def save_state(state):
with open(STATE_PATH, "w") as f:
json.dump(to_python(state), f, indent=2)
state = load_state()
weights = state["weights"]
# =====================================================
# ANÁLISIS MUSICAL (ROBUSTO, SIN DEPENDER DE VERSIONES)
# =====================================================
def analyze_audio(path):
y, sr = librosa.load(path, mono=True)
# ---------- BPM SEGURO ----------
onset_env = librosa.onset.onset_strength(y=y, sr=sr)
try:
tempo = float(librosa.beat.tempo(
onset_envelope=onset_env, sr=sr
)[0])
except Exception:
tempo = float(np.mean(onset_env) * 60)
if tempo <= 0 or np.isnan(tempo):
tempo = 120.0 # BPM seguro por defecto
# ---------- ENERGÍA ----------
rms = librosa.feature.rms(y=y)[0]
rms = librosa.util.normalize(rms)
# ---------- DROPS ----------
drops = [
i for i in range(10, len(rms) - 10)
if rms[i] > np.mean(rms[i-10:i-1]) * 1.4
]
return {
"tempo": tempo,
"energy": rms,
"drops": drops
}
# =====================================================
# FUNCIÓN DE DECISIÓN (IA)
# =====================================================
def score_transition(a, b, t):
bpm_sim = max(0, 1 - abs(a["tempo"] - b["tempo"]) / 35)
energy_sim = max(0, 1 - abs(a["energy"][t] - b["energy"][0]))
drop_penalty = 1 if t in a["drops"] else 0
return (
weights["bpm"] * bpm_sim +
weights["energy"] * energy_sim +
weights["smooth"] * ((bpm_sim + energy_sim) / 2) -
weights["drop_penalty"] * drop_penalty
)
# =====================================================
# TIME-STRETCH
# =====================================================
def bpm_adjust(segment, from_bpm, to_bpm):
if from_bpm <= 0 or to_bpm <= 0:
return segment
ratio = from_bpm / to_bpm
if abs(1 - ratio) > 0.20:
return segment
new_rate = int(segment.frame_rate * ratio)
return segment._spawn(
segment.raw_data,
overrides={"frame_rate": new_rate}
).set_frame_rate(segment.frame_rate)
# =====================================================
# IA DJ MIXER
# =====================================================
def auto_dj_mix(files, durations, crossfade_sec):
log = "🎧 Iniciando MONX DJ (IA)\n"
yield log, None
durs = [float(x.strip()) for x in durations.split(",")]
crossfade_ms = int(crossfade_sec * 1000)
tracks, analyses, scores = [], [], []
for i, f in enumerate(files):
log += f"\n🔍 Analizando canción {i+1}\n"
yield log, None
ext = f.name.split(".")[-1]
path = os.path.join(UPLOAD_DIR, f"{uuid.uuid4().hex}.{ext}")
with open(f.name, "rb") as src, open(path, "wb") as dst:
dst.write(src.read())
audio = effects.normalize(AudioSegment.from_file(path))
tracks.append(audio)
analyses.append(analyze_audio(path))
mix = AudioSegment.silent(0)
for i in range(len(tracks)):
play_ms = min(int(durs[i] * 1000), len(tracks[i]))
segment = tracks[i][:play_ms]
if i == 0:
mix = segment
continue
prev, curr = analyses[i - 1], analyses[i]
candidates = range(5, min(len(prev["energy"]) - 1, 50))
best_t = max(candidates, key=lambda t: score_transition(prev, curr, t))
scores.append(score_transition(prev, curr, best_t))
target_bpm = curr["tempo"]
mix_adj = bpm_adjust(mix, prev["tempo"], target_bpm)
seg_adj = bpm_adjust(segment, curr["tempo"], target_bpm)
safe_cf = min(crossfade_ms, len(mix_adj), len(seg_adj))
mix = bpm_adjust(
mix_adj.append(seg_adj, crossfade=safe_cf),
target_bpm, curr["tempo"]
)
out_path = os.path.join(
OUTPUT_DIR, f"monx_dj_mix_{uuid.uuid4().hex}.m4a"
)
mix.export(out_path, format="ipod", codec="aac", bitrate="192k")
# ---------- APRENDIZAJE AUTOMÁTICO ----------
if scores:
new_avg = sum(scores) / len(scores)
reward = 1 if new_avg > state["avg_score"] else -1
lr = 0.05
for k in weights:
weights[k] += reward * lr * abs(weights[k])
total = sum(abs(v) for v in weights.values())
for k in weights:
weights[k] /= total
state["avg_score"] = new_avg
state["runs"] += 1
state["weights"] = weights
save_state(state)
log += "\n✅ Mix listo. Da feedback para que MONX DJ aprenda."
yield log, out_path
# =====================================================
# FEEDBACK HUMANO
# =====================================================
def feedback(reward):
lr = 0.08
for k in weights:
weights[k] += reward * lr * abs(weights[k])
total = sum(abs(v) for v in weights.values())
for k in weights:
weights[k] /= total
state["weights"] = weights
save_state(state)
return "🧠 Feedback recibido. MONX DJ ha aprendido."
# =====================================================
# UI
# =====================================================
with gr.Blocks(title="MONX DJ") as demo:
gr.Markdown(
"<h1 style='text-align:center'>🎚️ MONX DJ</h1>"
"<p style='text-align:center'>IA DJ con aprendizaje real</p>"
)
files = gr.File(
label="Sube 2 a 4 canciones",
file_count="multiple",
file_types=[".mp3", ".wav", ".flac", ".m4a"]
)
durations = gr.Textbox(label="Duración por canción (seg)", value="90,90")
crossfade = gr.Slider(6, 20, value=12, step=1, label="Crossfade (seg)")
btn = gr.Button("🔥 Auto Mix")
status = gr.Markdown()
output = gr.Audio(type="filepath")
btn.click(auto_dj_mix, [files, durations, crossfade], [status, output])
gr.Markdown("### ¿Te gustó el mix?")
like = gr.Button("👍 Sí")
dislike = gr.Button("👎 No")
fb_status = gr.Markdown()
like.click(lambda: feedback(1), None, fb_status)
dislike.click(lambda: feedback(-1), None, fb_status)
demo.launch()