Spaces:
Running on Zero
Running on Zero
| import os | |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") | |
| import sys | |
| from pathlib import Path | |
| import spaces # MUST come before any CUDA-touching import | |
| import torch | |
| sys.path.insert(0, str(Path(__file__).parent / "src")) | |
| import numpy as np | |
| import soundfile as sf | |
| import torchaudio.functional as AF | |
| import gradio as gr | |
| from minimax_music3_latent_refiner import MiniMaxMusic3RefinerPipeline | |
| MODEL_ID = "terminusresearch/minimax-music3-latent-refiner-v0.10" | |
| MAX_SECONDS = 120.0 # keep ZeroGPU duration honest; model is quadratic in frames | |
| print("Loading MiniMax Music 3 latent refiner pipeline (refiner + DAV + MERT + CLAP)...") | |
| # Load on CPU: from_pretrained's safetensors/torch.load paths touch real CUDA ops that the | |
| # ZeroGPU main process cannot satisfy. The .to("cuda") moves below are intercepted by the | |
| # spaces hijack, which packs tensors for streaming into the GPU worker. | |
| REFINER = MiniMaxMusic3RefinerPipeline.from_pretrained(MODEL_ID, device="cpu") | |
| REFINER.refiner.to("cuda") | |
| REFINER.audio_vae.to("cuda") | |
| REFINER.mert.to("cuda") | |
| REFINER.clap.to("cuda") | |
| for _v in REFINER.normalization.values(): | |
| _v.to("cuda") | |
| print("Pipeline loaded.") | |
| def _load_waveform(path: str): | |
| """Read any audio file as float32 [channels, samples] at its native rate.""" | |
| if not path: | |
| raise gr.Error("Please upload an audio file or pick an example first.") | |
| data, sr = sf.read(path, dtype="float32", always_2d=True) | |
| if data.shape[1] > 2: | |
| data = data[:, :2] | |
| return torch.from_numpy(data.T.copy()), sr | |
| def _trim(waveform: torch.Tensor, sr: int, max_seconds: float): | |
| limit = int(max_seconds * sr) | |
| if waveform.shape[-1] > limit: | |
| return waveform[..., :limit], True | |
| return waveform, False | |
| DAV_HOP = 512 | |
| def _align_hop(waveform: torch.Tensor, sr: int): | |
| """Trim the tail to a whole DAV hop. | |
| The windowed refinement raises 'window overlap left uncovered latent frames' when the | |
| total sample count is not a multiple of the 512-sample DAV hop (the final window start | |
| is aligned down and the last partial frame is never covered). Trimming to a whole hop | |
| removes the uncovered frame; at 44.1 kHz that is at most ~11 ms of audio. | |
| """ | |
| frames = waveform.shape[-1] // DAV_HOP | |
| return waveform[..., : frames * DAV_HOP] | |
| def refine( | |
| audio_path: str, | |
| steps: int = 32, | |
| window_seconds: float = 30.0, | |
| overlap_seconds: float = 2.0, | |
| direct: bool = False, | |
| ): | |
| """Restore a damaged music clip with the MiniMax Music 3 latent refiner. | |
| Args: | |
| audio_path: degraded/damaged music audio to restore. | |
| steps: deterministic Euler bridge sampling steps (release default 32). | |
| window_seconds: overlapping inference window in seconds (30 s matches training). | |
| overlap_seconds: cross-fade overlap between windows in latent frames. | |
| direct: process the whole clip as one dense sequence instead of windows. | |
| Returns: | |
| Tuple of (restored audio at 44.1 kHz stereo, status text). | |
| """ | |
| if not audio_path: | |
| raise gr.Error("Please upload an audio file or pick an example first.") | |
| waveform, sr = _load_waveform(audio_path) | |
| waveform, trimmed = _trim(waveform, sr, MAX_SECONDS) | |
| waveform = _align_hop(waveform, sr) | |
| steps = int(steps) | |
| if steps < 1: | |
| steps = 32 | |
| ws = None if direct else float(window_seconds) | |
| result = REFINER( | |
| waveform, | |
| sr, | |
| steps=steps, | |
| window_seconds=ws, | |
| overlap_seconds=float(overlap_seconds), | |
| ) | |
| out = result.audio.squeeze(0).T.numpy() | |
| out_path = Path("/tmp") / "refined.wav" | |
| sf.write(out_path, out, result.sample_rate, subtype="PCM_16") | |
| status = ( | |
| f"Restored {out.shape[0] / result.sample_rate:.1f}s of audio " | |
| f"({steps} bridge steps, {'one dense sequence' if ws is None else f'{ws:.0f}s windows'})." | |
| ) | |
| if trimmed: | |
| status += f" Input was trimmed to the {MAX_SECONDS:.0f}s demo limit." | |
| return str(out_path), status | |
| def degrade(audio_path: str, lowpass_hz: float, noise_db: float, bits: float): | |
| """Apply a degradation chain (bandwidth cut + noise + bit depth + soft clip) | |
| to a clean clip, so you can hear what the refiner restores. | |
| Args: | |
| audio_path: clean music audio to damage. | |
| lowpass_hz: lowpass cutoff in Hz (bandwidth reduction). | |
| noise_db: additive white noise level in dBFS. | |
| bits: bit-depth reduction (bits per sample). | |
| Returns: | |
| Tuple of (damaged audio, status text). | |
| """ | |
| if not audio_path: | |
| raise gr.Error("Upload clean audio to damage first.") | |
| import scipy.signal as sps | |
| data, sr = sf.read(audio_path, dtype="float32", always_2d=True) | |
| if data.shape[1] > 2: | |
| data = data[:, :2] | |
| limit = int(MAX_SECONDS * sr) | |
| data = data[:limit] | |
| y = data.copy() | |
| nyq = sr / 2 | |
| if lowpass_hz < nyq: | |
| y = sps.sosfilt(sps.butter(2, lowpass_hz / nyq, btype="low", output="sos"), y, axis=0) | |
| y = sps.sosfilt(sps.butter(2, 60.0 / nyq, btype="high", output="sos"), y, axis=0) | |
| if noise_db > -90: | |
| rng = np.random.default_rng(0) | |
| y = y + rng.normal(0.0, 1.0, y.shape).astype(np.float32) * (10 ** (noise_db / 20.0)) | |
| if bits >= 2: | |
| levels = 2 ** int(bits) | |
| y = np.round(y * levels) / levels | |
| peak = max(float(np.abs(y).max()), 1e-6) | |
| y = np.tanh(y * (0.95 / peak) * 2.0) * 0.9 | |
| out_path = str(Path("/tmp") / "damaged.wav") | |
| sf.write(out_path, np.clip(y, -1, 1), sr, subtype="PCM_16") | |
| return out_path, f"Damaged {y.shape[0] / sr:.1f}s — now press Restore to refine it." | |
| CSS = """ | |
| #col-container { max-width: 1100px; margin: 0 auto; } | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| """ | |
| with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo: | |
| gr.Markdown( | |
| """ | |
| # 🎵 MiniMax Music 3 — Latent Refiner v0.10 | |
| Restore damaged music while keeping the performance, timing, and arrangement intact. | |
| A 137M-parameter bridge transformer that works directly in MiniMax Music 3's continuous | |
| DAV latent space, conditioned on MERT frame features and CLAP audio embeddings. | |
| **Upload a degraded clip** (bandwidth-limited, noisy, quantized, clipped recordings), or | |
| use the *Damage* tab to apply the training-style degradation chain to your own clean audio, | |
| then press **Restore**. | |
| Model: [`terminusresearch/minimax-music3-latent-refiner-v0.10`](https://huggingface.co/terminusresearch/minimax-music3-latent-refiner-v0.10) | |
| """ | |
| ) | |
| with gr.Column(elem_id="col-container"): | |
| with gr.Tab("Restore"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| audio_in = gr.Audio( | |
| label="Damaged music (input)", | |
| type="filepath", | |
| sources=["upload", "microphone"], | |
| ) | |
| restore_btn = gr.Button("Restore", variant="primary") | |
| status = gr.Textbox(label="Status", interactive=False, lines=2) | |
| with gr.Column(): | |
| audio_out = gr.Audio(label="Restored music (output)", type="filepath") | |
| with gr.Accordion("Advanced settings", open=False): | |
| steps = gr.Slider(1, 64, value=32, step=1, label="Bridge steps") | |
| window_seconds = gr.Slider( | |
| 5.0, 60.0, value=30.0, step=5.0, | |
| label="Window seconds (30 s matches training)", | |
| ) | |
| overlap_seconds = gr.Slider( | |
| 0.0, 10.0, value=2.0, step=0.5, label="Window overlap (seconds)" | |
| ) | |
| direct = gr.Checkbox( | |
| value=False, | |
| label="One dense sequence (no windows; not the quality baseline)", | |
| ) | |
| with gr.Tab("Damage your own audio"): | |
| gr.Markdown( | |
| "Apply the refiner's training-style degradation chain (bandwidth reduction, additive " | |
| "noise, bit-depth reduction, soft clipping) to a clean clip, then restore it in the " | |
| "**Restore** tab to compare. Public-domain (FreePD/CC0) source clips." | |
| ) | |
| clean_in = gr.Audio(label="Clean music (input)", type="filepath", sources=["upload", "microphone"]) | |
| with gr.Row(): | |
| lowpass_hz = gr.Slider(500, 20000, value=3200, step=100, label="Lowpass cutoff (Hz)") | |
| noise_db = gr.Slider(-90, -10, value=-34, step=1, label="Noise level (dBFS)") | |
| bits = gr.Slider(2, 16, value=6, step=1, label="Bit depth") | |
| damage_btn = gr.Button("Damage", variant="secondary") | |
| damaged_out = gr.Audio(label="Damaged music (output)", type="filepath") | |
| damage_status = gr.Textbox(label="Status", interactive=False, lines=1) | |
| gr.Examples( | |
| examples=[ | |
| ["piano_strings.wav"], | |
| ["epic_orchestral.wav"], | |
| ], | |
| inputs=[audio_in], | |
| outputs=[audio_out, status], | |
| fn=refine, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| label="Examples (public-domain music with the training-style degradation applied)", | |
| ) | |
| gr.Markdown( | |
| """ | |
| #### Reference clean sources | |
| These are the undamaged originals of the two examples — listen side by side to judge the restoration. | |
| """ | |
| ) | |
| with gr.Row(): | |
| gr.Audio(value="reference_piano_strings_clean.wav", label="Clean original — piano & strings", type="filepath") | |
| gr.Audio(value="reference_epic_orchestral_clean.wav", label="Clean original — epic orchestral", type="filepath") | |
| restore_btn.click( | |
| refine, | |
| inputs=[audio_in, steps, window_seconds, overlap_seconds, direct], | |
| outputs=[audio_out, status], | |
| api_name="restore", | |
| ) | |
| damage_btn.click( | |
| degrade, | |
| inputs=[clean_in, lowpass_hz, noise_db, bits], | |
| outputs=[damaged_out, damage_status], | |
| api_name="damage", | |
| ) | |
| demo.launch(mcp_server=True) |