micro-TCN / app.py
harp-dev's picture
Tidy the launch call and drop stray files
98a1280 verified
Raw History Blame Contribute Delete
5.38 kB
from __future__ import annotations
import uuid
from pathlib import Path
from tempfile import gettempdir
import gradio as gr
import soundfile as sf
try:
import spaces
except ImportError:
class spaces:
class GPU:
def __init__(self, func=None, duration=60):
self.func = func
def __call__(self, *args, **kwargs):
if self.func is not None:
return self.func(*args, **kwargs)
return args[0]
from pyharp import ModelCard, build_endpoint, SampleRate, Channels
from pyharp.tags import Subcategory
from microtcn_runtime import process_audio
MIN_AUDIO_SECONDS = 0.5
MAX_AUDIO_SECONDS = 30
OUTPUT_ROOT = Path(gettempdir()) / "microtcn_outputs"
model_card = ModelCard(
name="micro-TCN",
description=(
"Emulate a Teletronix LA-2A optical compressor/limiter using a "
"lightweight temporal convolutional network. 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/csteinmetz1/micro-tcn Paper: "
"https://arxiv.org/abs/2102.06200"
),
author="Christian J. Steinmetz & Joshua D. Reiss",
tags=[Subcategory.NEURAL_ANALOG_EFFECTS, SampleRate(44100), Channels(1), 'compressor', 'limiter', 'analog-emulation'],
)
@spaces.GPU(duration=120)
def match_input_format(output_path: str, input_path: str) -> str:
"""
Rewrite an output file at the sample rate and channel count it came in with.
The models here are fixed-rate and mono, and hand the writing to vendored
upstream code, so the format is corrected afterwards rather than threaded
through inference. Duplicated channels are copies, not a recovered stereo
image; the model card says so.
Args:
output_path (str): File the model wrote. Overwritten in place.
input_path (str): File the request supplied.
Returns:
output_path (str): The same path, for use in a return statement.
"""
import librosa
import numpy as np
import soundfile
want = soundfile.info(input_path)
have = soundfile.info(output_path)
if have.samplerate == want.samplerate and have.channels == want.channels:
return output_path
data, rate = soundfile.read(output_path, always_2d=True)
if rate != want.samplerate:
data = librosa.resample(
data.T, orig_sr=rate, target_sr=want.samplerate
).T
if data.shape[1] == 1 and want.channels > 1:
data = np.tile(data, (1, want.channels))
elif data.shape[1] > 1 and want.channels == 1:
data = data.mean(axis=1, keepdims=True)
soundfile.write(output_path, data, want.samplerate)
return output_path
def process_fn(
audio_path: str | None,
mode: str,
peak_reduction: float,
) -> str:
if not audio_path:
raise gr.Error("Please upload an audio file.")
try:
duration = sf.info(audio_path).duration
except Exception as exc:
raise gr.Error(f"Could not read the audio file: {exc}") from exc
if duration < MIN_AUDIO_SECONDS:
raise gr.Error("Audio must be at least 0.5 seconds long.")
if duration > MAX_AUDIO_SECONDS:
raise gr.Error(
f"Audio must be no longer than {MAX_AUDIO_SECONDS} seconds. "
f"Received {duration:.1f} seconds."
)
limit_mode = 1.0 if mode == "Limit" else 0.0
try:
audio, sample_rate = process_audio(
audio_path=audio_path,
limit_mode=limit_mode,
peak_reduction=peak_reduction,
)
except Exception as exc:
raise gr.Error(f"micro-TCN inference failed: {exc}") from exc
output_dir = OUTPUT_ROOT / uuid.uuid4().hex
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "microtcn_output.wav"
sf.write(str(output_path), audio, sample_rate)
return match_input_format(str(output_path), audio_path)
if __name__ == "__main__":
with gr.Blocks(title="micro-TCN LA-2A Emulation") as demo:
input_components = [
gr.Audio(type="filepath", label="Input Audio")
.harp_required(True)
.set_info("Audio clip to process, up to 30 seconds."),
gr.Dropdown(
choices=["Compress", "Limit"],
value="Compress",
label="Mode",
).set_info(
"Compress for gentle gain reduction, Limit for harder peak control."
),
gr.Slider(
minimum=0.0,
maximum=1.0,
value=0.42,
step=0.01,
label="Peak Reduction",
).set_info("Normalized peak-reduction control, from 0 to 1."),
]
output_components = [
gr.Audio(
type="filepath",
label="Processed Audio",
).set_info("Audio processed through the LA-2A emulation."),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
demo.queue(default_concurrency_limit=1).launch(show_error=True, pwa=True)