File size: 4,432 Bytes
394433f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import gradio as gr
import spaces
import tempfile
import numpy as np
import scipy.io.wavfile
import torch

print("Cargando modelo...")
from transformers import AutoProcessor, MusicgenForConditionalGeneration
processor = AutoProcessor.from_pretrained("facebook/musicgen-medium")
modelo = MusicgenForConditionalGeneration.from_pretrained("facebook/musicgen-medium")
SR = modelo.config.audio_encoder.sampling_rate  # 32000
print("Modelo listo")

FRAME = SR // 50          # muestras por token (musicgen va a 50 Hz)
MAX_TOKENS = 1500         # tope fisico de musicgen (~30s)
CONTEXT_SEG = 5           # segundos de "arranque" que damos para continuar
CHUNK_MAX_SEG = 25        # maximo que puede generar por pasada con ese contexto


def _a_numpy(audio_values):
    a = audio_values[0].cpu().numpy()
    return np.squeeze(a).astype(np.float32)


def _crossfade_join(a, b, sr, xf_seg=0.4):
    """Une a + b con un cruce corto para tapar la costura."""
    xf = min(int(xf_seg * sr), len(a), len(b))
    if xf <= 0:
        return np.concatenate([a, b])
    fade_out = np.linspace(1.0, 0.0, xf)
    fade_in = np.linspace(0.0, 1.0, xf)
    mezcla = a[-xf:] * fade_out + b[:xf] * fade_in
    return np.concatenate([a[:-xf], mezcla, b[xf:]])


@spaces.GPU(duration=300)
def generar_musica(prompt: str, duracion: int = 30) -> str:
    if not prompt.strip():
        raise gr.Error("El prompt no puede estar vacio.")
    duracion = max(5, min(60, int(duracion)))
    print(f"Generando: {prompt} | objetivo {duracion}s")

    device = "cuda" if torch.cuda.is_available() else "cpu"
    modelo.to(device)

    # --- Pasada 1: generacion inicial solo con texto ---
    base_seg = min(duracion, 30)
    inputs = processor(text=[prompt], padding=True, return_tensors="pt").to(device)
    with torch.no_grad():
        salida = modelo.generate(**inputs, max_new_tokens=min(int(base_seg * 50), MAX_TOKENS))
    audio = _a_numpy(salida)

    # --- Pasadas de continuacion hasta llegar a la duracion ---
    while len(audio) < int(duracion * SR):
        restante_seg = duracion - len(audio) / SR
        nuevos_seg = min(CHUNK_MAX_SEG, restante_seg + 1)  # +1 de margen para el cruce
        nuevos_tokens = int(nuevos_seg * 50)

        # cogemos la cola como arranque
        cola = audio[-int(CONTEXT_SEG * SR):]
        inputs = processor(
            audio=cola,
            sampling_rate=SR,
            text=[prompt],
            padding=True,
            return_tensors="pt",
        ).to(device)
        with torch.no_grad():
            cont = modelo.generate(**inputs, max_new_tokens=nuevos_tokens)
        cont_audio = _a_numpy(cont)

        # la salida incluye la cola que le dimos: la quitamos y nos quedamos lo nuevo
        n_prompt = (len(cola) // FRAME) * FRAME
        parte_nueva = cont_audio[n_prompt:]
        if len(parte_nueva) < FRAME:  # por si acaso no genero nada nuevo
            break
        audio = _crossfade_join(audio, parte_nueva, SR)

    audio = audio[: int(duracion * SR)]

    # --- Limpieza final: normalizar, seguridad, headroom, fade-out ---
    pico = np.max(np.abs(audio))
    if pico > 0:
        audio = audio / pico
    audio = np.clip(audio, -1.0, 1.0) * 0.97
    fade_len = min(len(audio), int(SR * 0.15))
    if fade_len > 0:
        audio[-fade_len:] = audio[-fade_len:] * np.linspace(1.0, 0.0, fade_len)

    audio = (audio * 32767).astype(np.int16)
    tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
    scipy.io.wavfile.write(tmp.name, SR, audio)
    print(f"Audio listo ({len(audio)/SR:.1f}s): {tmp.name}")
    return tmp.name


with gr.Blocks(title="Fabrica de Musica") as demo:
    gr.Markdown("# Fabrica de Musica Teshua")
    gr.Markdown("Melodias continuas de hasta 60s con musicgen-medium (el modelo continua la composicion, no la repite).")
    with gr.Row():
        with gr.Column():
            prompt_input = gr.Textbox(label="Prompt en ingles", placeholder="mystical ambient music, spiritual, no vocals", lines=2)
            duracion_input = gr.Slider(label="Duracion segundos", minimum=5, maximum=60, value=30, step=1)
            btn = gr.Button("Generar Musica", variant="primary")
        with gr.Column():
            audio_output = gr.Audio(label="Musica generada", type="filepath")
    btn.click(fn=generar_musica, inputs=[prompt_input, duracion_input], outputs=audio_output, api_name="generar_musica")

demo.launch(server_name="0.0.0.0", server_port=7860)