File size: 4,141 Bytes
b703376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
112
113
"""Woosh-DFlow text-to-audio BACKEND Space (plain Gradio 6, no pyharp).



This is the *backend* half of the HARP two-Space workflow for Sony AI's Woosh

sound-effect model. It reuses Woosh's own inference code (the same calls the

upstream ``gradio_Woosh-DFlow.py`` demo makes) and exposes a single clean

``/generate`` API endpoint that a thin pyharp HARP frontend proxies to via

``gradio_client``.



Why a separate backend Space at all: Woosh needs ``python>=3.12``,

``torch==2.8.0`` and ``gradio>=6.9.0``, which cannot coexist in one process with

pyharp v0.3.0 (it hard-pins ``gradio==5.28.0``). Isolating Woosh in its own

Space sidesteps that conflict entirely.



Weights (CC-BY-NC, from the official v1.0.0 GitHub release) are baked into the

Docker image at build time (see the Dockerfile), so this file just loads them.

"""

from __future__ import annotations

import logging
import os
import tempfile

import gradio as gr
import numpy as np
import soundfile as sf
import torch

from woosh.components.base import LoadConfig
from woosh.inference.flowmap_sampler import sample_euler
from woosh.model.flowmap_from_pretrained import FlowMapFromPretrained

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("woosh-backend")

# Matches the upstream demo: 128-channel latents, ~5s at 48 kHz after the AE.
SAMPLE_RATE = 48000
LATENT_CHANNELS = 128
LATENT_FRAMES = 501
NUM_STEPS = 4  # Woosh-DFlow is the *distilled* model: 4 sampling steps.
RENOISE = [0, 0.5, 0.5, 0.3]
CHECKPOINT = os.environ.get("WOOSH_CHECKPOINT", "checkpoints/Woosh-DFlow")

if torch.cuda.is_available():
    DEVICE = "cuda"
elif getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available():
    DEVICE = "mps"
else:
    DEVICE = "cpu"

log.info("Loading Woosh-DFlow from %s on %s ...", CHECKPOINT, DEVICE)
LDM = FlowMapFromPretrained(LoadConfig(path=CHECKPOINT)).eval().to(DEVICE)
log.info("Model loaded on %s.", DEVICE)


@torch.inference_mode()
def generate(prompt: str, cfg_scale: float = 4.5, seed: int = -1) -> str:
    """Generate one sound effect from a text prompt; return a WAV file path."""

    if not prompt or not prompt.strip():
        raise gr.Error("Please enter a text prompt describing the sound effect.")

    if seed is None or int(seed) < 0:
        seed = int.from_bytes(os.urandom(4), "big") % (2**31)
    torch.manual_seed(int(seed))

    noise = torch.randn(1, LATENT_CHANNELS, LATENT_FRAMES).to(DEVICE)
    cond = LDM.get_cond(
        {"audio": None, "description": [prompt]}, no_dropout=True, device=DEVICE
    )
    x_fake = sample_euler(
        model=LDM,
        noise=noise,
        cond=cond,
        num_steps=NUM_STEPS,
        renoise=RENOISE,
        cfg=float(cfg_scale),
    )
    audio = LDM.autoencoder.inverse(x_fake).cpu().float()

    # Peak-normalize to avoid clipping, then write a float32 WAV.
    peak = audio.abs().amax(dim=-1, keepdim=True).clamp(min=1.0)
    audio = (audio / peak).clamp(-1.0, 1.0)
    wav = np.ascontiguousarray(audio[0].squeeze().numpy())

    out_path = os.path.join(tempfile.mkdtemp(), "woosh_dflow.wav")
    sf.write(out_path, wav, SAMPLE_RATE)
    return out_path


with gr.Blocks(title="Woosh-DFlow backend") as demo:
    gr.Markdown(
        "# Woosh-DFlow \u2014 text-to-audio backend\n"
        "Plain-Gradio **backend** for the HARP two-Space workflow. Point a pyharp "
        "remote frontend at the `/generate` API endpoint."
    )
    prompt = gr.Textbox(
        label="Prompt",
        placeholder="e.g. sportscar engine revving and driving away quickly",
    )
    cfg = gr.Slider(0.0, 15.0, value=4.5, step=0.1, label="CFG scale")
    seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
    out = gr.Audio(label="Generated audio", type="filepath")
    gr.Button("Generate", variant="primary").click(
        generate, inputs=[prompt, cfg, seed], outputs=out, api_name="generate"
    )

demo.queue(max_size=8).launch(
    server_name="0.0.0.0",
    server_port=int(os.environ.get("PORT", "7860")),
    show_error=True,
)