Spaces:
Running
Running
| import io | |
| import math | |
| import tempfile | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Dict, Optional, Tuple | |
| import gradio as gr | |
| import librosa | |
| import matplotlib.pyplot as plt | |
| from matplotlib.colors import LinearSegmentedColormap | |
| import numpy as np | |
| import onnxruntime as ort | |
| import soundfile as sf | |
| from PIL import Image | |
| # ----------------------------- | |
| # Configuration | |
| # ----------------------------- | |
| MAX_SECONDS = 10.0 | |
| ONNX_DIR = Path("./onnx") | |
| ATTN_LIMIT_NOISY_FRAME_OFFSET = 4 | |
| # Spectrogram palette mirrors the UI: ink/slate shadows, orange energy, | |
| # and warm cream highlights. Keeping it here makes the visual theme explicit | |
| # and gives both before/after plots the same stable color scale. | |
| SPECTROGRAM_CMAP = LinearSegmentedColormap.from_list( | |
| "dpdfnet_ui", | |
| [ | |
| "#17191c", # ink | |
| "#34383d", # slate | |
| "#6b321d", # burnt umber | |
| "#a64a16", # deep accent | |
| "#d76522", # UI orange | |
| "#f1cdb6", # accent soft | |
| "#fffaf7", # warm highlight | |
| ], | |
| N=256, | |
| ) | |
| SPECTROGRAM_DB_MIN = -80.0 | |
| SPECTROGRAM_DB_MAX = 0.0 | |
| class ModelSpec: | |
| name: str | |
| sr: int | |
| onnx_path: str | |
| # ----------------------------- | |
| # Model discovery and metadata | |
| # ----------------------------- | |
| def _infer_model_meta(model_name: str) -> int: | |
| normalized = model_name.lower().replace("-", "_") | |
| if "48khz" in normalized or "48k" in normalized or "48hr" in normalized: | |
| return 48000 | |
| if "8khz" in normalized or normalized.endswith("_8k") or "_8k_" in normalized: | |
| return 8000 | |
| # Fallback for unknown 16 kHz DPDFNet variants | |
| return 16000 | |
| def _display_label(spec: ModelSpec) -> str: | |
| khz = int(spec.sr // 1000) | |
| return f"{spec.name} ({khz} kHz)" | |
| def discover_model_presets() -> Dict[str, ModelSpec]: | |
| ordered_names = [ | |
| "baseline", | |
| "dpdfnet2", | |
| "dpdfnet4", | |
| "dpdfnet8", | |
| "dpdfnet2_8khz", | |
| "dpdfnet8_8khz", | |
| "dpdfnet2_48khz_hr", | |
| "dpdfnet8_48khz_hr", | |
| ] | |
| found_paths = {p.stem: p for p in ONNX_DIR.glob("*.onnx") if p.is_file()} | |
| presets: Dict[str, ModelSpec] = {} | |
| for name in ordered_names: | |
| p = found_paths.get(name) | |
| if p is None: | |
| continue | |
| sr = _infer_model_meta(name) | |
| spec = ModelSpec( | |
| name=name, | |
| sr=sr, | |
| onnx_path=str(p), | |
| ) | |
| presets[_display_label(spec)] = spec | |
| # Include any additional ONNX files not in the canonical order list. | |
| for name, p in sorted(found_paths.items()): | |
| if name in ordered_names: | |
| continue | |
| sr = _infer_model_meta(name) | |
| spec = ModelSpec( | |
| name=name, | |
| sr=sr, | |
| onnx_path=str(p), | |
| ) | |
| presets[_display_label(spec)] = spec | |
| return presets | |
| MODEL_PRESETS = discover_model_presets() | |
| def _model_choice_label(spec: ModelSpec) -> str: | |
| """Return an icon-free model name; sample rate is selected separately.""" | |
| friendly_names = { | |
| "baseline": "Baseline", | |
| "dpdfnet2": "DPDFNet2", | |
| "dpdfnet4": "DPDFNet4", | |
| "dpdfnet8": "DPDFNet8", | |
| "dpdfnet2_8khz": "DPDFNet2", | |
| "dpdfnet8_8khz": "DPDFNet8", | |
| "dpdfnet2_48khz_hr": "DPDFNet2", | |
| "dpdfnet8_48khz_hr": "DPDFNet8", | |
| } | |
| return friendly_names.get( | |
| spec.name, | |
| spec.name.replace("_", " ").replace("-", " ").title(), | |
| ) | |
| def _build_model_choices_by_sr() -> Dict[int, list]: | |
| """Group discovered ONNX models by sample rate in a stable order.""" | |
| preferred_order = { | |
| 8000: [ | |
| "dpdfnet2_8khz", | |
| "dpdfnet8_8khz", | |
| ], | |
| 16000: [ | |
| "baseline", | |
| "dpdfnet2", | |
| "dpdfnet4", | |
| "dpdfnet8", | |
| ], | |
| 48000: [ | |
| "dpdfnet2_48khz_hr", | |
| "dpdfnet8_48khz_hr", | |
| ], | |
| } | |
| grouped: Dict[int, list] = {} | |
| for sr in sorted({spec.sr for spec in MODEL_PRESETS.values()}): | |
| keys_at_sr = [ | |
| key for key, spec in MODEL_PRESETS.items() | |
| if spec.sr == sr | |
| ] | |
| key_by_name = { | |
| MODEL_PRESETS[key].name: key | |
| for key in keys_at_sr | |
| } | |
| ordered_keys = [ | |
| key_by_name[name] | |
| for name in preferred_order.get(sr, []) | |
| if name in key_by_name | |
| ] | |
| ordered_keys.extend( | |
| key | |
| for key in sorted( | |
| keys_at_sr, | |
| key=lambda item: MODEL_PRESETS[item].name, | |
| ) | |
| if key not in ordered_keys | |
| ) | |
| grouped[sr] = [ | |
| (_model_choice_label(MODEL_PRESETS[key]), key) | |
| for key in ordered_keys | |
| ] | |
| return grouped | |
| MODEL_CHOICES_BY_SR = _build_model_choices_by_sr() | |
| # Keep the familiar rates first, while still supporting any additional models. | |
| AVAILABLE_SAMPLE_RATES = [ | |
| sr for sr in (8000, 16000, 48000) | |
| if MODEL_CHOICES_BY_SR.get(sr) | |
| ] | |
| AVAILABLE_SAMPLE_RATES.extend( | |
| sr for sr in sorted(MODEL_CHOICES_BY_SR) | |
| if sr not in AVAILABLE_SAMPLE_RATES | |
| ) | |
| SAMPLE_RATE_CHOICES = [ | |
| (f"{sr // 1000} kHz", str(sr)) | |
| for sr in AVAILABLE_SAMPLE_RATES | |
| ] | |
| DEFAULT_SAMPLE_RATE = ( | |
| 16000 | |
| if 16000 in AVAILABLE_SAMPLE_RATES | |
| else (AVAILABLE_SAMPLE_RATES[0] if AVAILABLE_SAMPLE_RATES else None) | |
| ) | |
| def _default_model_key_for_sr(sr: int) -> Optional[str]: | |
| """Prefer the DPDFNet2 variant for every sample-rate group.""" | |
| preferred_name = { | |
| 8000: "dpdfnet2_8khz", | |
| 16000: "dpdfnet2", | |
| 48000: "dpdfnet2_48khz_hr", | |
| }.get(sr) | |
| if preferred_name is not None: | |
| for key, spec in MODEL_PRESETS.items(): | |
| if spec.sr == sr and spec.name == preferred_name: | |
| return key | |
| for key, spec in MODEL_PRESETS.items(): | |
| if spec.sr == sr and spec.name.lower().startswith("dpdfnet2"): | |
| return key | |
| choices = MODEL_CHOICES_BY_SR.get(sr, []) | |
| return choices[0][1] if choices else None | |
| DEFAULT_MODEL_CHOICES = ( | |
| MODEL_CHOICES_BY_SR.get(DEFAULT_SAMPLE_RATE, []) | |
| if DEFAULT_SAMPLE_RATE is not None | |
| else [] | |
| ) | |
| DEFAULT_MODEL_KEY = ( | |
| _default_model_key_for_sr(DEFAULT_SAMPLE_RATE) | |
| if DEFAULT_SAMPLE_RATE is not None | |
| else None | |
| ) | |
| ATTN_RADIO_CHOICES = [ | |
| ("Gentle · 6 dB", "6"), | |
| ("Balanced · 12 dB", "12"), | |
| ("Strong · 24 dB", "24"), | |
| ("Unlimited", "unlimited"), | |
| ] | |
| DEFAULT_ATTN_CHOICE = "unlimited" | |
| # ----------------------------- | |
| # ONNX Runtime + frontend cache | |
| # ----------------------------- | |
| _SESSIONS: Dict[str, ort.InferenceSession] = {} | |
| _INIT_STATES: Dict[str, np.ndarray] = {} | |
| def resolve_model_path(local_path: str) -> str: | |
| p = Path(local_path) | |
| if p.exists(): | |
| return str(p) | |
| raise gr.Error( | |
| f"ONNX model not found at: {local_path}. " | |
| "Expected local models under ./onnx/." | |
| ) | |
| def get_ort_session(model_key: str) -> ort.InferenceSession: | |
| if model_key in _SESSIONS: | |
| return _SESSIONS[model_key] | |
| spec = MODEL_PRESETS[model_key] | |
| onnx_path = resolve_model_path(spec.onnx_path) | |
| options = ort.SessionOptions() | |
| options.intra_op_num_threads = 1 | |
| options.inter_op_num_threads = 1 | |
| sess = ort.InferenceSession( | |
| onnx_path, | |
| sess_options=options, | |
| providers=["CPUExecutionProvider"], | |
| ) | |
| _SESSIONS[model_key] = sess | |
| return sess | |
| def _load_initial_state(model_key: str, session: ort.InferenceSession) -> np.ndarray: | |
| if model_key in _INIT_STATES: | |
| return _INIT_STATES[model_key] | |
| if len(session.get_inputs()) < 2: | |
| raise gr.Error("Expected streaming ONNX model with two inputs: (spec, state).") | |
| meta = session.get_modelmeta().custom_metadata_map | |
| try: | |
| state_size = int(meta["state_size"]) | |
| erb_norm_state_size = int(meta["erb_norm_state_size"]) | |
| spec_norm_state_size = int(meta["spec_norm_state_size"]) | |
| erb_norm_init = np.array( | |
| [float(x) for x in meta["erb_norm_init"].split(",")], dtype=np.float32 | |
| ) | |
| spec_norm_init = np.array( | |
| [float(x) for x in meta["spec_norm_init"].split(",")], dtype=np.float32 | |
| ) | |
| except KeyError as exc: | |
| raise gr.Error( | |
| f"ONNX model is missing required metadata key: {exc}. " | |
| "Re-export the model to embed state initialisation metadata." | |
| ) | |
| init_state = np.zeros(state_size, dtype=np.float32) | |
| init_state[0:erb_norm_state_size] = erb_norm_init | |
| init_state[erb_norm_state_size:erb_norm_state_size + spec_norm_state_size] = spec_norm_init | |
| init_state = np.ascontiguousarray(init_state) | |
| _INIT_STATES[model_key] = init_state | |
| return init_state | |
| # ----------------------------- | |
| # STFT/iSTFT (module-free) | |
| # ----------------------------- | |
| def vorbis_window(window_len: int) -> np.ndarray: | |
| window_size_h = window_len / 2 | |
| indices = np.arange(window_len) | |
| sin = np.sin(0.5 * np.pi * (indices + 0.5) / window_size_h) | |
| window = np.sin(0.5 * np.pi * sin * sin) | |
| return window.astype(np.float32) | |
| def _infer_stft_params(model_key: str, session: ort.InferenceSession) -> Tuple[int, int, np.ndarray]: | |
| # ONNX spec input is [B, T, F, 2] (or dynamic variants). | |
| spec_shape = session.get_inputs()[0].shape | |
| freq_bins = spec_shape[-2] if len(spec_shape) >= 2 else None | |
| if isinstance(freq_bins, int) and freq_bins > 1: | |
| win_len = int((freq_bins - 1) * 2) | |
| else: | |
| # 20 ms windows for DPDFNet family. | |
| sr = MODEL_PRESETS[model_key].sr | |
| win_len = int(round(sr * 0.02)) | |
| hop = win_len // 2 | |
| win = vorbis_window(win_len) | |
| return win_len, hop, win | |
| def _preprocess_waveform(waveform: np.ndarray, win_len: int, hop: int, win: np.ndarray) -> np.ndarray: | |
| audio = np.asarray(waveform, dtype=np.float32).reshape(-1) | |
| audio_pad = np.pad(audio, (0, win_len), mode="constant") | |
| spec = librosa.stft( | |
| y=audio_pad, | |
| n_fft=win_len, | |
| hop_length=hop, | |
| win_length=win_len, | |
| window=win, | |
| center=True, | |
| pad_mode="reflect", | |
| ) | |
| spec = spec.T.astype(np.complex64, copy=False) # [T, F] | |
| spec_ri = np.stack([spec.real, spec.imag], axis=-1).astype(np.float32, copy=False) # [T, F, 2] | |
| return np.ascontiguousarray(spec_ri[None, ...], dtype=np.float32) # [1, T, F, 2] | |
| def _postprocess_spec(spec_e: np.ndarray, win_len: int, hop: int, win: np.ndarray) -> np.ndarray: | |
| spec_c = np.asarray(spec_e[0], dtype=np.float32) # [T, F, 2] | |
| spec = (spec_c[..., 0] + 1j * spec_c[..., 1]).T.astype(np.complex64, copy=False) # [F, T] | |
| waveform_e = librosa.istft( | |
| spec, | |
| hop_length=hop, | |
| win_length=win_len, | |
| window=win, | |
| center=True, | |
| length=None, | |
| ).astype(np.float32, copy=False) | |
| return np.concatenate( | |
| [waveform_e[win_len * 2 :], np.zeros(win_len * 2, dtype=np.float32)], | |
| axis=0, | |
| ) | |
| def _validate_attn_limit_db(attn_limit_db: Optional[float]) -> Optional[float]: | |
| if attn_limit_db is None: | |
| return None | |
| value = float(attn_limit_db) | |
| if np.isnan(value) or value < 0.0: | |
| raise gr.Error("Attenuation limit must be zero or greater.") | |
| return value | |
| def _apply_attn_limit( | |
| spec_noisy: np.ndarray, | |
| spec_enh: np.ndarray, | |
| attn_limit_db: Optional[float], | |
| ) -> np.ndarray: | |
| value = _validate_attn_limit_db(attn_limit_db) | |
| enhanced = np.asarray(spec_enh, dtype=np.float32) | |
| if value is None: | |
| return enhanced | |
| noisy = np.asarray(spec_noisy, dtype=np.float32) | |
| if noisy.shape != enhanced.shape: | |
| raise gr.Error( | |
| "The noisy and enhanced spectra do not have matching shapes." | |
| ) | |
| aligned_noisy = np.zeros_like(noisy, dtype=np.float32) | |
| if noisy.shape[1] > ATTN_LIMIT_NOISY_FRAME_OFFSET: | |
| aligned_noisy[:, ATTN_LIMIT_NOISY_FRAME_OFFSET:, :, :] = noisy[ | |
| :, :-ATTN_LIMIT_NOISY_FRAME_OFFSET, :, : | |
| ] | |
| alpha = float(10.0 ** (-value / 20.0)) | |
| limited = alpha * aligned_noisy + (1.0 - alpha) * enhanced | |
| return np.ascontiguousarray(limited, dtype=np.float32) | |
| # ----------------------------- | |
| # ONNX inference (non-streaming pre/post, streaming ONNX state loop) | |
| # ----------------------------- | |
| def enhance_audio_onnx( | |
| audio_mono: np.ndarray, | |
| model_key: str, | |
| attn_limit_db: Optional[float] = None, | |
| ) -> np.ndarray: | |
| sess = get_ort_session(model_key) | |
| inputs = sess.get_inputs() | |
| outputs = sess.get_outputs() | |
| if len(inputs) < 2 or len(outputs) < 2: | |
| raise gr.Error( | |
| "Expected streaming ONNX signature with 2 inputs (spec, state) and 2 outputs (spec_e, state_out)." | |
| ) | |
| in_spec_name = inputs[0].name | |
| in_state_name = inputs[1].name | |
| out_spec_name = outputs[0].name | |
| out_state_name = outputs[1].name | |
| waveform = np.asarray(audio_mono, dtype=np.float32).reshape(-1) | |
| win_len, hop, win = _infer_stft_params(model_key, sess) | |
| spec_r_np = _preprocess_waveform(waveform, win_len=win_len, hop=hop, win=win) | |
| state = _load_initial_state(model_key, sess).copy() | |
| spec_e_frames = [] | |
| num_frames = int(spec_r_np.shape[1]) | |
| for t in range(num_frames): | |
| spec_t = np.ascontiguousarray(spec_r_np[:, t : t + 1, :, :], dtype=np.float32) | |
| spec_e_t, state = sess.run( | |
| [out_spec_name, out_state_name], | |
| {in_spec_name: spec_t, in_state_name: state}, | |
| ) | |
| spec_e_frames.append(np.ascontiguousarray(spec_e_t, dtype=np.float32)) | |
| if not spec_e_frames: | |
| return waveform | |
| spec_e_np = np.concatenate(spec_e_frames, axis=1) | |
| spec_e_np = _apply_attn_limit(spec_r_np, spec_e_np, attn_limit_db) | |
| waveform_e = _postprocess_spec(spec_e_np, win_len=win_len, hop=hop, win=win) | |
| return np.asarray(waveform_e, dtype=np.float32).reshape(-1) | |
| # ----------------------------- | |
| # Audio utilities | |
| # ----------------------------- | |
| def _load_wav_from_gradio_path(path: str) -> Tuple[np.ndarray, int]: | |
| data, sr = sf.read(path, always_2d=True) | |
| data = data.astype(np.float32, copy=False) | |
| return data, int(sr) | |
| def _to_mono(x: np.ndarray) -> Tuple[np.ndarray, int]: | |
| if x.ndim == 1: | |
| return x.astype(np.float32, copy=False), 1 | |
| if x.shape[1] == 1: | |
| return x[:, 0], 1 | |
| return x.mean(axis=1), int(x.shape[1]) | |
| def _resample(y: np.ndarray, sr_in: int, sr_out: int) -> np.ndarray: | |
| if sr_in == sr_out: | |
| return y | |
| return librosa.resample(y, orig_sr=sr_in, target_sr=sr_out).astype(np.float32, copy=False) | |
| def _match_length(y: np.ndarray, target_len: int) -> np.ndarray: | |
| if len(y) == target_len: | |
| return y | |
| if len(y) > target_len: | |
| return y[:target_len] | |
| out = np.zeros((target_len,), dtype=y.dtype) | |
| out[: len(y)] = y | |
| return out | |
| def _save_wav(y: np.ndarray, sr: int, prefix: str) -> str: | |
| tmp = tempfile.NamedTemporaryFile(prefix=prefix, suffix=".wav", delete=False) | |
| tmp.close() | |
| sf.write(tmp.name, y, sr) | |
| return tmp.name | |
| def _spectrogram_image(y: np.ndarray, sr: int) -> Image.Image: | |
| win_length = max(256, int(0.032 * sr)) | |
| hop_length = max(64, int(0.008 * sr)) | |
| n_fft = 1 << (int(math.ceil(math.log2(win_length)))) | |
| S = librosa.stft(y, n_fft=n_fft, hop_length=hop_length, win_length=win_length, center=False) | |
| S_db = librosa.amplitude_to_db(np.abs(S) + 1e-10, ref=np.max) | |
| fig, ax = plt.subplots(figsize=(8.4, 3.2)) | |
| fig.patch.set_facecolor("#17191c") | |
| ax.set_facecolor("#17191c") | |
| ax.imshow( | |
| S_db, | |
| origin="lower", | |
| aspect="auto", | |
| cmap=SPECTROGRAM_CMAP, | |
| vmin=SPECTROGRAM_DB_MIN, | |
| vmax=SPECTROGRAM_DB_MAX, | |
| interpolation="nearest", | |
| ) | |
| ax.set_axis_off() | |
| fig.subplots_adjust(left=0, right=1, top=1, bottom=0) | |
| buf = io.BytesIO() | |
| fig.savefig(buf, format="png", dpi=160) | |
| plt.close(fig) | |
| buf.seek(0) | |
| return Image.open(buf) | |
| # ----------------------------- | |
| # Main pipeline | |
| # ----------------------------- | |
| def _resolve_attn_choice(attn_choice: Optional[str]) -> Optional[float]: | |
| if not attn_choice or attn_choice == "unlimited": | |
| return None | |
| return _validate_attn_limit_db(float(attn_choice)) | |
| def run_enhancement( | |
| source: str, | |
| mic_path: Optional[str], | |
| file_path: Optional[str], | |
| model_key: str, | |
| attn_choice: Optional[str], | |
| ): | |
| if not MODEL_PRESETS: | |
| raise gr.Error("No ONNX models found under ./onnx/. Add models and retry.") | |
| chosen_path = mic_path if source == "Microphone" else file_path | |
| if not chosen_path: | |
| raise gr.Error("Please provide audio either from the microphone or by uploading a file.") | |
| x, sr_orig = _load_wav_from_gradio_path(chosen_path) | |
| y_mono, n_ch = _to_mono(x) | |
| max_samples = int(MAX_SECONDS * sr_orig) | |
| was_trimmed = len(y_mono) > max_samples | |
| if was_trimmed: | |
| y_mono = y_mono[:max_samples] | |
| dur = len(y_mono) / float(sr_orig) | |
| spec = MODEL_PRESETS[model_key] | |
| sr_model = spec.sr | |
| attn_limit_db = _resolve_attn_choice(attn_choice) | |
| y_model = _resample(y_mono, sr_orig, sr_model) | |
| y_enh_model = enhance_audio_onnx(y_model, model_key, attn_limit_db) | |
| y_enh = _resample(y_enh_model, sr_model, sr_orig) | |
| y_enh = _match_length(y_enh, len(y_mono)) | |
| noisy_out = _save_wav(y_mono, sr_orig, prefix="noisy_mono_") | |
| enh_out = _save_wav(y_enh, sr_orig, prefix="enhanced_") | |
| noisy_img = _spectrogram_image(y_mono, sr_orig) | |
| enh_img = _spectrogram_image(y_enh, sr_orig) | |
| details = [spec.name, f"{attn_limit_db:g} dB" if attn_limit_db is not None else "unlimited"] | |
| if was_trimmed: | |
| details.append("first 10s") | |
| status = "✓ Enhanced · " + " · ".join(details) | |
| return noisy_out, enh_out, noisy_img, enh_img, status | |
| def set_source_visibility(source: str): | |
| return ( | |
| gr.update(visible=(source == "Microphone")), | |
| gr.update(visible=(source == "Upload")), | |
| ) | |
| def set_model_choices(sample_rate: str): | |
| """Show only models that match the selected sample rate.""" | |
| try: | |
| sr = int(sample_rate) | |
| except (TypeError, ValueError): | |
| sr = DEFAULT_SAMPLE_RATE | |
| if sr is None: | |
| return gr.update(choices=[], value=None) | |
| choices = MODEL_CHOICES_BY_SR.get(sr, []) | |
| return gr.update( | |
| choices=choices, | |
| value=_default_model_key_for_sr(sr), | |
| ) | |
| # ----------------------------- | |
| # UI — Wide guided flow | |
| # ----------------------------- | |
| THEME = gr.themes.Base( | |
| primary_hue="orange", | |
| secondary_hue="slate", | |
| neutral_hue="slate", | |
| font=["Aptos", "Segoe UI Variable", "Segoe UI", "Helvetica Neue", "sans-serif"], | |
| ).set( | |
| body_background_fill="#f4f4f1", | |
| body_background_fill_dark="#f4f4f1", | |
| body_text_color="#17191c", | |
| body_text_color_dark="#17191c", | |
| body_text_color_subdued="#505760", | |
| body_text_color_subdued_dark="#505760", | |
| background_fill_primary="#ffffff", | |
| background_fill_primary_dark="#ffffff", | |
| background_fill_secondary="#f8f8f6", | |
| background_fill_secondary_dark="#f8f8f6", | |
| border_color_primary="#d6d8d3", | |
| border_color_primary_dark="#d6d8d3", | |
| block_background_fill="#ffffff", | |
| block_background_fill_dark="#ffffff", | |
| block_border_color="#d6d8d3", | |
| block_border_color_dark="#d6d8d3", | |
| block_label_background_fill="#ffffff", | |
| block_label_background_fill_dark="#ffffff", | |
| block_label_text_color="#2f3439", | |
| block_label_text_color_dark="#2f3439", | |
| block_info_text_color="#505760", | |
| block_info_text_color_dark="#505760", | |
| input_background_fill="#ffffff", | |
| input_background_fill_dark="#ffffff", | |
| input_background_fill_focus="#ffffff", | |
| input_background_fill_focus_dark="#ffffff", | |
| input_border_color="#c8cbc6", | |
| input_border_color_dark="#c8cbc6", | |
| input_border_color_focus="#c95f20", | |
| input_border_color_focus_dark="#c95f20", | |
| input_placeholder_color="#68707a", | |
| input_placeholder_color_dark="#68707a", | |
| checkbox_label_background_fill="#ffffff", | |
| checkbox_label_background_fill_dark="#ffffff", | |
| checkbox_label_background_fill_hover="#faf7f4", | |
| checkbox_label_background_fill_hover_dark="#faf7f4", | |
| checkbox_label_background_fill_selected="#fff4ec", | |
| checkbox_label_background_fill_selected_dark="#fff4ec", | |
| checkbox_label_border_color="#cfd2cd", | |
| checkbox_label_border_color_dark="#cfd2cd", | |
| checkbox_label_border_color_hover="#b8bcb6", | |
| checkbox_label_border_color_hover_dark="#b8bcb6", | |
| checkbox_label_border_color_selected="#c95f20", | |
| checkbox_label_border_color_selected_dark="#c95f20", | |
| checkbox_label_text_color="#24282d", | |
| checkbox_label_text_color_dark="#24282d", | |
| checkbox_label_text_color_selected="#17191c", | |
| checkbox_label_text_color_selected_dark="#17191c", | |
| button_primary_background_fill="#d76522", | |
| button_primary_background_fill_hover="#bd561a", | |
| button_primary_background_fill_dark="#d76522", | |
| button_primary_background_fill_hover_dark="#bd561a", | |
| button_primary_border_color="#d76522", | |
| button_primary_border_color_dark="#d76522", | |
| button_primary_text_color="#ffffff", | |
| button_primary_text_color_dark="#ffffff", | |
| accordion_text_color="#2f3439", | |
| accordion_text_color_dark="#2f3439", | |
| ) | |
| CSS = r""" | |
| :root { | |
| --ink: #17191c; | |
| --muted: #505760; | |
| --line: #d6d8d3; | |
| --line-strong: #c4c7c1; | |
| --paper: #ffffff; | |
| --soft: #f4f4f1; | |
| --accent: #d76522; | |
| --accent-soft: #f1cdb6; | |
| --option-soft: #f6eee8; | |
| --option-hover: #f3dfd1; | |
| } | |
| html, body { | |
| min-width: 100%; | |
| background: var(--soft) !important; | |
| color-scheme: light !important; | |
| } | |
| * { box-sizing: border-box; } | |
| .gradio-container { | |
| width: min(1460px, calc(100vw - 32px)) !important; | |
| max-width: none !important; | |
| margin: 0 auto !important; | |
| padding: 18px 0 30px !important; | |
| font-family: Aptos, "Segoe UI Variable", "Segoe UI", sans-serif !important; | |
| color: var(--ink) !important; | |
| } | |
| .gradio-container > * { | |
| width: 100% !important; | |
| max-width: none !important; | |
| } | |
| #app-header { | |
| margin: 0 0 14px; | |
| text-align: left; | |
| } | |
| #app-header h1 { | |
| margin: 0; | |
| color: var(--ink); | |
| font-size: clamp(28px, 3vw, 38px); | |
| font-weight: 760; | |
| line-height: 1.02; | |
| letter-spacing: -.04em; | |
| } | |
| #app-header p { | |
| margin: 5px 0 0; | |
| color: var(--muted); | |
| font-size: 13px; | |
| font-weight: 500; | |
| } | |
| .workspace-shell, | |
| .results-row { | |
| width: 100% !important; | |
| gap: 14px !important; | |
| align-items: stretch !important; | |
| } | |
| .workspace-shell > div, | |
| .results-row > div { | |
| min-width: 0 !important; | |
| } | |
| .flow-card, | |
| .result-card { | |
| padding: 16px !important; | |
| border: 1px solid var(--line) !important; | |
| border-radius: 14px !important; | |
| background: var(--paper) !important; | |
| box-shadow: 0 1px 2px rgba(24, 27, 30, .04) !important; | |
| } | |
| .section-heading { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| margin-bottom: 12px; | |
| color: var(--ink); | |
| font-size: 14px; | |
| font-weight: 760; | |
| } | |
| .step-number { | |
| display: inline-grid; | |
| width: 22px; | |
| height: 22px; | |
| flex: 0 0 22px; | |
| place-items: center; | |
| border-radius: 999px; | |
| background: #f3dfd1; | |
| color: #9d4614; | |
| font-size: 11px; | |
| font-weight: 800; | |
| } | |
| .field-title { | |
| margin: 12px 0 6px; | |
| color: #2d3237; | |
| font-size: 11px; | |
| font-weight: 760; | |
| letter-spacing: .01em; | |
| } | |
| .field-title:first-of-type { margin-top: 0; } | |
| /* Gradio 6 renders Radio choices inside div.wrap. */ | |
| .source-tabs .wrap, | |
| .option-grid .wrap { | |
| display: grid !important; | |
| gap: 6px !important; | |
| width: 100% !important; | |
| min-width: 0 !important; | |
| overflow: visible !important; | |
| } | |
| .source-tabs .wrap { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)) !important; | |
| } | |
| .option-grid .wrap, | |
| .model-grid .wrap { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)) !important; | |
| } | |
| .sample-rate-grid .wrap { | |
| grid-template-columns: repeat(3, minmax(0, 1fr)) !important; | |
| } | |
| .attn-grid .wrap { | |
| grid-template-columns: repeat(4, minmax(0, 1fr)) !important; | |
| gap: 6px !important; | |
| } | |
| .source-tabs .wrap label, | |
| .option-grid .wrap label { | |
| position: relative; | |
| display: flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| width: 100% !important; | |
| min-width: 0 !important; | |
| margin: 0 !important; | |
| border: 1px solid #dfcfc3 !important; | |
| border-radius: 9px !important; | |
| background: var(--option-soft) !important; | |
| color: #23272c !important; | |
| box-shadow: none !important; | |
| cursor: pointer; | |
| transition: border-color .14s ease, background-color .14s ease, box-shadow .14s ease; | |
| } | |
| .source-tabs .wrap label { | |
| min-height: 36px; | |
| padding: 7px 10px !important; | |
| } | |
| .option-grid .wrap label { | |
| min-height: 42px; | |
| padding: 8px 9px !important; | |
| } | |
| .model-grid .wrap label { | |
| min-height: 40px; | |
| justify-content: flex-start !important; | |
| padding: 8px 10px !important; | |
| text-align: left !important; | |
| } | |
| .attn-grid .wrap label { | |
| min-height: 40px; | |
| padding: 6px 4px !important; | |
| } | |
| .source-tabs .wrap label:hover, | |
| .option-grid .wrap label:hover { | |
| border-color: #c99a79 !important; | |
| background: var(--option-hover) !important; | |
| } | |
| .source-tabs .wrap label.selected, | |
| .option-grid .wrap label.selected, | |
| .source-tabs .wrap label:has(input:checked), | |
| .option-grid .wrap label:has(input:checked) { | |
| border-color: var(--accent) !important; | |
| background: var(--accent-soft) !important; | |
| box-shadow: inset 0 0 0 1px var(--accent) !important; | |
| } | |
| .source-tabs .wrap label span, | |
| .option-grid .wrap label span, | |
| .source-tabs .wrap label, | |
| .option-grid .wrap label { | |
| color: #23272c !important; | |
| opacity: 1 !important; | |
| font-size: 12px !important; | |
| font-weight: 700 !important; | |
| line-height: 1.2 !important; | |
| } | |
| .attn-grid .wrap label, | |
| .attn-grid .wrap label span { | |
| white-space: nowrap !important; | |
| font-size: 10.5px !important; | |
| letter-spacing: -.015em !important; | |
| text-align: center !important; | |
| } | |
| /* Gradio may wrap the option text in an inner span. Give that span the | |
| full tile width so every attenuation label, including Unlimited, is | |
| optically centered rather than centered only within its text width. */ | |
| .attn-grid .wrap label > span, | |
| .attn-grid .wrap label span { | |
| display: flex !important; | |
| width: 100% !important; | |
| min-width: 0 !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| text-align: center !important; | |
| } | |
| /* Two clear, restrained motion languages ----------------------------------- | |
| Model capability: a rotating conic-gradient lives behind an inset surface, | |
| so only a short illuminated segment is visible on the true perimeter. | |
| Noise reduction: one centered dotted sine-wave drifts horizontally behind | |
| the selected option. Stronger reduction makes it calmer and fainter. | |
| */ | |
| .model-grid .wrap label, | |
| .attn-grid .wrap label { | |
| position: relative; | |
| overflow: hidden !important; | |
| isolation: isolate; | |
| } | |
| /* -------------------------------------------------------------------------- | |
| MODEL CAPABILITY — text shimmer | |
| -------------------------------------------------------------------------- */ | |
| .model-grid .wrap label { | |
| --model-fill: rgb(215 101 34 / .030); | |
| --model-selected-fill: #f5d8c5; | |
| --model-glow: .06; | |
| --shimmer-red: rgb(214 86 39); | |
| --shimmer-red-soft: rgb(235 141 113); | |
| background: var(--model-fill) !important; | |
| border: 1px solid #dfcfc3 !important; | |
| } | |
| /* Capability changes only the shimmer warmth/glow, not the speed. */ | |
| .model-grid .wrap label:has(input[value*="baseline"]) { | |
| --model-glow: .050; | |
| --shimmer-red: rgb(207 112 82); | |
| --shimmer-red-soft: rgb(236 198 184); | |
| } | |
| .model-grid .wrap label:has(input[value*="dpdfnet2"]) { | |
| --model-glow: .090; | |
| --shimmer-red: rgb(212 97 61); | |
| --shimmer-red-soft: rgb(241 183 159); | |
| } | |
| .model-grid .wrap label:has(input[value*="dpdfnet4"]) { | |
| --model-glow: .145; | |
| --shimmer-red: rgb(217 77 44); | |
| --shimmer-red-soft: rgb(242 152 123); | |
| } | |
| .model-grid .wrap label:has(input[value*="dpdfnet8"]) { | |
| --model-glow: .210; | |
| --shimmer-red: rgb(219 55 28); | |
| --shimmer-red-soft: rgb(246 123 93); | |
| } | |
| .model-grid .wrap label > span { | |
| position: relative; | |
| z-index: 3; | |
| color: #23272c !important; | |
| } | |
| .model-grid .wrap label:hover { | |
| border-color: #c99a79 !important; | |
| background: rgb(215 101 34 / .060) !important; | |
| } | |
| .model-grid .wrap label.selected, | |
| .model-grid .wrap label:has(input:checked) { | |
| border-color: rgb(215 101 34 / .54) !important; | |
| background: var(--model-selected-fill) !important; | |
| box-shadow: | |
| inset 0 0 0 1px rgb(255 255 255 / .30), | |
| 0 3px 12px rgb(215 101 34 / calc(var(--model-glow) * .34)) !important; | |
| } | |
| /* Selected model names get a moving light pass. The shimmer always runs at the | |
| same speed; capability is expressed only by warmer red tones and stronger glow. */ | |
| .model-grid .wrap label.selected > span, | |
| .model-grid .wrap label:has(input:checked) > span { | |
| color: transparent !important; | |
| background-image: linear-gradient( | |
| 100deg, | |
| #5c2d1b 0%, | |
| #7b3b23 18%, | |
| var(--shimmer-red) 34%, | |
| #fffaf7 48%, | |
| #ffffff 50%, | |
| #fff3ee 54%, | |
| var(--shimmer-red-soft) 67%, | |
| var(--shimmer-red) 82%, | |
| #6b321d 100% | |
| ); | |
| background-size: 220% 100%; | |
| background-position: 130% 50%; | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| filter: drop-shadow(0 0 3px rgb(215 101 34 / calc(var(--model-glow) * .58))); | |
| animation: model-text-shimmer 2.4s linear infinite; | |
| } | |
| @keyframes model-text-shimmer { | |
| from { background-position: 130% 50%; } | |
| to { background-position: -40% 50%; } | |
| } | |
| /* -------------------------------------------------------------------------- | |
| NOISE REDUCTION — one calm dotted wave | |
| -------------------------------------------------------------------------- */ | |
| .attn-grid .wrap label { | |
| --reduction-fill: rgb(215 101 34 / .032); | |
| --reduction-selected-fill: rgb(215 101 34 / .098); | |
| --wave-opacity: .34; | |
| --wave-speed: 5.2s; | |
| background: var(--reduction-fill) !important; | |
| } | |
| /* More reduction => less visible and less active background motion. */ | |
| .attn-grid .wrap label:nth-child(1) { | |
| --reduction-fill: rgb(215 101 34 / .032); | |
| --reduction-selected-fill: rgb(215 101 34 / .100); | |
| --wave-opacity: .48; | |
| --wave-speed: 4.7s; | |
| } | |
| .attn-grid .wrap label:nth-child(2) { | |
| --reduction-fill: rgb(215 101 34 / .038); | |
| --reduction-selected-fill: rgb(215 101 34 / .112); | |
| --wave-opacity: .36; | |
| --wave-speed: 6.4s; | |
| } | |
| .attn-grid .wrap label:nth-child(3) { | |
| --reduction-fill: rgb(215 101 34 / .044); | |
| --reduction-selected-fill: rgb(215 101 34 / .124); | |
| --wave-opacity: .25; | |
| --wave-speed: 8.5s; | |
| } | |
| .attn-grid .wrap label:nth-child(4) { | |
| --reduction-fill: rgb(215 101 34 / .050); | |
| --reduction-selected-fill: rgb(215 101 34 / .136); | |
| --wave-opacity: .15; | |
| --wave-speed: 11.5s; | |
| } | |
| .attn-grid .wrap label::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| z-index: 0; | |
| pointer-events: none; | |
| opacity: 0; | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='30' viewBox='0 0 160 30'%3E%3Cg fill='%23231f1c'%3E%3Ccircle cx='0' cy='15.00' r='1.2'/%3E%3Ccircle cx='10' cy='12.13' r='1.2'/%3E%3Ccircle cx='20' cy='9.70' r='1.2'/%3E%3Ccircle cx='30' cy='8.08' r='1.2'/%3E%3Ccircle cx='40' cy='7.50' r='1.2'/%3E%3Ccircle cx='50' cy='8.08' r='1.2'/%3E%3Ccircle cx='60' cy='9.70' r='1.2'/%3E%3Ccircle cx='70' cy='12.13' r='1.2'/%3E%3Ccircle cx='80' cy='15.00' r='1.2'/%3E%3Ccircle cx='90' cy='17.87' r='1.2'/%3E%3Ccircle cx='100' cy='20.30' r='1.2'/%3E%3Ccircle cx='110' cy='21.92' r='1.2'/%3E%3Ccircle cx='120' cy='22.50' r='1.2'/%3E%3Ccircle cx='130' cy='21.92' r='1.2'/%3E%3Ccircle cx='140' cy='20.30' r='1.2'/%3E%3Ccircle cx='150' cy='17.87' r='1.2'/%3E%3Ccircle cx='160' cy='15.00' r='1.2'/%3E%3C/g%3E%3C/svg%3E"); | |
| background-repeat: repeat-x; | |
| background-size: 160px 30px; | |
| background-position: 0 50%; | |
| } | |
| .attn-grid .wrap label::after { | |
| content: none; | |
| } | |
| .attn-grid .wrap label > span { | |
| position: relative; | |
| z-index: 2; | |
| } | |
| .attn-grid .wrap label:hover { | |
| background: rgb(215 101 34 / .075) !important; | |
| } | |
| .attn-grid .wrap label.selected, | |
| .attn-grid .wrap label:has(input:checked) { | |
| border-color: var(--accent) !important; | |
| background: var(--reduction-selected-fill) !important; | |
| box-shadow: | |
| inset 0 0 0 1px rgb(215 101 34 / .24), | |
| 0 3px 10px rgb(215 101 34 / .045) !important; | |
| } | |
| .attn-grid .wrap label.selected::before, | |
| .attn-grid .wrap label:has(input:checked)::before { | |
| opacity: var(--wave-opacity); | |
| animation: reduction-wave var(--wave-speed) linear infinite; | |
| } | |
| @keyframes reduction-wave { | |
| from { background-position: 0 50%; } | |
| to { background-position: 160px 50%; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .model-grid .wrap label.selected > span, | |
| .model-grid .wrap label:has(input:checked) > span, | |
| .attn-grid .wrap label::before { | |
| animation: none !important; | |
| } | |
| .model-grid .wrap label.selected > span, | |
| .model-grid .wrap label:has(input:checked) > span { | |
| background-position: 50% 50%; | |
| } | |
| .attn-grid .wrap label.selected::before, | |
| .attn-grid .wrap label:has(input:checked)::before { | |
| opacity: calc(var(--wave-opacity) * .82); | |
| background-position: 40px 50%; | |
| } | |
| } | |
| .source-tabs input[type="radio"], | |
| .option-grid input[type="radio"] { | |
| position: absolute !important; | |
| width: 1px !important; | |
| height: 1px !important; | |
| opacity: 0 !important; | |
| pointer-events: none !important; | |
| } | |
| .audio-input, | |
| .audio-output, | |
| .spec-card { | |
| overflow: hidden; | |
| border: 1px solid var(--line) !important; | |
| border-radius: 10px !important; | |
| background: #fafaf8 !important; | |
| box-shadow: none !important; | |
| } | |
| .audio-input { min-height: 205px; } | |
| .audio-output { min-height: 108px; } | |
| .audio-input .wrap, | |
| .audio-output .wrap { | |
| border: 0 !important; | |
| background: transparent !important; | |
| } | |
| .audio-input .upload-container, | |
| .audio-input .upload-container *, | |
| .audio-input .empty, | |
| .audio-input .empty * { | |
| color: #444b53 !important; | |
| opacity: 1 !important; | |
| } | |
| .audio-input svg, | |
| .audio-output svg { | |
| color: #606871 !important; | |
| stroke: #606871 !important; | |
| } | |
| .gradio-container label, | |
| .gradio-container .label-wrap, | |
| .gradio-container .label-wrap span, | |
| .gradio-container .block-label, | |
| .gradio-container .block-label span { | |
| color: #30353a !important; | |
| opacity: 1 !important; | |
| } | |
| #run_btn { | |
| min-height: 42px; | |
| margin-top: 12px; | |
| border: 1px solid var(--accent) !important; | |
| border-radius: 9px !important; | |
| background: var(--accent) !important; | |
| color: #ffffff !important; | |
| box-shadow: none !important; | |
| font-size: 13px !important; | |
| font-weight: 780 !important; | |
| } | |
| #run_btn:hover { | |
| background: #bd561a !important; | |
| border-color: #bd561a !important; | |
| } | |
| #status_md { | |
| min-height: 18px; | |
| margin-top: 6px; | |
| color: #4f565e !important; | |
| font-size: 11px; | |
| font-weight: 600; | |
| text-align: center; | |
| } | |
| #status_md p { margin: 0; color: inherit !important; } | |
| .results-title { | |
| margin: 18px 2px 8px; | |
| color: var(--ink); | |
| font-size: 15px; | |
| font-weight: 760; | |
| letter-spacing: -.015em; | |
| } | |
| .result-tag { | |
| margin-bottom: 6px; | |
| color: #525960; | |
| font-size: 11px; | |
| font-weight: 760; | |
| } | |
| .result-tag.after { color: #a64a16; } | |
| .spec-card img { border-radius: 9px !important; } | |
| .gradio-container .accordion { | |
| margin-top: 8px !important; | |
| border: 1px solid var(--line) !important; | |
| border-radius: 10px !important; | |
| background: var(--paper) !important; | |
| box-shadow: none !important; | |
| } | |
| .gradio-container .accordion > .label-wrap { | |
| padding: 9px 11px !important; | |
| color: #3f464d !important; | |
| font-size: 11px !important; | |
| font-weight: 720 !important; | |
| } | |
| /* Compact Gradio's internal spacing without changing the workflow. */ | |
| .flow-card > .gap, | |
| .result-card > .gap { | |
| gap: 8px !important; | |
| } | |
| .audio-input .upload-container, | |
| .audio-input .empty { | |
| min-height: 165px !important; | |
| padding: 12px !important; | |
| } | |
| .audio-input .upload-container p, | |
| .audio-input .empty p { | |
| margin: 2px 0 !important; | |
| font-size: 12px !important; | |
| line-height: 1.3 !important; | |
| } | |
| .audio-input .upload-container svg, | |
| .audio-input .empty svg { | |
| width: 23px !important; | |
| height: 23px !important; | |
| } | |
| .audio-output audio { | |
| min-height: 38px !important; | |
| } | |
| @media (max-width: 980px) { | |
| .gradio-container { | |
| width: min(100% - 20px, 960px) !important; | |
| padding: 16px 0 28px !important; | |
| } | |
| .workspace-shell { | |
| flex-direction: column !important; | |
| } | |
| } | |
| @media (max-width: 620px) { | |
| .gradio-container { width: calc(100% - 20px) !important; } | |
| .flow-card, .result-card { padding: 13px !important; } | |
| .option-grid .wrap { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; } | |
| .sample-rate-grid .wrap { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; } | |
| .attn-grid .wrap { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; } | |
| #app-header h1 { font-size: 29px; } | |
| } | |
| """ | |
| HEADER_HTML = """ | |
| <div id="app-header"> | |
| <h1>DPDFNet</h1> | |
| </div> | |
| """ | |
| try: | |
| _GRADIO_MAJOR = int(gr.__version__.split(".", 1)[0]) | |
| except (AttributeError, ValueError): | |
| _GRADIO_MAJOR = 5 | |
| _BLOCKS_KWARGS = { | |
| "title": "DPDFNet Speech Enhancement", | |
| "fill_width": True, | |
| } | |
| _LAUNCH_KWARGS = {} | |
| if _GRADIO_MAJOR >= 6: | |
| _LAUNCH_KWARGS.update(theme=THEME, css=CSS) | |
| else: | |
| _BLOCKS_KWARGS.update(theme=THEME, css=CSS) | |
| with gr.Blocks(**_BLOCKS_KWARGS) as demo: | |
| gr.HTML(HEADER_HTML) | |
| with gr.Row(elem_classes=["workspace-shell"], equal_height=False): | |
| with gr.Column(scale=7, min_width=560, elem_classes=["flow-card"]): | |
| gr.HTML( | |
| '<div class="section-heading"><span class="step-number">1</span>Add audio</div>' | |
| ) | |
| source = gr.Radio( | |
| choices=[("↑ Upload", "Upload"), ("● Record", "Microphone")], | |
| value="Upload", | |
| show_label=False, | |
| container=False, | |
| elem_classes=["source-tabs"], | |
| ) | |
| file_audio = gr.Audio( | |
| sources=["upload"], | |
| type="filepath", | |
| format="wav", | |
| label="Audio file", | |
| visible=True, | |
| buttons=["download"], | |
| elem_classes=["audio-input"], | |
| ) | |
| mic_audio = gr.Audio( | |
| sources=["microphone"], | |
| type="filepath", | |
| format="wav", | |
| label="Record", | |
| visible=False, | |
| buttons=["download"], | |
| elem_classes=["audio-input"], | |
| ) | |
| with gr.Column(scale=5, min_width=470, elem_classes=["flow-card"]): | |
| gr.HTML( | |
| '<div class="section-heading"><span class="step-number">2</span>Enhance</div>' | |
| ) | |
| gr.HTML('<div class="field-title">Sample rate</div>') | |
| sample_rate = gr.Radio( | |
| choices=SAMPLE_RATE_CHOICES, | |
| value=str(DEFAULT_SAMPLE_RATE) if DEFAULT_SAMPLE_RATE is not None else None, | |
| show_label=False, | |
| container=False, | |
| interactive=True, | |
| elem_classes=["option-grid", "sample-rate-grid"], | |
| ) | |
| gr.HTML('<div class="field-title">Model</div>') | |
| model_key = gr.Radio( | |
| choices=DEFAULT_MODEL_CHOICES, | |
| value=DEFAULT_MODEL_KEY, | |
| show_label=False, | |
| container=False, | |
| interactive=True, | |
| elem_classes=["option-grid", "model-grid"], | |
| ) | |
| gr.HTML('<div class="field-title">Noise reduction</div>') | |
| attn_choice = gr.Radio( | |
| choices=ATTN_RADIO_CHOICES, | |
| value=DEFAULT_ATTN_CHOICE, | |
| show_label=False, | |
| container=False, | |
| interactive=True, | |
| elem_classes=["option-grid", "attn-grid"], | |
| ) | |
| run_btn = gr.Button( | |
| "Remove noise", | |
| variant="primary", | |
| elem_id="run_btn", | |
| ) | |
| status = gr.Markdown("Ready", elem_id="status_md") | |
| gr.HTML('<div class="results-title">3. Compare</div>') | |
| with gr.Row(equal_height=True, elem_classes=["results-row"]): | |
| with gr.Column(elem_classes=["result-card"]): | |
| gr.HTML('<div class="result-tag">Original</div>') | |
| out_noisy = gr.Audio( | |
| label="Before", | |
| interactive=False, | |
| format="wav", | |
| buttons=["download"], | |
| elem_classes=["audio-output"], | |
| ) | |
| with gr.Column(elem_classes=["result-card"]): | |
| gr.HTML('<div class="result-tag after">Enhanced</div>') | |
| out_enh = gr.Audio( | |
| label="After", | |
| interactive=False, | |
| format="wav", | |
| buttons=["download"], | |
| elem_classes=["audio-output"], | |
| ) | |
| with gr.Accordion("View spectrograms", open=False): | |
| with gr.Row(): | |
| img_noisy = gr.Image( | |
| label="Original", | |
| elem_classes=["spec-card"], | |
| ) | |
| img_enh = gr.Image( | |
| label="Enhanced", | |
| elem_classes=["spec-card"], | |
| ) | |
| source.change( | |
| fn=set_source_visibility, | |
| inputs=source, | |
| outputs=[mic_audio, file_audio], | |
| ) | |
| sample_rate.change( | |
| fn=set_model_choices, | |
| inputs=sample_rate, | |
| outputs=model_key, | |
| ) | |
| run_btn.click( | |
| fn=run_enhancement, | |
| inputs=[source, mic_audio, file_audio, model_key, attn_choice], | |
| outputs=[out_noisy, out_enh, img_noisy, img_enh, status], | |
| api_name="enhance", | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue(max_size=32).launch(**_LAUNCH_KWARGS) | |