Spaces:
Running
Running
File size: 6,554 Bytes
d6212f9 1236813 94379ca d6212f9 7791167 6ab7ef6 c8e211f d6212f9 e0926b8 e62e2ef e0926b8 3f71e5d d6212f9 6ab7ef6 d6212f9 e62e2ef d6212f9 e62e2ef d6212f9 e62e2ef 6ab7ef6 e62e2ef 6ab7ef6 d6212f9 8011c96 b9798ab b9e24ed b9798ab 0d447ee b9798ab d6212f9 b9798ab 613f992 | 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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | import os
import tempfile
import torch
import soundfile as sf
import numpy as np
from pathlib import Path
from pyharp import ModelCard, build_endpoint, SampleRate, Channels
from pyharp.tags import Subcategory
import gradio as gr
from nam.train.lightning_module import LightningModule as _LightningModule
import nam.data as data
os.environ["TOKENIZERS_PARALLELISM"] = "true"
model = None
CKPT_PATH = Path(__file__).parent / "demo_ckpt.ckpt"
MODEL_CONFIG_PATH = Path(__file__).parent / "default_config_files/models/wavenet.json"
# A lossless input keeps its container. A lossy one comes back as WAV, since encoding the result
# lossily again would expose the artifacts of the first encode and add a second set.
LOSSLESS = {".wav", ".flac"}
def get_model():
global model
if model is None:
print("Loading PANAMA model...", flush=True)
import json
with open(MODEL_CONFIG_PATH, "r") as fp:
model_config = json.load(fp)
model = _LightningModule.load_from_checkpoint(
CKPT_PATH,
**_LightningModule.parse_config(model_config)
)
model.eval()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
print("Model loaded.", flush=True)
return model
model_card = ModelCard(
name="PANAMA",
description=(
"Parametric neural amp modeling. Apply amp simulation with 6 "
"controllable knobs. Amp modeling is mono: a stereo file is downmixed "
"before the amp and the single result is copied back across its "
"channels, so the output has the channel count it came in with but not "
"its stereo image. The input's sample rate is restored. Repository: "
"https://github.com/ETH-DISCO/PANAMA Paper: "
"https://arxiv.org/abs/2509.26564"
),
author="Florian Grötschla, Longxiang Jiao, Luca A. Lanzendörfer, Roger Wattenhofer",
tags=[Subcategory.NEURAL_ANALOG_EFFECTS, SampleRate(48000), Channels(1), 'amp-modeling', 'guitar', 'domain:music'],
)
def process_fn(input_audio_path: str, knob1: float, knob2: float, knob3: float, knob4: float, knob5: float, knob6: float) -> str:
if not input_audio_path:
raise gr.Error("Provide the guitar or bass recording to play through the amp.")
try:
info = sf.info(input_audio_path)
except Exception as exc:
raise gr.Error(f"Could not read the audio file: {exc}") from exc
if info.frames == 0:
raise gr.Error("The audio file is empty.")
m = get_model()
device = next(m.parameters()).device
# nam's loader rejects anything but mono, and an amp model is mono by
# nature, so the downmix happens here and the channel count is restored
# after inference rather than refusing the file
import librosa
import soundfile
with soundfile.SoundFile(input_audio_path) as handle:
original_sr = handle.samplerate
original_channels = handle.channels
input_audio_np, _ = librosa.load(input_audio_path, sr=None, mono=True)
if original_sr != m.net.sample_rate:
input_audio_np = librosa.resample(
input_audio_np, orig_sr=original_sr, target_sr=m.net.sample_rate
)
input_audio = torch.tensor(input_audio_np, dtype=torch.float32)
input_audio = input_audio.to(device)
g_tensor = torch.tensor([[knob1, knob2, knob3, knob4, knob5, knob6]], dtype=torch.float32).to(device)
print(f"Processing with g-vector: {g_tensor}", flush=True)
with torch.no_grad():
output = m.forward(input_audio, g_tensor)
output_np = output.cpu().numpy()
# Back to the rate the session uses, before the channels are rebuilt, so
# the resampler only ever sees one channel
if original_sr != m.net.sample_rate:
output_np = librosa.resample(
output_np, orig_sr=m.net.sample_rate, target_sr=original_sr
)
# Two identical channels play back the same as a mono file on a stereo
# track. It does not make the result stereo, which the model card says.
if original_channels > 1:
output_np = np.tile(output_np[:, None], (1, original_channels))
input_ext = Path(input_audio_path).suffix.lower()
output_path = (Path(tempfile.mkdtemp(prefix="panama-"))
/ f"panama{input_ext if input_ext in LOSSLESS else '.wav'}")
sf.write(output_path, output_np, original_sr)
return str(output_path)
# pyharp runs process_fn in a worker, which imports this file as __mp_main__ and starts with the
# server, so the model is loaded there, before the first request rather than during it.
if __name__ != "__main__":
get_model()
if __name__ == "__main__":
with gr.Blocks() as demo:
input_components = [
gr.Audio(type="filepath", label="Input Audio").harp_required(True).set_info((
"A dry guitar or bass recording. Feeding it an already-amped tone "
"stacks one amp on another."
)),
gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.5, label="Gain",
info="Preamp drive. Raise it for more distortion and compression."),
gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.5, label="Bass",
info="Low-end tone control."),
gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.5, label="Mid",
info=(
"Midrange tone control. Cutting it gives the scooped sound heavy "
"styles use."
)),
gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.5, label="Treble",
info="High-end tone control."),
gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.5, label="Master",
info=(
"Power amp level, which adds its own saturation as it rises rather "
"than only getting louder."
)),
gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.5, label="Presence",
info="Upper-midrange bite, which helps a part cut through a mix."),
]
output_components = [
gr.Audio(type="filepath", label="Amped Audio").set_info("Amp-processed audio."),
]
app = build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
print("Launching Gradio...", flush=True)
demo.queue().launch(server_name="0.0.0.0", server_port=7860, show_error=True, pwa=True)
|