DDSP / frontend_app.py
harp-dev's picture
Return the result at the input's rate and channel count, and pin Gradio in the frontend
6a9b881 verified
Raw History Blame Contribute Delete
5.23 kB
from __future__ import annotations
import json
import os
import subprocess
import gradio as gr
from pyharp import *
_BACKEND_PYTHON = os.environ.get("BACKEND_PYTHON", "/opt/backend/bin/python")
_BACKEND_SCRIPT = os.environ.get("BACKEND_SCRIPT", "/app/backend_worker.py")
_BACKEND_TIMEOUT = float(os.environ.get("BACKEND_TIMEOUT", "900"))
def _call_backend(payload):
# One-shot subprocess into the isolated backend venv. The worker prints
# exactly one JSON object as its final stdout line; all model/library
# chatter is redirected to stderr (captured in the Space logs).
completed = subprocess.run(
[_BACKEND_PYTHON, _BACKEND_SCRIPT],
input=json.dumps(payload),
capture_output=True,
text=True,
timeout=_BACKEND_TIMEOUT,
)
_lines = [ln for ln in completed.stdout.splitlines() if ln.strip()]
try:
response = json.loads(_lines[-1]) if _lines else {}
except json.JSONDecodeError as exc:
raise gr.Error(
"The backend returned no valid result. Last stderr: "
+ (completed.stderr[-1500:] or "(empty)")
) from exc
if not response.get("ok"):
raise gr.Error(
response.get("error") or completed.stderr[-1500:] or "Backend worker failed"
)
return response.get("outputs") or {}
model_card = ModelCard(
name="DDSP",
description=(
"Differentiable DSP timbre transfer: recast your audio in the timbre of "
"a pretrained instrument (Violin, Flute, Trumpet, ...). Runs the "
"original, unmodified DDSP stack in an isolated Python 3.9 backend. "
"The model is fixed-rate and mono, so the result is resampled back to "
"the input's rate and copied across its channels: the output has the "
"format it came in with, but no stereo image. "
"Repository: https://github.com/magenta/ddsp Paper: "
"https://arxiv.org/abs/2001.04643"
),
author="Jesse Engel, Lamtharn Hantrakul, Chenjie Gu, Adam Roberts",
tags=[Subcategory.TIMBRE_TRANSFER],
)
def process_fn(audio, model_name, threshold, adjust, quiet, autotune, pitch_shift, loudness_shift):
_inputs = dict(zip(["audio", "model_name", "threshold", "adjust", "quiet", "autotune", "pitch_shift", "loudness_shift"], [audio, model_name, threshold, adjust, quiet, autotune, pitch_shift, loudness_shift]))
_outputs = _call_backend({"inputs": _inputs})
_out_out_audio = _outputs.get("out_audio")
if not _out_out_audio:
raise gr.Error("The backend produced no 'out_audio' output. Check the Space logs (the backend worker's stderr is captured there).")
return _out_out_audio
if __name__ == "__main__":
with gr.Blocks() as demo:
input_components = [
gr.Audio(type="filepath", label="Input audio").harp_required(True).set_info((
"A monophonic recording to recast. One voice or one instrument line "
"at a time works; the model follows a single pitch, so chords "
"confuse it."
)),
gr.Dropdown(choices=["Violin", "Flute", "Flute2", "Trumpet", "Tenor_Saxophone"], value="Violin", label="Instrument",
info="The pretrained timbre to recast the input in."),
gr.Slider(minimum=0.0, maximum=2.0, step=0.01, value=1.0, label="Note-detection threshold",
info="Raise this if room noise or breath is being heard as notes."),
gr.Checkbox(value=True, label="Auto-adjust to model range",
info=(
"Shifts the input into the pitch range the chosen instrument was "
"trained on, which usually helps when the source sits much higher "
"or lower."
)),
gr.Slider(minimum=0.0, maximum=60.0, step=1.0, value=20.0, label="Quiet parts (dB)",
info=(
"Raise this to silence quiet passages, lower it to let them "
"through."
)),
gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=0.0, label="Autotune amount",
info=(
"Pulls detected pitches toward the nearest semitone. Zero keeps the "
"original intonation, including any slides and vibrato."
)),
gr.Slider(minimum=-2.0, maximum=2.0, step=1.0, value=0.0, label="Pitch shift (octaves)",
info="Transposes the input before synthesis."),
gr.Slider(minimum=-20.0, maximum=20.0, step=1.0, value=0.0, label="Loudness shift (dB)",
info=(
"Drives the instrument harder or softer, which changes its tone as "
"well as its level."
)),
]
output_components = [
gr.Audio(type="filepath", label="Timbre-transferred audio").set_info((
"The input resynthesized in the chosen instrument's timbre."
)),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
demo.queue().launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860")), show_error=True)