Spaces:
Running on Zero
Running on Zero
Vansh Chugh commited on
Commit ·
1a3ce68
1
Parent(s): 84c9833
initial deploy
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +12 -0
- README.md +17 -4
- app.py +209 -0
- conf/conf_FxGenerator_Public.yaml +71 -0
- conf/conf_FxProcessor_Public.yaml +40 -0
- diff_params/__init__.py +0 -0
- diff_params/edm_multitrack_embs.py +432 -0
- inference/__init__.py +0 -0
- inference/inference.py +517 -0
- inference/sampler.py +29 -0
- inference/sampler_euler_heun_multitrack.py +302 -0
- inference/validator_FxProcessor.py +436 -0
- networks/MLP_CLAP_regressor.py +23 -0
- networks/__init__.py +0 -0
- networks/blackbox_TCN.py +346 -0
- networks/dit_multitrack.py +488 -0
- networks/transformer.py +932 -0
- requirements.txt +15 -0
- utils/MSS_loss.py +260 -0
- utils/__init__.py +0 -0
- utils/collators.py +262 -0
- utils/common_audioeffects.py +1729 -0
- utils/data_utils.py +190 -0
- utils/dnnlib/__init__.py +8 -0
- utils/dnnlib/util.py +491 -0
- utils/evaluation/KAD_evaluate.py +307 -0
- utils/evaluation/dist_metrics.py +971 -0
- utils/evaluation/dist_metrics_multitrack.py +619 -0
- utils/evaluation/pairwise_metrics.py +958 -0
- utils/feature_extractors/AF_features_embedding.py +166 -0
- utils/feature_extractors/__init__.py +0 -0
- utils/feature_extractors/audio_features.py +76 -0
- utils/feature_extractors/dsp_features.py +141 -0
- utils/feature_extractors/fx_encoder_plus_plus.py +82 -0
- utils/feature_extractors/load_features.py +260 -0
- utils/feature_extractors/networks/__init__.py +2 -0
- utils/feature_extractors/networks/architectures.py +163 -0
- utils/feature_extractors/networks/configs.yaml +14 -0
- utils/feature_extractors/networks/network_utils.py +254 -0
- utils/feature_extractors/networks/pytorch_utils.py +256 -0
- utils/fx_normalization/__init__.py +0 -0
- utils/fx_normalization/features.npy +3 -0
- utils/fx_normalization/fxnorm.py +154 -0
- utils/fx_normalization/fxnorm_v2_public.py +142 -0
- utils/fxencoder_plusplus/__init__.py +12 -0
- utils/fxencoder_plusplus/model.py +676 -0
- utils/laion_clap/__init__.py +0 -0
- utils/laion_clap/clap_module/__init__.py +8 -0
- utils/laion_clap/clap_module/bert.py +32 -0
- utils/laion_clap/clap_module/bpe_simple_vocab_16e6.txt.gz +3 -0
.gitignore
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.DS_Store
|
| 4 |
+
*.pyo
|
| 5 |
+
*.egg-info/
|
| 6 |
+
.eggs/
|
| 7 |
+
dist/
|
| 8 |
+
build/
|
| 9 |
+
*.log
|
| 10 |
+
outputs/
|
| 11 |
+
.hydra/
|
| 12 |
+
wandb/
|
README.md
CHANGED
|
@@ -1,15 +1,28 @@
|
|
| 1 |
---
|
| 2 |
title: MEGAMI
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: yellow
|
| 5 |
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version:
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: cc-by-nc-sa-4.0
|
| 12 |
short_description: Automatic Mixing via Generative Model of Effect Embeddings
|
| 13 |
---
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: MEGAMI
|
| 3 |
+
emoji: 🎚
|
| 4 |
colorFrom: yellow
|
| 5 |
colorTo: gray
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.28.0
|
| 8 |
+
python_version: "3.10"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: cc-by-nc-sa-4.0
|
| 12 |
short_description: Automatic Mixing via Generative Model of Effect Embeddings
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# MEGAMI — Automatic Music Mixing
|
| 16 |
+
|
| 17 |
+
Upload 2–6 **dry (unprocessed) mono/stereo instrument stems** (at least ~12 s each) and MEGAMI will generate a professionally mixed stereo output.
|
| 18 |
+
|
| 19 |
+
**Requirements:**
|
| 20 |
+
- Each track must be at least 11.9 s long (525 312 samples @ 44 100 Hz)
|
| 21 |
+
- Supported formats: WAV, FLAC, MP3 (auto-resampled to 44 100 Hz)
|
| 22 |
+
- At least 2 tracks required
|
| 23 |
+
|
| 24 |
+
**How it works:**
|
| 25 |
+
1. MEGAMI’s diffusion model (FxGenerator) generates audio-effect embeddings from the stems
|
| 26 |
+
2. The effect processor (FxProcessor) applies those embeddings to produce a polished stereo mix
|
| 27 |
+
|
| 28 |
+
Paper: [arxiv.org/abs/2511.08040](https://arxiv.org/abs/2511.08040) | License: CC BY-NC-SA 4.0
|
app.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import shutil
|
| 3 |
+
import tempfile
|
| 4 |
+
import threading
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torchaudio
|
| 9 |
+
import soundfile as sf
|
| 10 |
+
import gradio as gr
|
| 11 |
+
from pyharp import ModelCard, build_endpoint
|
| 12 |
+
|
| 13 |
+
# ---- Model card ----
|
| 14 |
+
|
| 15 |
+
model_card = ModelCard(
|
| 16 |
+
name="MEGAMI",
|
| 17 |
+
description=(
|
| 18 |
+
"Automatic music mixing via a generative model of audio effect embeddings."
|
| 19 |
+
"Upload 2–6 dry (unprocessed) instrument stems and MEGAMI will generate "
|
| 20 |
+
"a professionally mixed stereo output."
|
| 21 |
+
),
|
| 22 |
+
author="Eloi Moliner, Marco A. Martínez-Ramírez, Junghyun Koo, Wei-Hsiang Liao, Kin Wai Cheuk, Joan Serrà, Vesa Välimäki, Yuki Mitsufuji",
|
| 23 |
+
tags=["mixing", "music-production", "generative", "diffusion"],
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# ---- Background model loading ----
|
| 27 |
+
|
| 28 |
+
_inference = None
|
| 29 |
+
_load_error = None
|
| 30 |
+
_model_ready = threading.Event()
|
| 31 |
+
|
| 32 |
+
MIN_AUDIO_LEN = 525312 # samples @ 44100 Hz ≈ 11.9 s
|
| 33 |
+
SAMPLE_RATE = 44100
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _download_checkpoints():
|
| 37 |
+
import urllib.request
|
| 38 |
+
from huggingface_hub import hf_hub_download
|
| 39 |
+
|
| 40 |
+
os.makedirs("checkpoints", exist_ok=True)
|
| 41 |
+
|
| 42 |
+
github_base = "https://github.com/SonyResearch/MEGAMI/releases/download/v0/"
|
| 43 |
+
for fname in ["FxGenerator_public.pt", "FxProcessor_public.pt", "CLAP_DA_public.pt"]:
|
| 44 |
+
dest = os.path.join("checkpoints", fname)
|
| 45 |
+
if not os.path.exists(dest):
|
| 46 |
+
print(f"[download] {fname} …")
|
| 47 |
+
urllib.request.urlretrieve(github_base + fname, dest)
|
| 48 |
+
print(f"[download] {fname} done.")
|
| 49 |
+
|
| 50 |
+
# The MEGAMI README cites the original LAION-CLAP file as the source for this checkpoint;
|
| 51 |
+
# no patching script is documented — we download the original and save under the expected filename
|
| 52 |
+
clap_dest = "checkpoints/music_audioset_epoch_15_esc_90.14.patched.pt"
|
| 53 |
+
if not os.path.exists(clap_dest):
|
| 54 |
+
print("[download] music_audioset_epoch_15_esc_90.14.patched.pt …")
|
| 55 |
+
cached = hf_hub_download(
|
| 56 |
+
repo_id="lukewys/laion_clap",
|
| 57 |
+
filename="music_audioset_epoch_15_esc_90.14.pt",
|
| 58 |
+
)
|
| 59 |
+
shutil.copy(cached, clap_dest)
|
| 60 |
+
print("[download] music_audioset_epoch_15_esc_90.14.patched.pt done.")
|
| 61 |
+
|
| 62 |
+
fxenc_path = "checkpoints/fxenc_plusplus_default.pt"
|
| 63 |
+
if not os.path.exists(fxenc_path):
|
| 64 |
+
print("[download] fxenc_plusplus_default.pt …")
|
| 65 |
+
cached = hf_hub_download(
|
| 66 |
+
repo_id="yytung/fxencoder-plusplus",
|
| 67 |
+
filename="fxenc_plusplus_default.pt",
|
| 68 |
+
)
|
| 69 |
+
shutil.copy(cached, fxenc_path)
|
| 70 |
+
print("[download] fxenc_plusplus_default.pt done.")
|
| 71 |
+
|
| 72 |
+
print("[download] All checkpoints ready.")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _load_model():
|
| 76 |
+
global _inference, _load_error
|
| 77 |
+
try:
|
| 78 |
+
_download_checkpoints()
|
| 79 |
+
|
| 80 |
+
import omegaconf
|
| 81 |
+
from inference.inference import Inference
|
| 82 |
+
|
| 83 |
+
method_args = omegaconf.OmegaConf.create(
|
| 84 |
+
{
|
| 85 |
+
"FxGenerator_code": "public",
|
| 86 |
+
"FxProcessor_code": "public",
|
| 87 |
+
"T": 30,
|
| 88 |
+
"cfg_scale": 1.0,
|
| 89 |
+
"Schurn": 0,
|
| 90 |
+
}
|
| 91 |
+
)
|
| 92 |
+
_inference = Inference(method_args=method_args)
|
| 93 |
+
print("[model] Inference object ready.")
|
| 94 |
+
except Exception as exc:
|
| 95 |
+
import traceback
|
| 96 |
+
_load_error = str(exc)
|
| 97 |
+
print(f"[model] Load FAILED: {exc}")
|
| 98 |
+
traceback.print_exc()
|
| 99 |
+
finally:
|
| 100 |
+
_model_ready.set()
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
threading.Thread(target=_load_model, daemon=True).start()
|
| 104 |
+
|
| 105 |
+
# ---- Process function ----
|
| 106 |
+
|
| 107 |
+
def process_fn(track1, track2, track3, track4, track5, track6, steps):
|
| 108 |
+
_model_ready.wait()
|
| 109 |
+
|
| 110 |
+
if _load_error:
|
| 111 |
+
raise gr.Error(f"Model failed to load: {_load_error}")
|
| 112 |
+
|
| 113 |
+
track_paths = [t for t in [track1, track2, track3, track4, track5, track6] if t is not None]
|
| 114 |
+
if len(track_paths) < 2:
|
| 115 |
+
raise gr.Error("Please upload at least 2 tracks.")
|
| 116 |
+
|
| 117 |
+
from utils.feature_extractors.dsp_features import compute_log_rms_gated_max
|
| 118 |
+
|
| 119 |
+
dry_tracks = []
|
| 120 |
+
dry_segments = []
|
| 121 |
+
|
| 122 |
+
for path in track_paths:
|
| 123 |
+
audio, file_sr = sf.read(path, dtype="float32")
|
| 124 |
+
if audio.ndim == 1:
|
| 125 |
+
audio = np.stack([audio, audio], axis=-1)
|
| 126 |
+
elif audio.shape[1] == 1:
|
| 127 |
+
audio = np.concatenate([audio, audio], axis=-1)
|
| 128 |
+
|
| 129 |
+
audio_tensor = torch.from_numpy(audio).T # (channels, samples)
|
| 130 |
+
if file_sr != SAMPLE_RATE:
|
| 131 |
+
audio_tensor = torchaudio.functional.resample(audio_tensor, file_sr, SAMPLE_RATE)
|
| 132 |
+
|
| 133 |
+
x_mono = audio_tensor.mean(dim=0, keepdim=True) # (1, samples)
|
| 134 |
+
|
| 135 |
+
if x_mono.shape[-1] < MIN_AUDIO_LEN:
|
| 136 |
+
needed_seconds = MIN_AUDIO_LEN / SAMPLE_RATE
|
| 137 |
+
got_seconds = x_mono.shape[-1] / SAMPLE_RATE
|
| 138 |
+
raise gr.Error(
|
| 139 |
+
f"Each track must be at least {needed_seconds:.1f} s long "
|
| 140 |
+
f"(got {got_seconds:.1f} s)."
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
x_mono = x_mono.to(_inference.device)
|
| 144 |
+
segment = _inference.select_high_energy_segment(x_mono)
|
| 145 |
+
|
| 146 |
+
dry_tracks.append(x_mono)
|
| 147 |
+
dry_segments.append(segment)
|
| 148 |
+
|
| 149 |
+
x_dry = torch.stack(dry_tracks) # (N, 1, L)
|
| 150 |
+
x_dry_segments = torch.stack(dry_segments) # (N, 1, MIN_AUDIO_LEN)
|
| 151 |
+
|
| 152 |
+
rms = compute_log_rms_gated_max(x_dry_segments)
|
| 153 |
+
silent = (rms < -60).squeeze()
|
| 154 |
+
if silent.ndim == 0:
|
| 155 |
+
silent = silent.unsqueeze(0)
|
| 156 |
+
if silent.all():
|
| 157 |
+
raise gr.Error("All uploaded tracks appear to be silent.")
|
| 158 |
+
if silent.any():
|
| 159 |
+
x_dry = x_dry[~silent]
|
| 160 |
+
x_dry_segments = x_dry_segments[~silent]
|
| 161 |
+
|
| 162 |
+
_inference.method_args.T = int(steps) # slider returns float, but T is used as a loop bound so casting to int
|
| 163 |
+
|
| 164 |
+
embedding_preds = _inference.generate_Fx(x_dry_segments, num_samples=1)
|
| 165 |
+
fx_embeddings = _inference.embedding_post_processing(embedding_preds)
|
| 166 |
+
fx_embedding = fx_embeddings[0]
|
| 167 |
+
|
| 168 |
+
y_final = _inference.apply_effects(x_dry.clone(), fx_embedding) # (N, 2, L)
|
| 169 |
+
mix = y_final.sum(dim=0) # (2, L)
|
| 170 |
+
|
| 171 |
+
peak = torch.max(torch.abs(mix)).clamp(min=1e-8) # avoiding div by 0 for silent output
|
| 172 |
+
mix = mix / peak
|
| 173 |
+
|
| 174 |
+
output_file = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
|
| 175 |
+
sf.write(
|
| 176 |
+
output_file.name,
|
| 177 |
+
mix.cpu().clamp(-1, 1).numpy().T,
|
| 178 |
+
SAMPLE_RATE,
|
| 179 |
+
subtype="PCM_16",
|
| 180 |
+
)
|
| 181 |
+
return output_file.name
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
# ---- Gradio interface ----
|
| 185 |
+
|
| 186 |
+
inputs = [
|
| 187 |
+
gr.Audio(type="filepath", label="Track 1 (required)").harp_required(True),
|
| 188 |
+
gr.Audio(type="filepath", label="Track 2 (required)").harp_required(True),
|
| 189 |
+
gr.Audio(type="filepath", label="Track 3"),
|
| 190 |
+
gr.Audio(type="filepath", label="Track 4"),
|
| 191 |
+
gr.Audio(type="filepath", label="Track 5"),
|
| 192 |
+
gr.Audio(type="filepath", label="Track 6"),
|
| 193 |
+
gr.Slider(
|
| 194 |
+
minimum=10,
|
| 195 |
+
maximum=100,
|
| 196 |
+
step=10,
|
| 197 |
+
value=30,
|
| 198 |
+
label="Diffusion Steps (higher = better quality, slower)",
|
| 199 |
+
),
|
| 200 |
+
]
|
| 201 |
+
|
| 202 |
+
outputs = [
|
| 203 |
+
gr.Audio(type="filepath", label="MEGAMI Mix"),
|
| 204 |
+
]
|
| 205 |
+
|
| 206 |
+
demo = build_endpoint(inputs, outputs, process_fn, model_card)
|
| 207 |
+
|
| 208 |
+
if __name__ == "__main__":
|
| 209 |
+
demo.queue().launch(pwa=True)
|
conf/conf_FxGenerator_Public.yaml
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_dir: "checkpoints"
|
| 2 |
+
|
| 3 |
+
diff_params:
|
| 4 |
+
_target_: "diff_params.edm_multitrack_embs.EDM_Multitrack_Embeddings"
|
| 5 |
+
type: "ve_karras"
|
| 6 |
+
content_encoder_type: "CLAP"
|
| 7 |
+
style_encoder_type: "FxEncoder++_DynamicFeatures"
|
| 8 |
+
sample_rate: 44100
|
| 9 |
+
cfg_dropout_prob: 0.2
|
| 10 |
+
sde_hp:
|
| 11 |
+
sigma_data: 0.025
|
| 12 |
+
sigma_min: 5e-4
|
| 13 |
+
sigma_max: 5
|
| 14 |
+
max_sigma: 5
|
| 15 |
+
rho: 10
|
| 16 |
+
CLAP_args:
|
| 17 |
+
ckpt_path: "checkpoints/music_audioset_epoch_15_esc_90.14.patched.pt"
|
| 18 |
+
distance_type: "cosine"
|
| 19 |
+
normalize: True
|
| 20 |
+
use_adaptor: True
|
| 21 |
+
adaptor_checkpoint: "checkpoints/CLAP_DA_public.pt"
|
| 22 |
+
adaptor_type: "MLP_CLAP_regressor"
|
| 23 |
+
add_noise: True
|
| 24 |
+
noise_sigma: 0.011
|
| 25 |
+
fx_encoder_plusplus_args:
|
| 26 |
+
distance_type: "cosine"
|
| 27 |
+
ckpt_path: "checkpoints/fxenc_plusplus_default.pt"
|
| 28 |
+
context_signal: "wet"
|
| 29 |
+
apply_fxnormaug: False
|
| 30 |
+
fxnormaug_train: null
|
| 31 |
+
fxnormaug_inference: null
|
| 32 |
+
default_shape: [1, 3, 64, 33]
|
| 33 |
+
|
| 34 |
+
network:
|
| 35 |
+
_target_: "networks.dit_multitrack.DiffusionTransformer"
|
| 36 |
+
io_channels: 64
|
| 37 |
+
embed_dim: 512
|
| 38 |
+
depth: 16
|
| 39 |
+
num_heads: 16
|
| 40 |
+
cond_token_dim: 64
|
| 41 |
+
global_cond_dim: 0
|
| 42 |
+
input_concat_dim: 0
|
| 43 |
+
project_cond_tokens: false
|
| 44 |
+
transformer_type: "continuous_transformer"
|
| 45 |
+
pos_emb_strategy: "concatenation"
|
| 46 |
+
pos_emb_dim: 64
|
| 47 |
+
pos_emb_type: "one-hot"
|
| 48 |
+
pos_emb_crossattn_strategy: "concatenation"
|
| 49 |
+
pos_emb_crossattn_dim: 64
|
| 50 |
+
pos_emb_crossattn_type: "one-hot"
|
| 51 |
+
use_taxonomy_in_pos_emb: False
|
| 52 |
+
|
| 53 |
+
tester:
|
| 54 |
+
checkpoint: null
|
| 55 |
+
sampler:
|
| 56 |
+
_target_: "inference.sampler_euler_heun_multitrack.SamplerEulerHeun"
|
| 57 |
+
sampling_params:
|
| 58 |
+
same_as_training: False
|
| 59 |
+
sde_hp:
|
| 60 |
+
sigma_data: 0.025
|
| 61 |
+
sigma_min: 5e-4
|
| 62 |
+
sigma_max: 5
|
| 63 |
+
rho: 10
|
| 64 |
+
Schurn: 0
|
| 65 |
+
Snoise: 1
|
| 66 |
+
Stmin: 0
|
| 67 |
+
Stmax: 10
|
| 68 |
+
order: 1
|
| 69 |
+
T: 51
|
| 70 |
+
schedule: "edm"
|
| 71 |
+
cfg_scale: 1.0
|
conf/conf_FxProcessor_Public.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_dir: "checkpoints"
|
| 2 |
+
|
| 3 |
+
exp:
|
| 4 |
+
apply_fxnorm: True
|
| 5 |
+
fxnorm:
|
| 6 |
+
_target_: "utils.fx_normalization.fxnorm_v2_public.FxNormAug"
|
| 7 |
+
sample_rate: 44100
|
| 8 |
+
device: "cpu"
|
| 9 |
+
features_path: "utils/fx_normalization/features.npy"
|
| 10 |
+
use_gated_RMSnorm: True
|
| 11 |
+
RMS_norm: -16
|
| 12 |
+
|
| 13 |
+
network:
|
| 14 |
+
_target_: "networks.blackbox_TCN.TCNModel"
|
| 15 |
+
ninputs: 1
|
| 16 |
+
noutputs: 2
|
| 17 |
+
cond_dim: 2112
|
| 18 |
+
nblocks: 14
|
| 19 |
+
kernel_size: 15
|
| 20 |
+
stride: 1
|
| 21 |
+
dilation_growth: 2
|
| 22 |
+
channel_growth: 1
|
| 23 |
+
channel_width: 128
|
| 24 |
+
stack_size: 15
|
| 25 |
+
grouped: False
|
| 26 |
+
causal: False
|
| 27 |
+
skip_connections: False
|
| 28 |
+
use_CLAP: True
|
| 29 |
+
CLAP_args:
|
| 30 |
+
ckpt_path: "checkpoints/music_audioset_epoch_15_esc_90.14.patched.pt"
|
| 31 |
+
distance_type: "cosine"
|
| 32 |
+
normalize: True
|
| 33 |
+
use_adaptor: False
|
| 34 |
+
adaptor_checkpoint: null
|
| 35 |
+
adaptor_type: null
|
| 36 |
+
add_noise: False
|
| 37 |
+
noise_sigma: null
|
| 38 |
+
|
| 39 |
+
tester:
|
| 40 |
+
checkpoint: null
|
diff_params/__init__.py
ADDED
|
File without changes
|
diff_params/edm_multitrack_embs.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Sony Research
|
| 2 |
+
# Licensed under CC BY-NC-SA 4.0
|
| 3 |
+
# See LICENSE file for details
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
import sys
|
| 8 |
+
import math
|
| 9 |
+
import time
|
| 10 |
+
import os
|
| 11 |
+
import einops
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
from utils.multitrack_utils import multitrack_batched_processing
|
| 15 |
+
from utils.data_utils import apply_RMS_normalization
|
| 16 |
+
|
| 17 |
+
class EDM_Multitrack_Embeddings:
|
| 18 |
+
"""
|
| 19 |
+
This implements the time-frequency domain diffusion
|
| 20 |
+
Definition of the diffusion parameterization, following ( Karras et al., "Elucidating...", 2022).
|
| 21 |
+
This includes only the utilities needed for training, not for sampling.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self,
|
| 25 |
+
type,
|
| 26 |
+
sde_hp,
|
| 27 |
+
default_shape,
|
| 28 |
+
cfg_dropout_prob=0.2,
|
| 29 |
+
sample_rate=44100,
|
| 30 |
+
context_signal="dry",
|
| 31 |
+
content_encoder_type="CLAP",
|
| 32 |
+
style_encoder_type="fx_encoder2048+AFv6",
|
| 33 |
+
*args,
|
| 34 |
+
**kwargs
|
| 35 |
+
):
|
| 36 |
+
|
| 37 |
+
self.type = type
|
| 38 |
+
self.sde_hp = sde_hp
|
| 39 |
+
|
| 40 |
+
self.sigma_data = self.sde_hp.sigma_data # depends on the training data!! precalculated variance of the dataset
|
| 41 |
+
self.sigma_min = self.sde_hp.sigma_min
|
| 42 |
+
self.sigma_max = self.sde_hp.sigma_max
|
| 43 |
+
self.rho = self.sde_hp.rho
|
| 44 |
+
|
| 45 |
+
self.default_shape = torch.Size(default_shape)
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
self.max_t = self.sde_hp.max_sigma
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(e)
|
| 51 |
+
print("max_sigma not defined, please add it. It should be the highest sigma value seen during training")
|
| 52 |
+
|
| 53 |
+
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 54 |
+
self.device=device
|
| 55 |
+
|
| 56 |
+
self.cfg_dropout_prob = cfg_dropout_prob
|
| 57 |
+
|
| 58 |
+
self.context_signal=context_signal
|
| 59 |
+
|
| 60 |
+
self.sample_rate=sample_rate
|
| 61 |
+
|
| 62 |
+
self.prepare_content_encoder(content_encoder_type, sample_rate, *args, **kwargs)
|
| 63 |
+
self.prepare_style_encoder(style_encoder_type, *args, **kwargs)
|
| 64 |
+
|
| 65 |
+
def prepare_content_encoder(self, type, sample_rate, *args, **kwargs):
|
| 66 |
+
|
| 67 |
+
if type=="CLAP":
|
| 68 |
+
CLAP_args= kwargs.get("CLAP_args", None)
|
| 69 |
+
assert CLAP_args is not None, "CLAP_args must be provided for CLAP AE"
|
| 70 |
+
|
| 71 |
+
# Save original path
|
| 72 |
+
from utils.feature_extractors.load_features import load_CLAP
|
| 73 |
+
CLAP_encoder= load_CLAP(CLAP_args, device=self.device)
|
| 74 |
+
|
| 75 |
+
def encode_fn(x, *args, **kwargs):
|
| 76 |
+
x=x.to(self.device)
|
| 77 |
+
|
| 78 |
+
type= kwargs.get("type", None)
|
| 79 |
+
z=CLAP_encoder(x, type) #shape (B, C)
|
| 80 |
+
|
| 81 |
+
z=z.view(z.shape[0], 64, -1) #shape (B, 64, N)
|
| 82 |
+
|
| 83 |
+
z=z.permute(0, 2, 1) #shape (B, N, 64)
|
| 84 |
+
|
| 85 |
+
return z
|
| 86 |
+
|
| 87 |
+
self.content_encode_fn=encode_fn
|
| 88 |
+
|
| 89 |
+
else:
|
| 90 |
+
raise NotImplementedError(f"AE type {AE_type} not implemented")
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def prepare_style_encoder(self, type, *args, **kwargs):
|
| 94 |
+
|
| 95 |
+
if type=="FxEncoder++_DynamicFeatures":
|
| 96 |
+
|
| 97 |
+
Fxencoder_plusplus_args=kwargs.get("fx_encoder_plusplus_args", None)
|
| 98 |
+
|
| 99 |
+
from utils.feature_extractors.load_features import load_fx_encoder_plusplus_2048
|
| 100 |
+
feat_extractor = load_fx_encoder_plusplus_2048(Fxencoder_plusplus_args, self.device)
|
| 101 |
+
|
| 102 |
+
from utils.feature_extractors.AF_features_embedding import AF_fourier_embedding
|
| 103 |
+
AFembedding= AF_fourier_embedding(device=self.device)
|
| 104 |
+
|
| 105 |
+
def fxencode_fn(x):
|
| 106 |
+
"""
|
| 107 |
+
x: tensor of shape [B, C, L] where B is the batch size, C is the number of channels and L is the length of the audio
|
| 108 |
+
"""
|
| 109 |
+
z=feat_extractor(x)
|
| 110 |
+
z=torch.nn.functional.normalize(z, dim=-1, p=2)
|
| 111 |
+
z=z*math.sqrt(z.shape[-1]) # rescale to keep the same scale
|
| 112 |
+
|
| 113 |
+
z_af,_=AFembedding.encode(x)
|
| 114 |
+
z_af=z_af* math.sqrt(z_af.shape[-1]) # rescale to keep the same scale
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
z_all= torch.cat([z, z_af], dim=-1)
|
| 118 |
+
|
| 119 |
+
#now L2 normalize
|
| 120 |
+
|
| 121 |
+
norm_z= z_all/ math.sqrt(z_all.shape[-1]) # normalize by dividing by sqrt(dim) to keep the same scale
|
| 122 |
+
|
| 123 |
+
norm_z=norm_z.view(norm_z.shape[0], 64, -1) # Reshape to [B, 64, L//64] where L//64 is the number of frames
|
| 124 |
+
|
| 125 |
+
return norm_z
|
| 126 |
+
|
| 127 |
+
def reshape_fn(embed):
|
| 128 |
+
"""
|
| 129 |
+
embed: tensor of shape [B, 64, L//64] where B is the batch size
|
| 130 |
+
"""
|
| 131 |
+
embed=embed.view(embed.shape[0], -1)
|
| 132 |
+
|
| 133 |
+
return embed
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
self.style_encode_fn=fxencode_fn
|
| 137 |
+
self.style_reshape=reshape_fn
|
| 138 |
+
|
| 139 |
+
else:
|
| 140 |
+
raise NotImplementedError(f"FX encoder type {type} not implemented")
|
| 141 |
+
|
| 142 |
+
def sample_time_training(self, N):
|
| 143 |
+
"""
|
| 144 |
+
For training, getting t according to a similar criteria as sampling. Simpler and safer to what Karras et al. did
|
| 145 |
+
Args:
|
| 146 |
+
N (int): batch size
|
| 147 |
+
"""
|
| 148 |
+
a = torch.rand(N)
|
| 149 |
+
t = (self.sigma_max**(1/self.rho) +a *(self.sigma_min**(1/self.rho) - self.sigma_max**(1/self.rho)))**self.rho
|
| 150 |
+
|
| 151 |
+
return t
|
| 152 |
+
|
| 153 |
+
def sample_prior(self, shape=None, t=None, dtype=None):
|
| 154 |
+
"""
|
| 155 |
+
Just sample some gaussian noise, nothing more
|
| 156 |
+
Args:
|
| 157 |
+
shape (tuple): shape of the noise to sample, something like (B,T)
|
| 158 |
+
"""
|
| 159 |
+
assert shape is not None
|
| 160 |
+
if t is not None:
|
| 161 |
+
n = torch.randn(shape).to(t.device) * t
|
| 162 |
+
else:
|
| 163 |
+
n = torch.randn(shape)
|
| 164 |
+
return n
|
| 165 |
+
|
| 166 |
+
def cskip(self, sigma):
|
| 167 |
+
"""
|
| 168 |
+
Just one of the preconditioning parameters
|
| 169 |
+
Args:
|
| 170 |
+
sigma (float): noise level (equal to timestep is sigma=t, which is our default)
|
| 171 |
+
|
| 172 |
+
"""
|
| 173 |
+
return self.sigma_data**2 *(sigma**2+self.sigma_data**2)**-1
|
| 174 |
+
|
| 175 |
+
def cout(self, sigma):
|
| 176 |
+
"""
|
| 177 |
+
Just one of the preconditioning parameters
|
| 178 |
+
Args:
|
| 179 |
+
sigma (float): noise level (equal to timestep is sigma=t, which is our default)
|
| 180 |
+
"""
|
| 181 |
+
return sigma*self.sigma_data* (self.sigma_data**2+sigma**2)**(-0.5)
|
| 182 |
+
|
| 183 |
+
def cin(self, sigma):
|
| 184 |
+
"""
|
| 185 |
+
Just one of the preconditioning parameters
|
| 186 |
+
Args:
|
| 187 |
+
sigma (float): noise level (equal to timestep is sigma=t, which is our default)
|
| 188 |
+
"""
|
| 189 |
+
return (self.sigma_data**2+sigma**2)**(-0.5)
|
| 190 |
+
|
| 191 |
+
def cnoise(self, sigma):
|
| 192 |
+
"""
|
| 193 |
+
preconditioning of the noise embedding
|
| 194 |
+
Args:
|
| 195 |
+
sigma (float): noise level (equal to timestep is sigma=t, which is our default)
|
| 196 |
+
"""
|
| 197 |
+
return (1/4)*torch.log(sigma)
|
| 198 |
+
|
| 199 |
+
def lambda_w(self, sigma):
|
| 200 |
+
"""
|
| 201 |
+
Score matching loss weighting
|
| 202 |
+
"""
|
| 203 |
+
return (sigma*self.sigma_data)**(-2) * (self.sigma_data**2+sigma**2)
|
| 204 |
+
|
| 205 |
+
def prepare_train_preconditioning(self, x, t, n=None, *args, **kwargs):
|
| 206 |
+
|
| 207 |
+
mu, sigma = self._mean(x, t), self._std(t).unsqueeze(-1)
|
| 208 |
+
sigma = sigma.view(*sigma.size(), *(1,)*(x.ndim - sigma.ndim))
|
| 209 |
+
if n is None:
|
| 210 |
+
n=self.sample_prior(shape=x.shape).to(x.device)
|
| 211 |
+
x_perturbed = mu + sigma *n
|
| 212 |
+
#self.sample_prior(x.shape).to(x.device)
|
| 213 |
+
|
| 214 |
+
cskip = self.cskip(sigma)
|
| 215 |
+
cout = self.cout(sigma)
|
| 216 |
+
cin = self.cin(sigma)
|
| 217 |
+
cnoise = self.cnoise(sigma.squeeze())
|
| 218 |
+
|
| 219 |
+
#check if cnoise is a scalar, if so, repeat it
|
| 220 |
+
if len(cnoise.shape) == 0:
|
| 221 |
+
cnoise = cnoise.repeat(x.shape[0],)
|
| 222 |
+
else:
|
| 223 |
+
cnoise = cnoise.view(x.shape[0],)
|
| 224 |
+
|
| 225 |
+
target = 1/cout * (x - cskip * x_perturbed)
|
| 226 |
+
|
| 227 |
+
return cin * x_perturbed, target, cnoise
|
| 228 |
+
|
| 229 |
+
def loss_fn(self, net, sample=None, sample_aug=None, context=None, clusters=None, taxonomy=None, masks=None, *args, **kwargs):
|
| 230 |
+
"""
|
| 231 |
+
Loss function, which is the mean squared error between the denoised latent and the clean latent
|
| 232 |
+
Args:
|
| 233 |
+
net (nn.Module): Model of the denoiser
|
| 234 |
+
x (Tensor): shape: (B,T) Intermediate noisy latent to denoise
|
| 235 |
+
sigma (float): noise level (equal to timestep is sigma=t, which is our default)
|
| 236 |
+
"""
|
| 237 |
+
|
| 238 |
+
start=time.time()
|
| 239 |
+
y=sample
|
| 240 |
+
|
| 241 |
+
t = self.sample_time_training(y.shape[0]).to(y.device)
|
| 242 |
+
|
| 243 |
+
if self.context_signal == "wet":
|
| 244 |
+
if sample_aug is not None:
|
| 245 |
+
context = sample_aug
|
| 246 |
+
else:
|
| 247 |
+
context = y.clone() # use the wet signal as context
|
| 248 |
+
|
| 249 |
+
else:
|
| 250 |
+
assert context is not None, "Context must be provided if context_signal is not 'wet'"
|
| 251 |
+
|
| 252 |
+
a=time.time
|
| 253 |
+
|
| 254 |
+
with torch.no_grad():
|
| 255 |
+
|
| 256 |
+
y_style=self.style_encode(y, masks=masks, taxonomy=taxonomy)
|
| 257 |
+
|
| 258 |
+
if context is not None:
|
| 259 |
+
z, x=self.transform_forward(context, is_condition=True, clusters=clusters, masks=masks, taxonomy=taxonomy, is_wet=(self.context_signal == "wet"))
|
| 260 |
+
if self.cfg_dropout_prob > 0.0:
|
| 261 |
+
null_embed = torch.zeros_like(z, device=z.device)
|
| 262 |
+
#dropout context with probability cfg_dropout_prob
|
| 263 |
+
mask = torch.rand(z.shape[0], device=z.device) < self.cfg_dropout_prob
|
| 264 |
+
z = torch.where(mask.view(-1,1,1,1), null_embed, z)
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
input, target, cnoise = self.prepare_train_preconditioning(y_style, t )
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
if len(cnoise.shape)==1:
|
| 271 |
+
cnoise=cnoise.unsqueeze(-1)
|
| 272 |
+
if input.ndim==2:
|
| 273 |
+
input=input.unsqueeze(1)
|
| 274 |
+
|
| 275 |
+
estimate = net(input, cnoise, cross_attn_cond=z, taxonomy=taxonomy, mask=masks, cross_attn_cond_mask=masks)
|
| 276 |
+
|
| 277 |
+
if target.ndim==2 and estimate.ndim==3:
|
| 278 |
+
estimate=estimate.squeeze(1)
|
| 279 |
+
|
| 280 |
+
error=torch.square(torch.abs(estimate-target))
|
| 281 |
+
|
| 282 |
+
# do not propagate the error of the padded tracks
|
| 283 |
+
error= error*masks.view(masks.shape[0], masks.shape[1], 1, 1)
|
| 284 |
+
|
| 285 |
+
compensating_scalar= torch.numel(masks)/ torch.sum(masks, dim=(0,1), keepdim=False).clamp(min=1.0)
|
| 286 |
+
error= error * compensating_scalar.view(-1, 1, 1, 1)
|
| 287 |
+
|
| 288 |
+
return error, self._std(t), x, y
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def get_null_embed(self, context):
|
| 292 |
+
null_embed = torch.zeros_like(context, device=context.device)
|
| 293 |
+
return null_embed
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def denoiser(self, xn , net, t, cond=None,cfg_scale=1.0, masks=None, taxonomy=None, **kwargs):
|
| 297 |
+
"""
|
| 298 |
+
This method does the whole denoising step, which implies applying the model and the preconditioning
|
| 299 |
+
Args:
|
| 300 |
+
x (Tensor): shape: (CQT shape?) Intermediate noisy latent to denoise
|
| 301 |
+
model (nn.Module): Model of the denoiser
|
| 302 |
+
sigma (float): noise level (equal to timestep is sigma=t, which is our default)
|
| 303 |
+
"""
|
| 304 |
+
sigma = self._std(t).unsqueeze(-1)
|
| 305 |
+
sigma = sigma.view(*sigma.size(), *(1,)*(xn.ndim - sigma.ndim))
|
| 306 |
+
|
| 307 |
+
cskip = self.cskip(sigma)
|
| 308 |
+
cout = self.cout(sigma)
|
| 309 |
+
cin = self.cin(sigma)
|
| 310 |
+
cnoise = self.cnoise(sigma.squeeze())
|
| 311 |
+
|
| 312 |
+
#check if cnoise is a scalar, if so, repeat it
|
| 313 |
+
if len(cnoise.shape) == 0:
|
| 314 |
+
cnoise = cnoise.repeat(xn.shape[0],).unsqueeze(-1)
|
| 315 |
+
else:
|
| 316 |
+
cnoise = cnoise.view(xn.shape[0],).unsqueeze(-1)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
x_in=cin*xn
|
| 320 |
+
|
| 321 |
+
if cfg_scale == 1.0:
|
| 322 |
+
net_out=net(x_in, cnoise.to(torch.float32), cross_attn_cond=cond, mask=masks, taxonomy=taxonomy, cross_attn_cond_mask=masks) #this will crash because of broadcasting problems, debug later!
|
| 323 |
+
else:
|
| 324 |
+
null_embed = self.get_null_embed(cond)
|
| 325 |
+
|
| 326 |
+
inputs_cond= torch.cat([cond, null_embed], dim=0)
|
| 327 |
+
|
| 328 |
+
x_in_cat= torch.cat([x_in, x_in], dim=0)
|
| 329 |
+
|
| 330 |
+
cnoise= torch.cat([cnoise, cnoise], dim=0)
|
| 331 |
+
|
| 332 |
+
masks_in= torch.cat([masks, masks], dim=0) if masks is not None else None
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
net_out_batch=net(x_in_cat, cnoise.to(torch.float32), cross_attn_cond=inputs_cond , mask=masks_in, cross_attn_cond_mask=masks_in) #this will crash because of broadcasting problems, debug later!0
|
| 336 |
+
|
| 337 |
+
cond_output, uncond_output = torch.chunk(net_out_batch, 2, dim=0)
|
| 338 |
+
|
| 339 |
+
net_out = uncond_output + (cond_output - uncond_output) * cfg_scale
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
x_hat=cskip*xn + cout*net_out
|
| 343 |
+
x_hat=x_hat* masks.view(masks.shape[0], masks.shape[1], 1, 1)
|
| 344 |
+
|
| 345 |
+
return x_hat
|
| 346 |
+
|
| 347 |
+
def style_encode(self, x, masks=None, taxonomy=None, use_adaptor=False):
|
| 348 |
+
"""
|
| 349 |
+
Encode the input audio using the style encoder
|
| 350 |
+
Args:
|
| 351 |
+
x (Tensor): shape: (B,N, C, T) Audio to encode
|
| 352 |
+
masks (Tensor): shape: (B, N) Mask indicating which tracks are present in the batch
|
| 353 |
+
"""
|
| 354 |
+
def apply_fxenc(x_masked, taxonommy=None):
|
| 355 |
+
x_emb=self.style_encode_fn(x_masked)
|
| 356 |
+
|
| 357 |
+
return x_emb
|
| 358 |
+
|
| 359 |
+
assert masks is not None, "masks must be provided for style encoding"
|
| 360 |
+
|
| 361 |
+
output_emb=multitrack_batched_processing( x, taxonomy=taxonomy ,function=apply_fxenc, class_dependent=False, masks=masks)
|
| 362 |
+
|
| 363 |
+
return output_emb
|
| 364 |
+
|
| 365 |
+
def _mean(self, x, t):
|
| 366 |
+
return x
|
| 367 |
+
|
| 368 |
+
def _std(self, t):
|
| 369 |
+
return t
|
| 370 |
+
|
| 371 |
+
def _ode_integrand(self, x, t, score):
|
| 372 |
+
return -t * score
|
| 373 |
+
|
| 374 |
+
def transform_inverse(self, z):
|
| 375 |
+
#shape is (B, N, C, T)
|
| 376 |
+
B, N, C, T = z.shape
|
| 377 |
+
# Reshape z to (B*N, C, T)
|
| 378 |
+
z_reshaped = einops.rearrange(z, "b n c t -> (b n) c t")
|
| 379 |
+
z_reshaped= self.style_reshape(z_reshaped)
|
| 380 |
+
|
| 381 |
+
# Reshape back to (B, N, C)
|
| 382 |
+
|
| 383 |
+
z_out = einops.rearrange(z_reshaped, "(b n) c -> b n c", b=B, n=N)
|
| 384 |
+
|
| 385 |
+
return z_out
|
| 386 |
+
|
| 387 |
+
def preprocessor(self, x, is_test=False, taxonomy=None):
|
| 388 |
+
"""
|
| 389 |
+
x: tensor of shape (BxN, C, T) where B is the batch size, N is the number of tracks, C is the number of channels and T is the number of time steps
|
| 390 |
+
taxonomy: list of lists of strings, where each string is the taxonomy of the track with length BxN. It may be useful if we want to apply different augmentations depending on the taxonomy of the track.
|
| 391 |
+
"""
|
| 392 |
+
if x.shape[1] == 2:
|
| 393 |
+
x = torch.mean(x, dim=1, keepdim=True).expand(-1, 2, -1) # convert to stereo if it is mono
|
| 394 |
+
elif x.shape[1] == 1: # if context is mono, we expand it to stereo
|
| 395 |
+
x = x.expand(-1, 2, -1)
|
| 396 |
+
|
| 397 |
+
if not is_test:
|
| 398 |
+
#random flip
|
| 399 |
+
if np.random.rand() > 0.5:
|
| 400 |
+
x = -x
|
| 401 |
+
|
| 402 |
+
#rms normalize context to -25 dB
|
| 403 |
+
x= apply_RMS_normalization(x, -25, device=self.device)
|
| 404 |
+
|
| 405 |
+
return x
|
| 406 |
+
|
| 407 |
+
def Tweedie2score(self, tweedie, xt, t, *args, **kwargs):
|
| 408 |
+
return (tweedie - self._mean(xt, t)) / self._std(t)**2
|
| 409 |
+
|
| 410 |
+
def score2Tweedie(self, score, xt, t, *args, **kwargs):
|
| 411 |
+
return self._std(t)**2 * score + self._mean(xt, t)
|
| 412 |
+
|
| 413 |
+
def transform_forward(self, x, y=None, is_condition=False, is_test=False, clusters=None, masks=None, taxonomy=None, is_wet=False):
|
| 414 |
+
|
| 415 |
+
assert masks is not None
|
| 416 |
+
|
| 417 |
+
#convert y to mono and rms normalize itdd
|
| 418 |
+
|
| 419 |
+
def prerprocess_and_encode(x_masked, taxonomy=None):
|
| 420 |
+
if is_condition:
|
| 421 |
+
|
| 422 |
+
x_masked=self.preprocessor(x_masked, is_test=is_test, taxonomy=taxonomy)
|
| 423 |
+
|
| 424 |
+
with torch.no_grad():
|
| 425 |
+
x_emb=self.content_encode_fn(x_masked, type="wet" if is_wet else "dry")
|
| 426 |
+
|
| 427 |
+
return x_emb, x_masked
|
| 428 |
+
|
| 429 |
+
z, x_out=multitrack_batched_processing(
|
| 430 |
+
x, taxonomy=taxonomy, function=prerprocess_and_encode, class_dependent=False, masks=masks, number_outputs=2
|
| 431 |
+
)
|
| 432 |
+
return z, x_out
|
inference/__init__.py
ADDED
|
File without changes
|
inference/inference.py
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Sony Research
|
| 2 |
+
# Licensed under CC BY-NC-SA 4.0
|
| 3 |
+
# See LICENSE file for details
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
from utils.feature_extractors.dsp_features import compute_log_rms_gated_max
|
| 8 |
+
|
| 9 |
+
import pyloudnorm as pyln
|
| 10 |
+
|
| 11 |
+
import hydra
|
| 12 |
+
import torch
|
| 13 |
+
import torchaudio
|
| 14 |
+
from hydra import initialize, compose
|
| 15 |
+
import utils.training_utils as tr_utils
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import omegaconf
|
| 19 |
+
import math
|
| 20 |
+
|
| 21 |
+
import soundfile as sf
|
| 22 |
+
from utils.data_utils import apply_RMS_normalization
|
| 23 |
+
|
| 24 |
+
import numpy as np
|
| 25 |
+
import glob
|
| 26 |
+
|
| 27 |
+
from utils.data_utils import read_wav_segment
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def load_audio( file, start=None, end=None, stereo=True):
|
| 31 |
+
|
| 32 |
+
x, fs=read_wav_segment(file, start, end)
|
| 33 |
+
if stereo:
|
| 34 |
+
if len(x.shape)==1:
|
| 35 |
+
#print( "dry not stereo , doubling channels", x_dry.shape)
|
| 36 |
+
x=x[:,np.newaxis]
|
| 37 |
+
x= np.concatenate((x, x), axis=-1)
|
| 38 |
+
elif len(x.shape)==2 and x.shape[-1]==1:
|
| 39 |
+
#print( "dry not stereo , doubling channels", x_dry.shape)
|
| 40 |
+
x = np.concatenate((x, x), axis=-1)
|
| 41 |
+
|
| 42 |
+
x=torch.from_numpy(x).permute(1,0)
|
| 43 |
+
|
| 44 |
+
return x, fs
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class Inference:
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
method_args=None,
|
| 51 |
+
path_benchmark="/add/the/path/to/the/benchmark/data/here",
|
| 52 |
+
load_segment_length=525312, #segment length used for loading and extract CLAP embeddings
|
| 53 |
+
processor_segment_length=525312, #segment length used for loading and extract CLAP embeddings
|
| 54 |
+
processor_overlap=8192,
|
| 55 |
+
):
|
| 56 |
+
|
| 57 |
+
self.method_args = method_args
|
| 58 |
+
self.path_benchmark = path_benchmark
|
| 59 |
+
self.load_segment_length=load_segment_length
|
| 60 |
+
self.processor_segment_length=processor_segment_length
|
| 61 |
+
self.processor_overlap=processor_overlap
|
| 62 |
+
|
| 63 |
+
self.FxGenerator_code = method_args.FxGenerator_code
|
| 64 |
+
self.FxProcessor_code = method_args.FxProcessor_code
|
| 65 |
+
|
| 66 |
+
self.config_file_rel = "../conf"
|
| 67 |
+
# self.config_path="/home/eloi/projects/project_mfm_eloi/src/conf"
|
| 68 |
+
|
| 69 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 70 |
+
|
| 71 |
+
self.load_FxGenerator() # Load the S1 model
|
| 72 |
+
self.load_FxProcessor() # Load the S2 model
|
| 73 |
+
self.prepare_feature_extractors() # Prepare the feature extractors
|
| 74 |
+
|
| 75 |
+
def load_FxGenerator(self):
|
| 76 |
+
if self.FxGenerator_code == "public":
|
| 77 |
+
config_name = "conf_FxGenerator_Public.yaml"
|
| 78 |
+
model_dir = "checkpoints"
|
| 79 |
+
ckpt = "FxGenerator_public.pt"
|
| 80 |
+
else:
|
| 81 |
+
raise ValueError(f"Unknown FxGenerator_code: {self.FxGenerator_code}")
|
| 82 |
+
|
| 83 |
+
overrides = [
|
| 84 |
+
f"model_dir={model_dir}",
|
| 85 |
+
f"tester.checkpoint={ckpt}",
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
with initialize(version_base=None, config_path=self.config_file_rel):
|
| 89 |
+
args = compose(config_name=config_name, overrides=overrides)
|
| 90 |
+
|
| 91 |
+
if not os.path.exists(args.model_dir):
|
| 92 |
+
raise Exception(f"Model directory {args.model_dir} does not exist")
|
| 93 |
+
|
| 94 |
+
diff_params = hydra.utils.instantiate(args.diff_params)
|
| 95 |
+
|
| 96 |
+
network = hydra.utils.instantiate(args.network)
|
| 97 |
+
network = network.to(self.device)
|
| 98 |
+
state_dict = torch.load(
|
| 99 |
+
os.path.join(args.model_dir, args.tester.checkpoint),
|
| 100 |
+
map_location=self.device,
|
| 101 |
+
weights_only=False,
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
tr_utils.load_state_dict(state_dict, ema=network)
|
| 105 |
+
|
| 106 |
+
self.sampler = hydra.utils.instantiate(
|
| 107 |
+
args.tester.sampler,
|
| 108 |
+
network,
|
| 109 |
+
diff_params,
|
| 110 |
+
args,
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
def load_FxProcessor(self):
|
| 114 |
+
|
| 115 |
+
### Loading effects model ###
|
| 116 |
+
|
| 117 |
+
if self.FxProcessor_code == "public":
|
| 118 |
+
config_name = "conf_FxProcessor_Public.yaml"
|
| 119 |
+
model_dir = "checkpoints"
|
| 120 |
+
ckpt = "FxProcessor_public.pt"
|
| 121 |
+
else:
|
| 122 |
+
raise ValueError(f"Unknown FxProcessor_code: {self.FxProcessor_code}")
|
| 123 |
+
|
| 124 |
+
overrides = [
|
| 125 |
+
f"model_dir={model_dir}",
|
| 126 |
+
f"tester.checkpoint={ckpt}",
|
| 127 |
+
]
|
| 128 |
+
|
| 129 |
+
with initialize(version_base=None, config_path=self.config_file_rel):
|
| 130 |
+
args = compose(config_name=config_name, overrides=overrides)
|
| 131 |
+
|
| 132 |
+
if not os.path.exists(args.model_dir):
|
| 133 |
+
raise Exception(f"Model directory {args.model_dir} does not exist")
|
| 134 |
+
|
| 135 |
+
fx_model = hydra.utils.instantiate(args.network)
|
| 136 |
+
self.fx_model = fx_model.to(self.device)
|
| 137 |
+
state_dict = torch.load(
|
| 138 |
+
os.path.join(args.model_dir, args.tester.checkpoint),
|
| 139 |
+
map_location=self.device,
|
| 140 |
+
weights_only=False,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
tr_utils.load_state_dict(state_dict, network=fx_model)
|
| 144 |
+
|
| 145 |
+
if args.exp.apply_fxnorm:
|
| 146 |
+
print("Applying fx_normalizer")
|
| 147 |
+
if "public" in self.FxProcessor_code:
|
| 148 |
+
fx_normalizer = hydra.utils.instantiate(args.exp.fxnorm, device=str(self.device))
|
| 149 |
+
self.fx_normalizer = lambda x: fx_normalizer(
|
| 150 |
+
x, use_gate=args.exp.use_gated_RMSnorm, RMS=args.exp.RMS_norm
|
| 151 |
+
)
|
| 152 |
+
else:
|
| 153 |
+
self.fx_normalizer = hydra.utils.instantiate(args.exp.fxnorm)
|
| 154 |
+
|
| 155 |
+
else:
|
| 156 |
+
print("No fx_normalizer specified, using identity function")
|
| 157 |
+
self.fx_normalizer = (
|
| 158 |
+
lambda x: x
|
| 159 |
+
) # identity function if no fx_normalizer is specified
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def prepare_feature_extractors(self):
|
| 163 |
+
|
| 164 |
+
### preparing feature extractor ###
|
| 165 |
+
|
| 166 |
+
Fxencoder_kwargs = omegaconf.OmegaConf.create(
|
| 167 |
+
{
|
| 168 |
+
"ckpt_path": "checkpoints/fxenc_plusplus_default.pt"
|
| 169 |
+
}
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
from utils.feature_extractors.load_features import load_fx_encoder_plusplus_2048
|
| 173 |
+
|
| 174 |
+
feat_extractor = load_fx_encoder_plusplus_2048(Fxencoder_kwargs, self.device)
|
| 175 |
+
|
| 176 |
+
from utils.feature_extractors.AF_features_embedding import AF_fourier_embedding
|
| 177 |
+
|
| 178 |
+
AFembedding = AF_fourier_embedding(device=self.device)
|
| 179 |
+
|
| 180 |
+
def FxEnc(x):
|
| 181 |
+
"""
|
| 182 |
+
x: tensor of shape [B, C, L] where B is the batch size, C is the number of channels and L is the length of the audio
|
| 183 |
+
"""
|
| 184 |
+
z = feat_extractor(x)
|
| 185 |
+
z = torch.nn.functional.normalize(
|
| 186 |
+
z, dim=-1, p=2
|
| 187 |
+
) # normalize to unit variance
|
| 188 |
+
z = z * math.sqrt(z.shape[-1]) # rescale to keep the same scale
|
| 189 |
+
|
| 190 |
+
z_af, _ = AFembedding.encode(x)
|
| 191 |
+
z_af = z_af * math.sqrt(z_af.shape[-1]) # rescale to keep the same scale
|
| 192 |
+
|
| 193 |
+
z_all = torch.cat([z, z_af], dim=-1)
|
| 194 |
+
|
| 195 |
+
# now L2 normalize
|
| 196 |
+
|
| 197 |
+
norm_z = z_all / math.sqrt(
|
| 198 |
+
z_all.shape[-1]
|
| 199 |
+
) # normalize by dividing by sqrt(dim) to keep the same scale
|
| 200 |
+
|
| 201 |
+
return norm_z
|
| 202 |
+
|
| 203 |
+
self.FxEnc = FxEnc
|
| 204 |
+
|
| 205 |
+
def embedding_post_processing(z):
|
| 206 |
+
"""
|
| 207 |
+
L2 normalize each of the features in z
|
| 208 |
+
"""
|
| 209 |
+
z_fxenc = z[
|
| 210 |
+
..., :2048
|
| 211 |
+
] # assuming the FxEncoder features are the first 2048 dimensions
|
| 212 |
+
z_af = z[
|
| 213 |
+
..., 2048:
|
| 214 |
+
] # assuming the AF features are the last 2048 dimensions
|
| 215 |
+
|
| 216 |
+
z_fxenc = torch.nn.functional.normalize(
|
| 217 |
+
z_fxenc, dim=-1, p=2
|
| 218 |
+
) # normalize to unit variance
|
| 219 |
+
z_af = torch.nn.functional.normalize(z_af, dim=-1, p=2)
|
| 220 |
+
|
| 221 |
+
z_fxenc = z_fxenc * math.sqrt(
|
| 222 |
+
z_fxenc.shape[-1]
|
| 223 |
+
) # rescale to keep the same scale
|
| 224 |
+
z_af = z_af * math.sqrt(z_af.shape[-1]) # rescale to
|
| 225 |
+
|
| 226 |
+
z_all = torch.cat([z_fxenc, z_af], dim=-1)
|
| 227 |
+
|
| 228 |
+
return z_all / math.sqrt(
|
| 229 |
+
z_all.shape[-1]
|
| 230 |
+
) # normalize by dividing by sqrt(dim) to keep the same scale
|
| 231 |
+
|
| 232 |
+
self.embedding_post_processing = embedding_post_processing
|
| 233 |
+
|
| 234 |
+
def get_log_rms_from_z(z):
|
| 235 |
+
|
| 236 |
+
z = z * math.sqrt(z.shape[-1]) # rescale to keep the same scale
|
| 237 |
+
AF = z[..., 2048:] # assuming the AF features are the last 2048 dimensions
|
| 238 |
+
AF = AF / math.sqrt(AF.shape[-1]) # normalize to unit variance
|
| 239 |
+
|
| 240 |
+
features = AFembedding.decode(AF)
|
| 241 |
+
log_rms = features[0]
|
| 242 |
+
|
| 243 |
+
return log_rms
|
| 244 |
+
|
| 245 |
+
def generate_Fx(
|
| 246 |
+
x, input_type="dry", num_samples=1, T=30, cfg_scale=1.0, Schurn=10
|
| 247 |
+
):
|
| 248 |
+
N, C, L = (
|
| 249 |
+
x.shape
|
| 250 |
+
) # B is the batch size, N is the number of tracks, C is the number of channels and L is the length of the audio
|
| 251 |
+
B = 1
|
| 252 |
+
|
| 253 |
+
shape = self.sampler.diff_params.default_shape
|
| 254 |
+
shape = [
|
| 255 |
+
num_samples,
|
| 256 |
+
N,
|
| 257 |
+
*shape[2:],
|
| 258 |
+
] # B is the batch size, we want to sample B samples
|
| 259 |
+
|
| 260 |
+
masks_fwd = torch.ones(
|
| 261 |
+
(B, N), dtype=torch.bool, device=self.device
|
| 262 |
+
) # Create masks for all tracks, assuming all tracks are present
|
| 263 |
+
masks_diff = torch.ones(
|
| 264 |
+
(num_samples, N), dtype=torch.bool, device=self.device
|
| 265 |
+
) # Create masks for all tracks, assuming all tracks are present
|
| 266 |
+
|
| 267 |
+
self.sampler.T = T
|
| 268 |
+
self.sampler.Schurn = Schurn # Set the Schurn parameter for the sampler
|
| 269 |
+
|
| 270 |
+
with torch.no_grad():
|
| 271 |
+
is_wet = "wet" in input_type
|
| 272 |
+
cond, x_preprocessed = self.sampler.diff_params.transform_forward(
|
| 273 |
+
x.unsqueeze(0),
|
| 274 |
+
is_condition=True,
|
| 275 |
+
is_test=True,
|
| 276 |
+
masks=masks_fwd,
|
| 277 |
+
is_wet=is_wet,
|
| 278 |
+
)
|
| 279 |
+
cond = cond.expand(
|
| 280 |
+
shape[0], -1, -1, -1
|
| 281 |
+
) # Expand the condition to match the batch size
|
| 282 |
+
preds, noise_init = self.sampler.predict_conditional(
|
| 283 |
+
shape,
|
| 284 |
+
cond=cond.contiguous(),
|
| 285 |
+
cfg_scale=cfg_scale,
|
| 286 |
+
device=self.device,
|
| 287 |
+
masks=masks_diff,
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
return preds
|
| 291 |
+
|
| 292 |
+
self.generate_Fx = lambda x, num_samples: generate_Fx(
|
| 293 |
+
x,
|
| 294 |
+
input_type="wet",
|
| 295 |
+
num_samples=num_samples,
|
| 296 |
+
T=self.method_args.T,
|
| 297 |
+
cfg_scale=self.method_args.cfg_scale,
|
| 298 |
+
Schurn=self.method_args.Schurn,
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def apply_rms(y_hat, z_pred):
|
| 303 |
+
"""
|
| 304 |
+
Apply RMS normalization to the generated audio y_hat based on the predicted features z_pred.
|
| 305 |
+
"""
|
| 306 |
+
pred_logrms = get_log_rms_from_z(
|
| 307 |
+
z_pred
|
| 308 |
+
) # get the log RMS from the generated features
|
| 309 |
+
pred_rms = 10 ** (pred_logrms / 20) # convert log RMS to linear scale
|
| 310 |
+
|
| 311 |
+
log_rms_y_hat = compute_log_rms_gated_max(
|
| 312 |
+
y_hat, sample_rate=44100, threshold=-60
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
rms_y_hat = 10 ** (log_rms_y_hat / 20) # convert log RMS to linear scale
|
| 316 |
+
|
| 317 |
+
gain = pred_rms / (
|
| 318 |
+
rms_y_hat + 1e-6
|
| 319 |
+
) # Compute the gain to apply to the generated audio
|
| 320 |
+
|
| 321 |
+
y_final = y_hat * gain.unsqueeze(-1)
|
| 322 |
+
|
| 323 |
+
return y_final
|
| 324 |
+
|
| 325 |
+
self.apply_rms = apply_rms
|
| 326 |
+
|
| 327 |
+
def apply_effects(x, z_pred):
|
| 328 |
+
segment_length = self.processor_segment_length
|
| 329 |
+
overlap = self.processor_overlap
|
| 330 |
+
total_length = x.shape[-1]
|
| 331 |
+
batch_size = x.shape[0]
|
| 332 |
+
|
| 333 |
+
# Normalize input and conditioning outside block loop
|
| 334 |
+
x_norm = x.mean(dim=1, keepdim=True)
|
| 335 |
+
|
| 336 |
+
if total_length > segment_length:
|
| 337 |
+
y_final = torch.zeros((batch_size, 2, total_length), device=x.device, dtype=x.dtype)
|
| 338 |
+
|
| 339 |
+
hann = torch.hann_window(overlap * 2, device=x.device, dtype=x.dtype)
|
| 340 |
+
hann_left = hann[:overlap].view(1, 1, -1)
|
| 341 |
+
hann_right = hann[overlap:].view(1, 1, -1)
|
| 342 |
+
|
| 343 |
+
step = segment_length - overlap
|
| 344 |
+
positions = list(range(0, total_length - overlap, step))
|
| 345 |
+
for i, start in enumerate(positions):
|
| 346 |
+
end = min(start + segment_length, total_length)
|
| 347 |
+
|
| 348 |
+
seg_x_norm = x_norm[..., start:end]
|
| 349 |
+
|
| 350 |
+
#check activity in seg_x_norm
|
| 351 |
+
|
| 352 |
+
rms_dry_segment=compute_log_rms_gated_max(seg_x_norm, sample_rate=44100) # Compute the log RMS of the dry audio
|
| 353 |
+
indices_non_silent = torch.where(rms_dry_segment > -45)[0] # Identify silent tracks
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
seg_x_norm_non_silent = seg_x_norm[indices_non_silent]
|
| 357 |
+
z_pred_non_silent = z_pred[indices_non_silent]
|
| 358 |
+
|
| 359 |
+
if "public" in self.FxProcessor_code:
|
| 360 |
+
seg_x_norm_non_silent = self.fx_normalizer(seg_x_norm_non_silent)
|
| 361 |
+
else:
|
| 362 |
+
seg_x_norm_non_silent = apply_RMS_normalization(seg_x_norm_non_silent, -25.0, device=self.device, use_gate=True)
|
| 363 |
+
seg_x_norm_non_silent = self.fx_normalizer(seg_x_norm_non_silent, use_gate=True)
|
| 364 |
+
|
| 365 |
+
with torch.no_grad():
|
| 366 |
+
seg_y_hat_non_silent=torch.zeros((seg_x_norm_non_silent.shape[0],2, seg_x_norm_non_silent.shape[2]), device=x.device, dtype=x.dtype)
|
| 367 |
+
#I thought it may be better (but less efficient) to run it like this instead of in parallel. To avoid OOM issues.
|
| 368 |
+
for i in range(seg_x_norm_non_silent.shape[0]):
|
| 369 |
+
seg_y_hat_non_silent[i] = self.fx_model(seg_x_norm_non_silent[i].unsqueeze(0), z_pred_non_silent[i].unsqueeze(0)).squeeze(0)
|
| 370 |
+
|
| 371 |
+
seg_y_hat_non_silent = apply_rms(seg_y_hat_non_silent, z_pred_non_silent)
|
| 372 |
+
|
| 373 |
+
#fill with zeros the silent segments
|
| 374 |
+
seg_y_hat= torch.zeros((seg_x_norm.shape[0], seg_y_hat_non_silent.shape[1], seg_y_hat_non_silent.shape[2]), device=x.device, dtype=x.dtype)
|
| 375 |
+
seg_y_hat[indices_non_silent]=seg_y_hat_non_silent
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
seg_len = end - start
|
| 379 |
+
|
| 380 |
+
if i == 0:
|
| 381 |
+
# First segment
|
| 382 |
+
y_final[..., start:end-overlap] += seg_y_hat[..., :seg_len-overlap]
|
| 383 |
+
y_final[..., end-overlap:end] += seg_y_hat[..., seg_len-overlap:] * hann_right
|
| 384 |
+
elif end == total_length:
|
| 385 |
+
# Last segment
|
| 386 |
+
y_final[..., start:start+overlap] += seg_y_hat[..., :overlap] * hann_left
|
| 387 |
+
y_final[..., start+overlap:end] += seg_y_hat[..., overlap:]
|
| 388 |
+
else:
|
| 389 |
+
# Middle segments
|
| 390 |
+
y_final[..., start:start+overlap] += seg_y_hat[..., :overlap] * hann_left
|
| 391 |
+
y_final[..., start+overlap:end-overlap] += seg_y_hat[..., overlap:seg_len-overlap]
|
| 392 |
+
y_final[..., end-overlap:end] += seg_y_hat[..., seg_len-overlap:] * hann_right
|
| 393 |
+
|
| 394 |
+
return y_final
|
| 395 |
+
|
| 396 |
+
else:
|
| 397 |
+
with torch.no_grad():
|
| 398 |
+
y_hat=torch.zeros((x_norm.shape[0], 2, x_norm.shape[2]), device=x.device, dtype=x.dtype)
|
| 399 |
+
for i in range(x_norm.shape[0]):
|
| 400 |
+
y_hat[i] = self.fx_model(x_norm[i].unsqueeze(0), z_pred[i].unsqueeze(0)).squeeze(0)
|
| 401 |
+
y_final = apply_rms(y_hat, z_pred)
|
| 402 |
+
return y_final
|
| 403 |
+
|
| 404 |
+
self.apply_effects = apply_effects
|
| 405 |
+
|
| 406 |
+
def select_high_energy_segment(self, x_dry, seq_length=525312):
|
| 407 |
+
C, L = x_dry.shape
|
| 408 |
+
|
| 409 |
+
track = x_dry
|
| 410 |
+
|
| 411 |
+
# Calculate energy for windows of size seq_length
|
| 412 |
+
num_windows = L - seq_length + 1
|
| 413 |
+
max_energy = 0
|
| 414 |
+
max_energy_start = 0
|
| 415 |
+
|
| 416 |
+
for i in range(0, num_windows, 1000): # Step by 1000 for efficiency
|
| 417 |
+
segment = track[..., i:i+seq_length]
|
| 418 |
+
energy = (segment ** 2).sum()
|
| 419 |
+
|
| 420 |
+
if energy > max_energy:
|
| 421 |
+
max_energy = energy
|
| 422 |
+
max_energy_start = i
|
| 423 |
+
|
| 424 |
+
# Fine-tune search around the best region
|
| 425 |
+
fine_start = max(0, max_energy_start - 1000)
|
| 426 |
+
fine_end = min(L - seq_length + 1, max_energy_start + 1000)
|
| 427 |
+
|
| 428 |
+
for i in range(fine_start, fine_end):
|
| 429 |
+
segment = track[..., i:i+seq_length]
|
| 430 |
+
energy = (segment ** 2).sum()
|
| 431 |
+
|
| 432 |
+
if energy > max_energy:
|
| 433 |
+
max_energy = energy
|
| 434 |
+
max_energy_start = i
|
| 435 |
+
|
| 436 |
+
return x_dry[:, max_energy_start:max_energy_start+seq_length]
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
def run_inference_single_song(self, exp_name="test_Sep28", directory=None, num_samples=1):
|
| 440 |
+
"""
|
| 441 |
+
Run the inference on a single example
|
| 442 |
+
"""
|
| 443 |
+
|
| 444 |
+
dry_files=glob.glob(os.path.join(directory, "*.wav"))
|
| 445 |
+
|
| 446 |
+
assert len(dry_files) > 0, f"No .wav files found in {directory}"
|
| 447 |
+
print(f"Found {len(dry_files)} dry files in {directory}")
|
| 448 |
+
print(dry_files)
|
| 449 |
+
|
| 450 |
+
dry_tracks=[]
|
| 451 |
+
dry_tracks_segments=[]
|
| 452 |
+
|
| 453 |
+
for f in dry_files:
|
| 454 |
+
x_dry_i, fs=load_audio(str(f), stereo=True)
|
| 455 |
+
x_dry_i=x_dry_i.to(self.device)
|
| 456 |
+
|
| 457 |
+
if fs!=self.sampler.diff_params.sample_rate:
|
| 458 |
+
x_dry_i=torchaudio.functional.resample(x_dry_i, orig_sr=fs, target_sr=self.sampler.diff_params.sample_rate)
|
| 459 |
+
#stereo to mono
|
| 460 |
+
x_dry_i = x_dry_i.mean(dim=0, keepdim=True)
|
| 461 |
+
dry_tracks.append(x_dry_i )
|
| 462 |
+
|
| 463 |
+
if x_dry_i.shape[-1] >= self.load_segment_length:
|
| 464 |
+
# search for each track the segment of seq_length size that has highest energy
|
| 465 |
+
# x_dry_i shape is (N, C, L) where N is the number of tracks, C is the number of channels and L is the length of the audio
|
| 466 |
+
x_dry_i_segment = self.select_high_energy_segment(x_dry_i, seq_length=self.load_segment_length)
|
| 467 |
+
else:
|
| 468 |
+
raise ValueError(f"Input audio {f} is too short, needs to be at least {self.load_segment_length/self.sampler.diff_params.sample_rate:.2f} seconds.")
|
| 469 |
+
|
| 470 |
+
dry_tracks_segments.append(x_dry_i_segment)
|
| 471 |
+
|
| 472 |
+
x_dry=torch.stack(dry_tracks, dim=0) # shape (N, C, L) where N is the number of tracks, C is the number of channels and L is the length of the audio
|
| 473 |
+
x_dry_segments= torch.stack(dry_tracks_segments, dim=0) # shape (N, C, L) where N is the number of tracks, C is the number of channels and L is the length of the audio
|
| 474 |
+
|
| 475 |
+
#first check if all tracks in x_dry have activity (RMS > -60 dBFS)
|
| 476 |
+
rms_dry=compute_log_rms_gated_max(x_dry_segments, sample_rate=44100) # Compute the log RMS of the dry audio
|
| 477 |
+
silent_tracks = rms_dry < -60 # Identify silent tracks
|
| 478 |
+
silent_tracks = silent_tracks.squeeze() # Remove singleton dimensions
|
| 479 |
+
|
| 480 |
+
if silent_tracks.any():
|
| 481 |
+
print(f"Removing {silent_tracks.sum()} silent tracks from the input audio.")
|
| 482 |
+
#shape before removing silent tracks is (N, C, L)
|
| 483 |
+
x_dry = x_dry[~silent_tracks] # Remove silent tracks
|
| 484 |
+
x_dry_segments = x_dry_segments[~silent_tracks] # Remove silent tracks
|
| 485 |
+
|
| 486 |
+
preds = self.generate_Fx(x_dry_segments, num_samples)
|
| 487 |
+
|
| 488 |
+
z_pred = self.embedding_post_processing(
|
| 489 |
+
preds
|
| 490 |
+
) # post-process the generated features
|
| 491 |
+
|
| 492 |
+
del self.sampler
|
| 493 |
+
|
| 494 |
+
for i in range(num_samples):
|
| 495 |
+
z_i = z_pred[
|
| 496 |
+
i
|
| 497 |
+
] # Randomly sample 100 features from the generated features
|
| 498 |
+
|
| 499 |
+
y_final = self.apply_effects(
|
| 500 |
+
x_dry.clone(), z_i
|
| 501 |
+
) # Apply the effects to the input audio
|
| 502 |
+
y_hat_mixture = y_final.sum(dim=0, keepdim=False)
|
| 503 |
+
|
| 504 |
+
# peak normalization of y_hat_mixture
|
| 505 |
+
peak = torch.max(torch.abs(y_hat_mixture))
|
| 506 |
+
y_hat_mixture /= peak # Normalize the audio to [-1, 1]
|
| 507 |
+
|
| 508 |
+
filename="MEGAMI_inference"+f"_sample{i}.wav"
|
| 509 |
+
os.makedirs(f"{directory}/{exp_name}", exist_ok=True)
|
| 510 |
+
sf.write(
|
| 511 |
+
f"{directory}/{exp_name}/{filename}",
|
| 512 |
+
y_hat_mixture.cpu().clamp(-1, 1).numpy().T,
|
| 513 |
+
44100,
|
| 514 |
+
subtype="PCM_16",
|
| 515 |
+
)
|
| 516 |
+
|
| 517 |
+
|
inference/sampler.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import omegaconf
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Sampler():
|
| 7 |
+
|
| 8 |
+
def __init__(self, model, diff_params, args):
|
| 9 |
+
|
| 10 |
+
self.model = model.eval() #is it ok to do this here?
|
| 11 |
+
self.diff_params = diff_params #same as training, useful if we need to apply a wrapper or something
|
| 12 |
+
self.args=args
|
| 13 |
+
if self.args.tester.sampling_params.same_as_training:
|
| 14 |
+
self.sde_hp = diff_params.sde_hp
|
| 15 |
+
else:
|
| 16 |
+
self.sde_hp = self.args.tester.sampling_params.sde_hp
|
| 17 |
+
|
| 18 |
+
self.T = self.args.tester.sampling_params.T
|
| 19 |
+
self.step_counter = 0
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
#def setup_wandb(self):
|
| 23 |
+
# config=omegaconf.OmegaConf.to_container(
|
| 24 |
+
# self.args, resolve=True, throw_on_missing=True
|
| 25 |
+
# )
|
| 26 |
+
# self.wandb_run=wandb.init(project=self.args.logging.wandb.project, entity=self.args.logging.wandb.entity, config=config)
|
| 27 |
+
# self.wandb_run.name=self.args.tester.wandb.run_name +os.path.basename(self.args.model_dir)+"_"+self.args.exp.exp_name+"_"+self.wandb_run.id
|
| 28 |
+
|
| 29 |
+
|
inference/sampler_euler_heun_multitrack.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from inference.sampler import Sampler
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class SamplerEulerHeun(Sampler):
|
| 6 |
+
|
| 7 |
+
def __init__(self, model, diff_params, args):
|
| 8 |
+
super().__init__(model, diff_params, args)
|
| 9 |
+
|
| 10 |
+
# stochasticity parameters
|
| 11 |
+
self.Schurn = self.args.tester.sampling_params.Schurn
|
| 12 |
+
self.Snoise = self.args.tester.sampling_params.Snoise
|
| 13 |
+
self.Stmin = self.args.tester.sampling_params.Stmin
|
| 14 |
+
self.Stmax = self.args.tester.sampling_params.Stmax
|
| 15 |
+
|
| 16 |
+
# order of the sampler
|
| 17 |
+
self.order = self.args.tester.sampling_params.order
|
| 18 |
+
self.cond=None
|
| 19 |
+
self.cfg_scale = 1.0
|
| 20 |
+
|
| 21 |
+
def predict_DPS(
|
| 22 |
+
self,
|
| 23 |
+
shape, # observations (lowpssed signal) Tensor with shape ??
|
| 24 |
+
cond=None,
|
| 25 |
+
cfg_scale=1.0,
|
| 26 |
+
device=None, # device
|
| 27 |
+
apply_inverse_transform=True, # whether to apply inverse transform
|
| 28 |
+
taxonomy=None, # taxonomy for the conditional input
|
| 29 |
+
masks=None, # masks for the conditional input
|
| 30 |
+
fwd_operator=None,
|
| 31 |
+
zeta=None,
|
| 32 |
+
dtype=torch.float32, # data type
|
| 33 |
+
):
|
| 34 |
+
self.cond = cond
|
| 35 |
+
assert self.cond is not None, "Conditional input is None"
|
| 36 |
+
|
| 37 |
+
self.taxonomy = taxonomy
|
| 38 |
+
self.masks = masks
|
| 39 |
+
|
| 40 |
+
self.cfg_scale = cfg_scale
|
| 41 |
+
|
| 42 |
+
# get the noise schedule
|
| 43 |
+
t = self.create_schedule().to(device).to(torch.float32)
|
| 44 |
+
|
| 45 |
+
# sample prior
|
| 46 |
+
x = self.diff_params.sample_prior(t=t[0], shape=shape, dtype=dtype)
|
| 47 |
+
|
| 48 |
+
# parameter for langevin stochasticity, if Schurn is 0, gamma will be 0 to, so the sampler will be deterministic
|
| 49 |
+
gamma = self.get_gamma(t).to(device)
|
| 50 |
+
|
| 51 |
+
for i in range(0, self.T-1, 1):
|
| 52 |
+
self.step_counter = i
|
| 53 |
+
x, x_den = self.step_DPS(x, t[i], t[i + 1], gamma[i], fwd_operator, zeta)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if apply_inverse_transform:
|
| 57 |
+
with torch.no_grad():
|
| 58 |
+
x_den_wave=self.diff_params.transform_inverse(x_den.detach())
|
| 59 |
+
|
| 60 |
+
return x_den_wave.detach(), None
|
| 61 |
+
else:
|
| 62 |
+
return x_den.detach(), None
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def predict_conditional(
|
| 66 |
+
self,
|
| 67 |
+
shape, # observations (lowpssed signal) Tensor with shape ??
|
| 68 |
+
cond=None,
|
| 69 |
+
cfg_scale=1.0,
|
| 70 |
+
device=None, # device
|
| 71 |
+
apply_inverse_transform=True, # whether to apply inverse transform
|
| 72 |
+
taxonomy=None, # taxonomy for the conditional input
|
| 73 |
+
masks=None, # masks for the conditional input
|
| 74 |
+
dtype=torch.float32, # data type
|
| 75 |
+
):
|
| 76 |
+
self.cond = cond
|
| 77 |
+
assert self.cond is not None, "Conditional input is None"
|
| 78 |
+
|
| 79 |
+
self.taxonomy = taxonomy
|
| 80 |
+
self.masks = masks
|
| 81 |
+
|
| 82 |
+
self.cfg_scale = cfg_scale
|
| 83 |
+
|
| 84 |
+
# get the noise schedule
|
| 85 |
+
t = self.create_schedule().to(device).to(torch.float32)
|
| 86 |
+
|
| 87 |
+
# sample prior
|
| 88 |
+
x = self.diff_params.sample_prior(t=t[0], shape=shape, dtype=dtype)
|
| 89 |
+
|
| 90 |
+
# parameter for langevin stochasticity, if Schurn is 0, gamma will be 0 to, so the sampler will be deterministic
|
| 91 |
+
gamma = self.get_gamma(t).to(device)
|
| 92 |
+
|
| 93 |
+
for i in range(0, self.T-1, 1):
|
| 94 |
+
self.step_counter = i
|
| 95 |
+
x, x_den = self.step(x, t[i], t[i + 1], gamma[i])
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
if apply_inverse_transform:
|
| 99 |
+
with torch.no_grad():
|
| 100 |
+
x_den_wave=self.diff_params.transform_inverse(x_den.detach())
|
| 101 |
+
|
| 102 |
+
return x_den_wave.detach(), None
|
| 103 |
+
else:
|
| 104 |
+
return x_den.detach(), None
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def predict_unconditional(
|
| 108 |
+
self,
|
| 109 |
+
shape, # observations (lowpssed signal) Tensor with shape ??
|
| 110 |
+
device
|
| 111 |
+
):
|
| 112 |
+
self.y = None
|
| 113 |
+
self.degradation = None
|
| 114 |
+
|
| 115 |
+
return self.predict(shape, device)
|
| 116 |
+
|
| 117 |
+
def get_gamma(self, t):
|
| 118 |
+
"""
|
| 119 |
+
Get the parameter gamma that defines the stochasticity of the sampler
|
| 120 |
+
Args
|
| 121 |
+
t (Tensor): shape: (N_steps, ) Tensor of timesteps, from which we will compute gamma
|
| 122 |
+
"""
|
| 123 |
+
N = t.shape[0]
|
| 124 |
+
gamma = torch.zeros(t.shape).to(t.device)
|
| 125 |
+
|
| 126 |
+
# If desired, only apply stochasticity between a certain range of noises Stmin is 0 by default and Stmax is a huge number by default. (Unless these parameters are specified, this does nothing)
|
| 127 |
+
indexes = torch.logical_and(t > self.Stmin, t < self.Stmax)
|
| 128 |
+
|
| 129 |
+
# We use Schurn=5 as the default in our experiments
|
| 130 |
+
gamma[indexes] = gamma[indexes] + torch.min(torch.Tensor([self.Schurn / N, 2 ** (1 / 2) - 1]))
|
| 131 |
+
|
| 132 |
+
return gamma
|
| 133 |
+
|
| 134 |
+
def get_Tweedie_estimate(self, x, t_i):
|
| 135 |
+
|
| 136 |
+
if x.ndim==2:
|
| 137 |
+
x_=x.unsqueeze(1)
|
| 138 |
+
elif x.ndim==3:
|
| 139 |
+
pass
|
| 140 |
+
|
| 141 |
+
if self.cond is not None:
|
| 142 |
+
x_hat = self.diff_params.denoiser(x, self.model, t_i, cond=self.cond, cfg_scale=self.cfg_scale, taxonomy=self.taxonomy, masks=self.masks)
|
| 143 |
+
else:
|
| 144 |
+
x_hat = self.diff_params.denoiser(x, self.model, t_i, taxonomy=self.taxonomy, masks=self.masks)
|
| 145 |
+
|
| 146 |
+
return x_hat
|
| 147 |
+
|
| 148 |
+
def Tweedie2score(self, tweedie, xt, t):
|
| 149 |
+
return self.diff_params.Tweedie2score(tweedie, xt, t)
|
| 150 |
+
|
| 151 |
+
def score2Tweedie(self, score, xt, t):
|
| 152 |
+
return self.diff_params.score2Tweedie(score, xt, t)
|
| 153 |
+
|
| 154 |
+
def stochastic_timestep(self, x, t, gamma, Snoise=1):
|
| 155 |
+
t_hat = t + gamma * t # if gamma_sig[i]==0 this is a deterministic step, make sure it doed not crash
|
| 156 |
+
t_hat=torch.clamp(t_hat, 0, self.diff_params.max_t)
|
| 157 |
+
epsilon = torch.randn(x.shape).to(x.device) * Snoise # sample Gaussiannoise, Snoise is 1 by default
|
| 158 |
+
if t_hat <= t:
|
| 159 |
+
x_hat = x
|
| 160 |
+
#print(f"t_hat<=t, gamma {gamma}")
|
| 161 |
+
else:
|
| 162 |
+
#print(t_hat, t)
|
| 163 |
+
x_hat = x + ((t_hat ** 2 - t ** 2) ** (1 / 2)) * epsilon # Perturb data
|
| 164 |
+
return x_hat, t_hat
|
| 165 |
+
|
| 166 |
+
def step_DPS(self, x_i, t_i, t_iplus1, gamma_i , fwd_operator=None, zeta=None):
|
| 167 |
+
|
| 168 |
+
#with torch.no_grad():
|
| 169 |
+
|
| 170 |
+
x_hat, t_hat=self.stochastic_timestep(x_i, t_i, gamma_i)
|
| 171 |
+
|
| 172 |
+
x_hat.requires_grad=True
|
| 173 |
+
|
| 174 |
+
x_den = self.get_Tweedie_estimate(x_hat, t_hat)
|
| 175 |
+
|
| 176 |
+
#optionally L2 normalize here...
|
| 177 |
+
|
| 178 |
+
#compute likelihood score
|
| 179 |
+
loss=fwd_operator(x_den)
|
| 180 |
+
|
| 181 |
+
loss.backward(retain_graph=False)
|
| 182 |
+
|
| 183 |
+
grads= x_hat.grad
|
| 184 |
+
|
| 185 |
+
#lets normalize the grads
|
| 186 |
+
norm_factor= torch.sqrt(torch.tensor(x_hat.view(-1).shape[0])).to(x_hat.device)
|
| 187 |
+
normguide=torch.norm(grads)/ norm_factor
|
| 188 |
+
zeta= zeta/(normguide+1e-8)
|
| 189 |
+
lh_score=-zeta*grads/t_hat
|
| 190 |
+
|
| 191 |
+
x_hat.detach_() # detach x_hat to avoid accumulating gradients
|
| 192 |
+
x_den.detach_() # detach x_den to avoid accumulating gradients
|
| 193 |
+
|
| 194 |
+
#compute normal score
|
| 195 |
+
score = self.Tweedie2score(x_den, x_hat, t_hat)
|
| 196 |
+
|
| 197 |
+
ode_integrand = self.diff_params._ode_integrand(x_hat, t_hat, score+ lh_score)
|
| 198 |
+
|
| 199 |
+
dt = t_iplus1 - t_hat
|
| 200 |
+
|
| 201 |
+
x_iplus1 = x_hat + dt * ode_integrand
|
| 202 |
+
|
| 203 |
+
return x_iplus1, x_den
|
| 204 |
+
|
| 205 |
+
def step(self, x_i, t_i, t_iplus1, gamma_i ):
|
| 206 |
+
|
| 207 |
+
with torch.no_grad():
|
| 208 |
+
x_hat, t_hat = self.stochastic_timestep(x_i, t_i, gamma_i)
|
| 209 |
+
x_den = self.get_Tweedie_estimate(x_hat, t_hat)
|
| 210 |
+
score = self.Tweedie2score(x_den, x_hat, t_hat)
|
| 211 |
+
ode_integrand = self.diff_params._ode_integrand(x_hat, t_hat, score)
|
| 212 |
+
dt = t_iplus1 - t_hat
|
| 213 |
+
|
| 214 |
+
if t_iplus1 != 0 and self.order == 2: # second order correction
|
| 215 |
+
t_prime = t_iplus1
|
| 216 |
+
x_prime = x_hat + dt * ode_integrand
|
| 217 |
+
x_den = self.get_Tweedie_estimate(x_prime, t_prime)
|
| 218 |
+
score = self.Tweedie2score(x_den, x_prime, t_prime)
|
| 219 |
+
ode_integrand_next = self.diff_params._ode_integrand(x_prime, t_prime, score)
|
| 220 |
+
ode_integrand_midpoint = .5 * (ode_integrand + ode_integrand_next)
|
| 221 |
+
x_iplus1 = x_hat + dt * ode_integrand_midpoint
|
| 222 |
+
|
| 223 |
+
else:
|
| 224 |
+
x_iplus1 = x_hat + dt * ode_integrand
|
| 225 |
+
|
| 226 |
+
return x_iplus1, x_den
|
| 227 |
+
|
| 228 |
+
def get_domain_shape(self, shape, device):
|
| 229 |
+
|
| 230 |
+
x=torch.zeros(shape, dtype=torch.float32).to(device)
|
| 231 |
+
X=self.diff_params.transform_forward(x)
|
| 232 |
+
|
| 233 |
+
return X.shape, X.dtype
|
| 234 |
+
|
| 235 |
+
def predict(
|
| 236 |
+
self,
|
| 237 |
+
shape, # observations (lowpssed signal) Tensor with shape ??
|
| 238 |
+
device, # lambda function
|
| 239 |
+
dtype=torch.float32, # data type
|
| 240 |
+
apply_inverse_transform=True # whether to apply inverse transform
|
| 241 |
+
):
|
| 242 |
+
|
| 243 |
+
# get the noise schedule
|
| 244 |
+
t = self.create_schedule().to(device).to(torch.float32)
|
| 245 |
+
|
| 246 |
+
# sample prior
|
| 247 |
+
x = self.diff_params.sample_prior(t=t[0], shape=shape, dtype=dtype)
|
| 248 |
+
|
| 249 |
+
# parameter for langevin stochasticity, if Schurn is 0, gamma will be 0 to, so the sampler will be deterministic
|
| 250 |
+
gamma = self.get_gamma(t).to(device)
|
| 251 |
+
|
| 252 |
+
for i in range(0, self.T-1, 1):
|
| 253 |
+
self.step_counter = i
|
| 254 |
+
x, x_den = self.step(x, t[i], t[i + 1], gamma[i])
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
if apply_inverse_transform:
|
| 258 |
+
with torch.no_grad():
|
| 259 |
+
x_den_wave=self.diff_params.transform_inverse(x_den.detach())
|
| 260 |
+
|
| 261 |
+
return x_den_wave.detach(), None
|
| 262 |
+
else:
|
| 263 |
+
return x_den.detach(), None
|
| 264 |
+
|
| 265 |
+
def create_schedule(self, sigma_min=None, sigma_max=None, rho=None, T=None):
|
| 266 |
+
"""
|
| 267 |
+
EDM schedule by default
|
| 268 |
+
"""
|
| 269 |
+
if T is None:
|
| 270 |
+
T=self.T
|
| 271 |
+
|
| 272 |
+
if self.args.tester.sampling_params.schedule == "edm":
|
| 273 |
+
if sigma_min is None:
|
| 274 |
+
sigma_min = self.sde_hp.sigma_min
|
| 275 |
+
if sigma_max is None:
|
| 276 |
+
sigma_max = self.sde_hp.sigma_max
|
| 277 |
+
if rho is None:
|
| 278 |
+
rho = self.sde_hp.rho
|
| 279 |
+
a = torch.arange(0, T)
|
| 280 |
+
t = (sigma_max**(1/rho) + a/(T-1) *(sigma_min**(1/rho) - sigma_max**(1/rho)))**rho
|
| 281 |
+
t[-1] = 0
|
| 282 |
+
return t
|
| 283 |
+
|
| 284 |
+
elif self.args.tester.sampling_params.schedule == "song":
|
| 285 |
+
if sigma_min is None:
|
| 286 |
+
sigma_min = self.sde_hp.sigma_min
|
| 287 |
+
if sigma_max is None:
|
| 288 |
+
sigma_max = self.sde_hp.sigma_max
|
| 289 |
+
if rho is None:
|
| 290 |
+
rho = self.sde_hp.rho
|
| 291 |
+
eps = 0. if not "t_eps" in self.args.tester.diff_params.keys() else self.args.tester.diff_params.t_eps
|
| 292 |
+
a = torch.arange(eps, T+1)
|
| 293 |
+
t = sigma_min**2 * (sigma_max / sigma_min)**(2*a)
|
| 294 |
+
t[-1] = 0
|
| 295 |
+
return t
|
| 296 |
+
elif self.args.tester.sampling_params.schedule == "FM":
|
| 297 |
+
t = torch.linspace(1, 0, T+1)
|
| 298 |
+
return t
|
| 299 |
+
|
| 300 |
+
else:
|
| 301 |
+
raise NotImplementedError(f"schedule {self.args.tester.sampling_params.schedule} not implemented")
|
| 302 |
+
|
inference/validator_FxProcessor.py
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 Sony Research
|
| 2 |
+
# Licensed under CC BY-NC-SA 4.0
|
| 3 |
+
# See LICENSE file for details
|
| 4 |
+
|
| 5 |
+
from datetime import date
|
| 6 |
+
import math
|
| 7 |
+
import io
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
from functools import partial
|
| 10 |
+
import re
|
| 11 |
+
import torch
|
| 12 |
+
import os
|
| 13 |
+
import wandb
|
| 14 |
+
import copy
|
| 15 |
+
from glob import glob
|
| 16 |
+
from tqdm import tqdm
|
| 17 |
+
import omegaconf
|
| 18 |
+
import hydra
|
| 19 |
+
import utils.log as utils_logging
|
| 20 |
+
import utils.training_utils as tr_utils
|
| 21 |
+
|
| 22 |
+
import soundfile as sf
|
| 23 |
+
import numpy as np
|
| 24 |
+
import torchaudio
|
| 25 |
+
|
| 26 |
+
from utils.data_utils import apply_RMS_normalization
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class ValidatorFxProcessor:
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
args,
|
| 33 |
+
network,
|
| 34 |
+
test_set_dict=None,
|
| 35 |
+
device=None,
|
| 36 |
+
in_training=False,
|
| 37 |
+
):
|
| 38 |
+
self.args = args
|
| 39 |
+
self.network = network
|
| 40 |
+
self.device = device
|
| 41 |
+
self.test_set_dict = test_set_dict
|
| 42 |
+
|
| 43 |
+
self.use_wandb = False # hardcoded for now
|
| 44 |
+
self.in_training = in_training
|
| 45 |
+
|
| 46 |
+
if in_training:
|
| 47 |
+
self.use_wandb = True
|
| 48 |
+
# Will inherit wandb_run from Trainer
|
| 49 |
+
else: # If we use the tester in training, we will log in WandB in the Trainer() class, no need to create all these paths
|
| 50 |
+
torch.backends.cudnn.benchmark = True
|
| 51 |
+
if self.device is None:
|
| 52 |
+
self.device = torch.device(
|
| 53 |
+
"cuda" if torch.cuda.is_available() else "cpu"
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
self.setup_wandb()
|
| 57 |
+
|
| 58 |
+
if self.args.tester.compute_metrics:
|
| 59 |
+
self.metrics_dict = self.prepare_metrics(self.args.tester.metrics)
|
| 60 |
+
else:
|
| 61 |
+
self.metrics_dict = {}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
self.RMS_norm = (
|
| 65 |
+
self.args.exp.RMS_norm
|
| 66 |
+
) # Use fixed RMS for evaluation, hardcoded for now
|
| 67 |
+
|
| 68 |
+
if self.args.exp.style_encoder_type == "FxEncoder++_DynamicFeatures":
|
| 69 |
+
|
| 70 |
+
Fxencoder_kwargs = self.args.exp.fx_encoder_plusplus_args
|
| 71 |
+
|
| 72 |
+
from utils.feature_extractors.load_features import (
|
| 73 |
+
load_fx_encoder_plusplus_2048,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
feat_extractor = load_fx_encoder_plusplus_2048(
|
| 77 |
+
Fxencoder_kwargs, self.device
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
from utils.feature_extractors.AF_features_embedding import (
|
| 81 |
+
AF_fourier_embedding,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
AFembedding = AF_fourier_embedding(device=self.device)
|
| 85 |
+
|
| 86 |
+
def fxencode_fn(x):
|
| 87 |
+
"""
|
| 88 |
+
x: tensor of shape [B, C, L] where B is the batch size, C is the number of channels and L is the length of the audio
|
| 89 |
+
"""
|
| 90 |
+
z = feat_extractor(x)
|
| 91 |
+
z = torch.nn.functional.normalize(
|
| 92 |
+
z, dim=-1, p=2
|
| 93 |
+
) # normalize to unit variance
|
| 94 |
+
z = z * math.sqrt(z.shape[-1]) # rescale to keep the same scale
|
| 95 |
+
|
| 96 |
+
z_af, _ = AFembedding.encode(x)
|
| 97 |
+
# embedding is l2 normalized, normalize to unit variance
|
| 98 |
+
z_af = z_af * math.sqrt(
|
| 99 |
+
z_af.shape[-1]
|
| 100 |
+
) # rescale to keep the same scale
|
| 101 |
+
|
| 102 |
+
# concatenate z and z_af (rescaling with sqrt(dim) to keep the same scale)
|
| 103 |
+
z_all = torch.cat([z, z_af], dim=-1)
|
| 104 |
+
|
| 105 |
+
norm_z = z_all / math.sqrt(
|
| 106 |
+
z_all.shape[-1]
|
| 107 |
+
) # normalize by dividing by sqrt(dim) to keep the same scale
|
| 108 |
+
|
| 109 |
+
return norm_z
|
| 110 |
+
|
| 111 |
+
self.style_encode = fxencode_fn
|
| 112 |
+
else:
|
| 113 |
+
raise NotImplementedError(
|
| 114 |
+
"Only FxEncoder++_DynamicFeatures is implemented for now"
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
if self.args.exp.apply_fxnorm:
|
| 118 |
+
self.fx_normalizer = hydra.utils.instantiate(self.args.exp.fxnorm)
|
| 119 |
+
|
| 120 |
+
def setup_wandb(self):
|
| 121 |
+
"""
|
| 122 |
+
Configure wandb, open a new run and log the configuration.
|
| 123 |
+
"""
|
| 124 |
+
config = omegaconf.OmegaConf.to_container(
|
| 125 |
+
self.args, resolve=True, throw_on_missing=True
|
| 126 |
+
)
|
| 127 |
+
self.wandb_run = wandb.init(
|
| 128 |
+
project="testing" + self.args.tester.wandb.project,
|
| 129 |
+
entity=self.args.tester.wandb.entity,
|
| 130 |
+
config=config,
|
| 131 |
+
tags=self.args.tester.wandb.tags,
|
| 132 |
+
)
|
| 133 |
+
# wandb.watch(self.network,
|
| 134 |
+
# log_freq=self.args.logging.heavy_log_interval)
|
| 135 |
+
|
| 136 |
+
self.wandb_run.name = self.args.tester.wandb.run_name
|
| 137 |
+
self.use_wandb = True
|
| 138 |
+
|
| 139 |
+
def setup_wandb_run(self, run):
|
| 140 |
+
# get the wandb run object from outside (in trainer.py or somewhere else)
|
| 141 |
+
self.wandb_run = run
|
| 142 |
+
self.use_wandb = True
|
| 143 |
+
|
| 144 |
+
def load_latest_checkpoint(self):
|
| 145 |
+
# load the latest checkpoint from self.args.model_dir
|
| 146 |
+
try:
|
| 147 |
+
# find latest checkpoint_id
|
| 148 |
+
save_basename = f"{self.args.exp.exp_name}-*.pt"
|
| 149 |
+
save_name = f"{self.args.model_dir}/{save_basename}"
|
| 150 |
+
list_weights = glob(save_name)
|
| 151 |
+
id_regex = re.compile(f"{self.args.exp.exp_name}-(\d*)\.pt")
|
| 152 |
+
list_ids = [
|
| 153 |
+
int(id_regex.search(weight_path).groups()[0])
|
| 154 |
+
for weight_path in list_weights
|
| 155 |
+
]
|
| 156 |
+
checkpoint_id = max(list_ids)
|
| 157 |
+
|
| 158 |
+
state_dict = torch.load(
|
| 159 |
+
f"{self.args.model_dir}/{self.args.exp.exp_name}-{checkpoint_id}.pt",
|
| 160 |
+
map_location=self.device,
|
| 161 |
+
)
|
| 162 |
+
try:
|
| 163 |
+
self.network.load_state_dict(state_dict["network"])
|
| 164 |
+
except Exception as e:
|
| 165 |
+
print(e)
|
| 166 |
+
print("Failed to load in strict mode, trying again without strict mode")
|
| 167 |
+
self.network.load_state_dict(state_dict["model"], strict=False)
|
| 168 |
+
|
| 169 |
+
print(f"Loaded checkpoint {checkpoint_id}")
|
| 170 |
+
return True
|
| 171 |
+
except (FileNotFoundError, ValueError):
|
| 172 |
+
raise ValueError("No checkpoint found")
|
| 173 |
+
|
| 174 |
+
def load_checkpoint(self, path):
|
| 175 |
+
state_dict = torch.load(path, map_location=self.device, weights_only=False)
|
| 176 |
+
print("state_dict keys:", state_dict.keys())
|
| 177 |
+
try:
|
| 178 |
+
self.it = state_dict["it"]
|
| 179 |
+
except:
|
| 180 |
+
self.it = 0
|
| 181 |
+
|
| 182 |
+
print(f"loading checkpoint {self.it}")
|
| 183 |
+
return tr_utils.load_state_dict(state_dict, network=self.network)
|
| 184 |
+
|
| 185 |
+
def log_figure(self, fig, name: str, step=None):
|
| 186 |
+
# Save the figure to a buffer
|
| 187 |
+
|
| 188 |
+
self.wandb_run.log(
|
| 189 |
+
{name: wandb.Image(fig)}, step=step if step is not None else self.it
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
def log_metric(self, value, name: str, step=None):
|
| 193 |
+
# print("logging metric it:", self.it, "name:", name)
|
| 194 |
+
self.wandb_run.log({name: value}, step=step if step is not None else self.it)
|
| 195 |
+
|
| 196 |
+
def log_audio(self, pred, name: str, it=None):
|
| 197 |
+
if it is None:
|
| 198 |
+
it = self.it
|
| 199 |
+
if self.use_wandb:
|
| 200 |
+
pred = pred.permute(1, 0)
|
| 201 |
+
self.wandb_run.log(
|
| 202 |
+
{
|
| 203 |
+
name: wandb.Audio(
|
| 204 |
+
pred.detach().cpu().numpy(),
|
| 205 |
+
sample_rate=self.args.exp.sample_rate,
|
| 206 |
+
)
|
| 207 |
+
},
|
| 208 |
+
step=it,
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
# ------------- UNCONDITIONAL SAMPLING ---------------#
|
| 212 |
+
|
| 213 |
+
##############################
|
| 214 |
+
### UNCONDITIONAL SAMPLING ###
|
| 215 |
+
##############################
|
| 216 |
+
|
| 217 |
+
def prepare_metrics(self, metrics):
|
| 218 |
+
metrics_dict = {}
|
| 219 |
+
for metric in metrics:
|
| 220 |
+
print(f"Preparing metric {metric}")
|
| 221 |
+
if "pairwise" in metric:
|
| 222 |
+
from utils.evaluation.pairwise_metrics import metric_factory
|
| 223 |
+
|
| 224 |
+
metrics_dict[metric] = metric_factory(
|
| 225 |
+
metric, self.args.exp.sample_rate, **self.args.tester
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
return metrics_dict
|
| 229 |
+
|
| 230 |
+
def test_paired(self, mode, exp_description=""):
|
| 231 |
+
|
| 232 |
+
# self.it = 0
|
| 233 |
+
for k, test_set in self.test_set_dict.items():
|
| 234 |
+
|
| 235 |
+
print(f"Testing on {k} set", k)
|
| 236 |
+
|
| 237 |
+
assert len(test_set) != 0, "No samples found in test set"
|
| 238 |
+
|
| 239 |
+
dict_y = {}
|
| 240 |
+
dict_x = {}
|
| 241 |
+
dict_y_hat = {}
|
| 242 |
+
|
| 243 |
+
i = 0
|
| 244 |
+
|
| 245 |
+
for x, y in tqdm(test_set):
|
| 246 |
+
|
| 247 |
+
B, C, T = y.shape
|
| 248 |
+
|
| 249 |
+
x = x.to(self.device).float()
|
| 250 |
+
if x.shape[-1] > self.args.exp.audio_len:
|
| 251 |
+
x = x[:, :, : self.args.exp.audio_len]
|
| 252 |
+
elif x.shape[-1] < self.args.exp.audio_len:
|
| 253 |
+
raise ValueError(
|
| 254 |
+
f"Sample length {x.shape[-1]} is less than expected {self.args.exp.audio_len}"
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
if mode == "paired":
|
| 258 |
+
y = y.to(self.device).float()
|
| 259 |
+
|
| 260 |
+
if y.shape[-1] > self.args.exp.audio_len:
|
| 261 |
+
y = y[:, :, : self.args.exp.audio_len]
|
| 262 |
+
elif y.shape[-1] < self.args.exp.audio_len:
|
| 263 |
+
raise ValueError(
|
| 264 |
+
f"Sample length {y.shape[-1]} is less than expected {self.args.exp.audio_len}"
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
if x.shape[1] == 2:
|
| 268 |
+
x = x.mean(
|
| 269 |
+
dim=1, keepdim=True
|
| 270 |
+
) # expand to [B*N, 1, L] to keep the shape consistent
|
| 271 |
+
|
| 272 |
+
# RMS normalization of x and y
|
| 273 |
+
|
| 274 |
+
if self.args.exp.apply_fxnorm:
|
| 275 |
+
x = self.fx_normalizer(
|
| 276 |
+
x,
|
| 277 |
+
RMS=self.args.exp.RMS_norm,
|
| 278 |
+
use_gate=self.args.exp.use_gated_RMSnorm,
|
| 279 |
+
)
|
| 280 |
+
else:
|
| 281 |
+
x = apply_RMS_normalization(
|
| 282 |
+
x,
|
| 283 |
+
self.args.exp.RMS_norm,
|
| 284 |
+
use_gate=self.args.exp.use_gated_RMSnorm,
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
if "baseline" in mode:
|
| 288 |
+
if mode == "baseline_dry":
|
| 289 |
+
preds = x # Just return the dry vocals as baseline
|
| 290 |
+
elif mode == "baseline_autoencoder":
|
| 291 |
+
preds = self.autoencoder_reconstruction(
|
| 292 |
+
y
|
| 293 |
+
) # Just return the dry vocals as baseline
|
| 294 |
+
elif mode == "baseline_random":
|
| 295 |
+
raise NotImplementedError(
|
| 296 |
+
"Baseline random sampling not implemented yet"
|
| 297 |
+
)
|
| 298 |
+
pass
|
| 299 |
+
else:
|
| 300 |
+
with torch.no_grad():
|
| 301 |
+
z = self.style_encode(y)
|
| 302 |
+
try:
|
| 303 |
+
preds = self.network(
|
| 304 |
+
x, z
|
| 305 |
+
) # Get the predictions from the network
|
| 306 |
+
except Exception as e:
|
| 307 |
+
print(f"Error during inference: {e}")
|
| 308 |
+
continue
|
| 309 |
+
print(
|
| 310 |
+
"y_pred",
|
| 311 |
+
preds.shape,
|
| 312 |
+
preds.std(),
|
| 313 |
+
preds.mean(),
|
| 314 |
+
preds.min(),
|
| 315 |
+
preds.max(),
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
is_nan = torch.isnan(preds).any()
|
| 319 |
+
if is_nan:
|
| 320 |
+
num_nan = torch.sum(torch.isnan(x)).item()
|
| 321 |
+
print(
|
| 322 |
+
f"Number of NaN values in sample_x: {num_nan} of {x.numel()}"
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
y = apply_RMS_normalization(
|
| 326 |
+
y, self.args.exp.RMS_norm, use_gate=self.args.exp.use_gated_RMSnorm
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
for b in range(B):
|
| 330 |
+
if self.use_wandb:
|
| 331 |
+
if (
|
| 332 |
+
i < self.args.tester.wandb.num_examples_to_log
|
| 333 |
+
): # Log only first 10 samples
|
| 334 |
+
self.log_audio(
|
| 335 |
+
preds[b], f"pred_wet_{k}_{mode}_{i}", it=self.it
|
| 336 |
+
) # Just log first sample
|
| 337 |
+
self.log_audio(
|
| 338 |
+
y[b], f"original_wet_{k}_{mode}_{i}", it=self.it
|
| 339 |
+
) # Just log first sample
|
| 340 |
+
self.log_audio(
|
| 341 |
+
x[b], f"original_dry_{k}_{mode}_{i}", it=self.it
|
| 342 |
+
) # Just log first sample
|
| 343 |
+
|
| 344 |
+
dict_y[i] = y[b].detach().cpu().numpy()
|
| 345 |
+
dict_x[i] = x[b].detach().cpu().numpy()
|
| 346 |
+
dict_y_hat[i] = preds[b].detach().cpu().numpy()
|
| 347 |
+
|
| 348 |
+
i += 1
|
| 349 |
+
|
| 350 |
+
if self.args.tester.compute_metrics:
|
| 351 |
+
for metric in self.metrics_dict.keys():
|
| 352 |
+
try:
|
| 353 |
+
print(f"Computing metric {metric}")
|
| 354 |
+
result, result_dict = self.metrics_dict[metric].compute(
|
| 355 |
+
dict_y, dict_y_hat, dict_x
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
if self.use_wandb:
|
| 359 |
+
if result is not None:
|
| 360 |
+
self.log_metric(
|
| 361 |
+
result, metric + "_" + k + "_" + mode, step=self.it
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
for key, value in result_dict.items():
|
| 365 |
+
if "figure" in key:
|
| 366 |
+
# log figure as an image
|
| 367 |
+
self.log_figure(
|
| 368 |
+
value, key + "_" + k + "_" + mode, step=self.it
|
| 369 |
+
)
|
| 370 |
+
else:
|
| 371 |
+
self.log_metric(
|
| 372 |
+
value, key + "_" + k + "_" + mode, step=self.it
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
except Exception as e:
|
| 376 |
+
print(f"Error computing metric {metric}: {e}")
|
| 377 |
+
continue
|
| 378 |
+
|
| 379 |
+
def prepare_directories(self, mode, unconditional=False, string=None):
|
| 380 |
+
|
| 381 |
+
today = date.today()
|
| 382 |
+
self.paths = {}
|
| 383 |
+
if (
|
| 384 |
+
"overriden_name" in self.args.tester.keys()
|
| 385 |
+
and self.args.tester.overriden_name is not None
|
| 386 |
+
):
|
| 387 |
+
self.path_sampling = os.path.join(
|
| 388 |
+
self.args.model_dir, self.args.tester.overriden_name
|
| 389 |
+
)
|
| 390 |
+
else:
|
| 391 |
+
self.path_sampling = os.path.join(
|
| 392 |
+
self.args.model_dir, "test" + today.strftime("%d_%m_%Y")
|
| 393 |
+
)
|
| 394 |
+
if not os.path.exists(self.path_sampling):
|
| 395 |
+
os.makedirs(self.path_sampling)
|
| 396 |
+
|
| 397 |
+
self.paths[mode] = os.path.join(
|
| 398 |
+
self.path_sampling, mode, self.args.exp.exp_name
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
if not os.path.exists(self.paths[mode]):
|
| 402 |
+
os.makedirs(self.paths[mode])
|
| 403 |
+
if string is None:
|
| 404 |
+
string = ""
|
| 405 |
+
|
| 406 |
+
self.paths[mode + "wet_original"] = os.path.join(
|
| 407 |
+
self.paths[mode], string + "wet_original"
|
| 408 |
+
)
|
| 409 |
+
if not os.path.exists(self.paths[mode + "wet_original"]):
|
| 410 |
+
os.makedirs(self.paths[mode + "wet_original"])
|
| 411 |
+
self.paths[mode + "dry"] = os.path.join(self.paths[mode], string + "dry")
|
| 412 |
+
if not os.path.exists(self.paths[mode + "dry"]):
|
| 413 |
+
os.makedirs(self.paths[mode + "dry"])
|
| 414 |
+
self.paths[mode + "wet_estimate"] = os.path.join(
|
| 415 |
+
self.paths[mode], string + "wet_estimate"
|
| 416 |
+
)
|
| 417 |
+
if not os.path.exists(self.paths[mode + "wet_estimate"]):
|
| 418 |
+
os.makedirs(self.paths[mode + "wet_estimate"])
|
| 419 |
+
|
| 420 |
+
def save_experiment_args(self, mode):
|
| 421 |
+
with open(
|
| 422 |
+
os.path.join(self.paths[mode], ".argv"), "w"
|
| 423 |
+
) as f: # Keep track of the arguments we used for this experiment
|
| 424 |
+
omegaconf.OmegaConf.save(config=self.args, f=f.name)
|
| 425 |
+
|
| 426 |
+
def do_test(self, it=0):
|
| 427 |
+
|
| 428 |
+
self.it = it
|
| 429 |
+
for m in self.args.tester.modes:
|
| 430 |
+
if m == "paired":
|
| 431 |
+
if not self.in_training:
|
| 432 |
+
self.prepare_directories(m, unconditional=True)
|
| 433 |
+
self.save_experiment_args(m)
|
| 434 |
+
self.test_paired(m)
|
| 435 |
+
else:
|
| 436 |
+
print("Warning: unknown mode: ", m)
|
networks/MLP_CLAP_regressor.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
class MLP_CLAP_regressor(nn.Module):
|
| 5 |
+
"""
|
| 6 |
+
A simple MLP regressor that uses CLAP features as input.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
def __init__(self, dim=512, hidden_dim=512):
|
| 10 |
+
super(MLP_CLAP_regressor, self).__init__()
|
| 11 |
+
|
| 12 |
+
self.model = nn.Sequential(
|
| 13 |
+
nn.Linear(dim, hidden_dim),
|
| 14 |
+
nn.ReLU(),
|
| 15 |
+
nn.Linear(hidden_dim, dim)
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
def forward(self, x):
|
| 19 |
+
|
| 20 |
+
emb= self.model(x)
|
| 21 |
+
#l2 normalization
|
| 22 |
+
return nn.functional.normalize(emb, p=2, dim=-1)
|
| 23 |
+
|
networks/__init__.py
ADDED
|
File without changes
|
networks/blackbox_TCN.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Adapted from: https://github.com/SonyResearch/ITO-Master
|
| 3 |
+
"""
|
| 4 |
+
import math
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import torch.nn.init as init
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
import omegaconf
|
| 14 |
+
import torchaudio
|
| 15 |
+
from utils.feature_extractors.load_features import load_CLAP
|
| 16 |
+
|
| 17 |
+
# 1-dimensional convolutional layer
|
| 18 |
+
# in the order of conv -> norm -> activation
|
| 19 |
+
class Conv1d_layer(nn.Module):
|
| 20 |
+
def __init__(self, in_channels, out_channels, kernel_size, \
|
| 21 |
+
stride=1, \
|
| 22 |
+
padding="SAME", dilation=1, bias=True, \
|
| 23 |
+
norm="batch", activation="relu", \
|
| 24 |
+
mode="conv"):
|
| 25 |
+
super(Conv1d_layer, self).__init__()
|
| 26 |
+
|
| 27 |
+
self.conv1d = nn.Sequential()
|
| 28 |
+
|
| 29 |
+
''' padding '''
|
| 30 |
+
if mode=="deconv":
|
| 31 |
+
padding = int(dilation * (kernel_size-1) / 2)
|
| 32 |
+
out_padding = 0 if stride==1 else 1
|
| 33 |
+
elif mode=="conv" or "alias_free" in mode:
|
| 34 |
+
if padding == "SAME":
|
| 35 |
+
pad = int((kernel_size-1) * dilation)
|
| 36 |
+
l_pad = int(pad//2)
|
| 37 |
+
r_pad = pad - l_pad
|
| 38 |
+
padding_area = (l_pad, r_pad)
|
| 39 |
+
elif padding == "VALID":
|
| 40 |
+
padding_area = (0, 0)
|
| 41 |
+
else:
|
| 42 |
+
pass
|
| 43 |
+
|
| 44 |
+
''' convolutional layer '''
|
| 45 |
+
if mode=="deconv":
|
| 46 |
+
self.conv1d.add_module("deconv1d", nn.ConvTranspose1d(in_channels, out_channels, kernel_size, \
|
| 47 |
+
stride=stride, padding=padding, output_padding=out_padding, \
|
| 48 |
+
dilation=dilation, \
|
| 49 |
+
bias=bias))
|
| 50 |
+
elif mode=="conv":
|
| 51 |
+
self.conv1d.add_module(f"{mode}1d_pad", nn.ReflectionPad1d(padding_area))
|
| 52 |
+
self.conv1d.add_module(f"{mode}1d", nn.Conv1d(in_channels, out_channels, kernel_size, \
|
| 53 |
+
stride=stride, padding=0, \
|
| 54 |
+
dilation=dilation, \
|
| 55 |
+
bias=bias))
|
| 56 |
+
elif "alias_free" in mode:
|
| 57 |
+
if "up" in mode:
|
| 58 |
+
up_factor = stride * 2
|
| 59 |
+
down_factor = 2
|
| 60 |
+
elif "down" in mode:
|
| 61 |
+
up_factor = 2
|
| 62 |
+
down_factor = stride * 2
|
| 63 |
+
else:
|
| 64 |
+
raise ValueError("choose alias-free method : 'up' or 'down'")
|
| 65 |
+
# procedure : conv -> upsample -> lrelu -> low-pass filter -> downsample
|
| 66 |
+
# the torchaudio.transforms.Resample's default resampling_method is 'sinc_interpolation' which performs low-pass filter during the process
|
| 67 |
+
# details at https://pytorch.org/audio/stable/transforms.html
|
| 68 |
+
self.conv1d.add_module(f"{mode}1d_pad", nn.ReflectionPad1d(padding_area))
|
| 69 |
+
self.conv1d.add_module(f"{mode}1d", nn.Conv1d(in_channels, out_channels, kernel_size, \
|
| 70 |
+
stride=1, padding=0, \
|
| 71 |
+
dilation=dilation, \
|
| 72 |
+
bias=bias))
|
| 73 |
+
self.conv1d.add_module(f"{mode}upsample", torchaudio.transforms.Resample(orig_freq=1, new_freq=up_factor))
|
| 74 |
+
self.conv1d.add_module(f"{mode}lrelu", nn.LeakyReLU())
|
| 75 |
+
self.conv1d.add_module(f"{mode}downsample", torchaudio.transforms.Resample(orig_freq=down_factor, new_freq=1))
|
| 76 |
+
|
| 77 |
+
''' normalization '''
|
| 78 |
+
if norm=="batch":
|
| 79 |
+
self.conv1d.add_module("batch_norm", nn.BatchNorm1d(out_channels))
|
| 80 |
+
# self.conv1d.add_module("batch_norm", nn.SyncBatchNorm(out_channels))
|
| 81 |
+
|
| 82 |
+
''' activation '''
|
| 83 |
+
if 'alias_free' not in mode:
|
| 84 |
+
if activation=="relu":
|
| 85 |
+
self.conv1d.add_module("relu", nn.ReLU())
|
| 86 |
+
elif activation=="lrelu":
|
| 87 |
+
self.conv1d.add_module("lrelu", nn.LeakyReLU())
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def forward(self, input):
|
| 91 |
+
# input shape should be : batch x channel x height x width
|
| 92 |
+
output = self.conv1d(input)
|
| 93 |
+
return output
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# compute receptive field
|
| 97 |
+
def compute_receptive_field(kernels, strides, dilations):
|
| 98 |
+
rf = 0
|
| 99 |
+
for i in range(len(kernels)):
|
| 100 |
+
rf += rf * strides[i] + (kernels[i]-strides[i]) * dilations[i]
|
| 101 |
+
return rf
|
| 102 |
+
|
| 103 |
+
# Feature-wise Linear Modulation
|
| 104 |
+
class FiLM(nn.Module):
|
| 105 |
+
def __init__(self, condition_len=2048, feature_len=1024):
|
| 106 |
+
super(FiLM, self).__init__()
|
| 107 |
+
self.film_fc = nn.Linear(condition_len, feature_len*2)
|
| 108 |
+
self.feat_len = feature_len
|
| 109 |
+
|
| 110 |
+
def forward(self, feature, condition):
|
| 111 |
+
film_factor = self.film_fc(condition).unsqueeze(-1)
|
| 112 |
+
r, b = torch.split(film_factor, self.feat_len, dim=1)
|
| 113 |
+
return r*feature + b
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class ConvBlock(nn.Module):
|
| 117 |
+
def __init__(self, dimension, layer_num, \
|
| 118 |
+
in_channels, out_channels, \
|
| 119 |
+
kernel_size, \
|
| 120 |
+
stride=1, padding="SAME", \
|
| 121 |
+
dilation=1, \
|
| 122 |
+
bias=True, \
|
| 123 |
+
norm="batch", \
|
| 124 |
+
activation="relu", last_activation="relu", \
|
| 125 |
+
mode="conv"):
|
| 126 |
+
super(ConvBlock, self).__init__()
|
| 127 |
+
|
| 128 |
+
conv_block = []
|
| 129 |
+
if dimension==1:
|
| 130 |
+
for i in range(layer_num-1):
|
| 131 |
+
conv_block.append(Conv1d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation))
|
| 132 |
+
conv_block.append(Conv1d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode))
|
| 133 |
+
elif dimension==2:
|
| 134 |
+
for i in range(layer_num-1):
|
| 135 |
+
conv_block.append(Conv2d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation))
|
| 136 |
+
conv_block.append(Conv2d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode))
|
| 137 |
+
self.conv_block = nn.Sequential(*conv_block)
|
| 138 |
+
|
| 139 |
+
def forward(self, input):
|
| 140 |
+
return self.conv_block(input)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class TCNBlock(torch.nn.Module):
|
| 144 |
+
def __init__(self,
|
| 145 |
+
in_ch,
|
| 146 |
+
out_ch,
|
| 147 |
+
kernel_size=3,
|
| 148 |
+
stride=1,
|
| 149 |
+
dilation=1,
|
| 150 |
+
cond_dim=2048,
|
| 151 |
+
grouped=False,
|
| 152 |
+
causal=False,
|
| 153 |
+
conditional=False,
|
| 154 |
+
**kwargs):
|
| 155 |
+
super(TCNBlock, self).__init__()
|
| 156 |
+
|
| 157 |
+
self.in_ch = in_ch
|
| 158 |
+
self.out_ch = out_ch
|
| 159 |
+
self.kernel_size = kernel_size
|
| 160 |
+
self.dilation = dilation
|
| 161 |
+
self.grouped = grouped
|
| 162 |
+
self.causal = causal
|
| 163 |
+
self.conditional = conditional
|
| 164 |
+
|
| 165 |
+
groups = out_ch if grouped and (in_ch % out_ch == 0) else 1
|
| 166 |
+
|
| 167 |
+
self.pad_length = ((kernel_size-1)*dilation) if self.causal else ((kernel_size-1)*dilation)//2
|
| 168 |
+
self.conv1 = torch.nn.Conv1d(in_ch,
|
| 169 |
+
out_ch,
|
| 170 |
+
kernel_size=kernel_size,
|
| 171 |
+
stride=stride,
|
| 172 |
+
padding=self.pad_length,
|
| 173 |
+
dilation=dilation,
|
| 174 |
+
groups=groups,
|
| 175 |
+
bias=False)
|
| 176 |
+
if grouped:
|
| 177 |
+
self.conv1b = torch.nn.Conv1d(out_ch, out_ch, kernel_size=1)
|
| 178 |
+
|
| 179 |
+
if conditional:
|
| 180 |
+
self.film = FiLM(cond_dim, out_ch)
|
| 181 |
+
self.bn = torch.nn.BatchNorm1d(out_ch)
|
| 182 |
+
|
| 183 |
+
self.relu = torch.nn.LeakyReLU()
|
| 184 |
+
|
| 185 |
+
if out_ch % in_ch == 0:
|
| 186 |
+
self.res = torch.nn.Conv1d(in_ch,
|
| 187 |
+
out_ch,
|
| 188 |
+
kernel_size=1,
|
| 189 |
+
stride=stride,
|
| 190 |
+
groups=in_ch,
|
| 191 |
+
bias=False)
|
| 192 |
+
else:
|
| 193 |
+
self.res = torch.nn.Conv1d(in_ch,
|
| 194 |
+
out_ch,
|
| 195 |
+
kernel_size=1,
|
| 196 |
+
stride=stride,
|
| 197 |
+
groups=1,
|
| 198 |
+
bias=False)
|
| 199 |
+
|
| 200 |
+
def forward(self, x, p):
|
| 201 |
+
x_in = x
|
| 202 |
+
|
| 203 |
+
x = self.relu(self.bn(self.conv1(x)))
|
| 204 |
+
#print("p", p.shape)
|
| 205 |
+
x = self.film(x, p)
|
| 206 |
+
|
| 207 |
+
x_res = self.res(x_in)
|
| 208 |
+
|
| 209 |
+
if self.causal:
|
| 210 |
+
x = x[..., :-self.pad_length]
|
| 211 |
+
x += x_res
|
| 212 |
+
|
| 213 |
+
return x
|
| 214 |
+
|
| 215 |
+
class FourierFeatures(nn.Module):
|
| 216 |
+
def __init__(self, in_features, out_features, std=1.):
|
| 217 |
+
super().__init__()
|
| 218 |
+
assert out_features % 2 == 0
|
| 219 |
+
self.weight = nn.Parameter(torch.randn(
|
| 220 |
+
[out_features // 2, in_features]) * std)
|
| 221 |
+
|
| 222 |
+
def forward(self, input):
|
| 223 |
+
f = 2 * math.pi * input @ self.weight.T
|
| 224 |
+
return torch.cat([f.cos(), f.sin()], dim=-1)
|
| 225 |
+
|
| 226 |
+
class TCNModel(nn.Module):
|
| 227 |
+
""" Temporal convolutional network with conditioning module.
|
| 228 |
+
Args:
|
| 229 |
+
nparams (int): Number of conditioning parameters.
|
| 230 |
+
ninputs (int): Number of input channels (mono = 1, stereo 2). Default: 1
|
| 231 |
+
noutputs (int): Number of output channels (mono = 1, stereo 2). Default: 1
|
| 232 |
+
nblocks (int): Number of total TCN blocks. Default: 10
|
| 233 |
+
kernel_size (int): Width of the convolutional kernels. Default: 3
|
| 234 |
+
dialation_growth (int): Compute the dilation factor at each block as dilation_growth ** (n % stack_size). Default: 1
|
| 235 |
+
channel_growth (int): Compute the output channels at each black as in_ch * channel_growth. Default: 2
|
| 236 |
+
channel_width (int): When channel_growth = 1 all blocks use convolutions with this many channels. Default: 64
|
| 237 |
+
stack_size (int): Number of blocks that constitute a single stack of blocks. Default: 10
|
| 238 |
+
grouped (bool): Use grouped convolutions to reduce the total number of parameters. Default: False
|
| 239 |
+
causal (bool): Causal TCN configuration does not consider future input values. Default: False
|
| 240 |
+
skip_connections (bool): Skip connections from each block to the output. Default: False
|
| 241 |
+
"""
|
| 242 |
+
def __init__(self,
|
| 243 |
+
ninputs=1,
|
| 244 |
+
noutputs=2,
|
| 245 |
+
nblocks=14,
|
| 246 |
+
kernel_size=15,
|
| 247 |
+
stride=1,
|
| 248 |
+
dilation_growth=2,
|
| 249 |
+
channel_growth=1,
|
| 250 |
+
channel_width=128,
|
| 251 |
+
stack_size=15,
|
| 252 |
+
cond_dim=2048,
|
| 253 |
+
grouped=False,
|
| 254 |
+
causal=False,
|
| 255 |
+
skip_connections=False,
|
| 256 |
+
use_CLAP=False,
|
| 257 |
+
CLAP_args=None,
|
| 258 |
+
):
|
| 259 |
+
super(TCNModel, self).__init__()
|
| 260 |
+
|
| 261 |
+
self.use_CLAP = use_CLAP
|
| 262 |
+
if self.use_CLAP:
|
| 263 |
+
assert CLAP_args is not None, "CLAP_args must be provided for CLAP AE"
|
| 264 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 265 |
+
CLAP_encoder= load_CLAP(CLAP_args, device=device)
|
| 266 |
+
cond_dim+= 512
|
| 267 |
+
def merge_CLAP_embeddings(x, emb):
|
| 268 |
+
|
| 269 |
+
clap_embedding = CLAP_encoder(x, type="dry")
|
| 270 |
+
#l2 normalize the clap embedding
|
| 271 |
+
clap_embedding = F.normalize(clap_embedding, p=2, dim=-1)
|
| 272 |
+
|
| 273 |
+
return torch.cat((emb, clap_embedding), dim=-1)
|
| 274 |
+
|
| 275 |
+
self.merge_CLAP_embeddings = merge_CLAP_embeddings
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
self.hparams = {
|
| 279 |
+
"ninputs": ninputs,
|
| 280 |
+
"noutputs": noutputs,
|
| 281 |
+
"nblocks": nblocks,
|
| 282 |
+
"kernel_size": kernel_size,
|
| 283 |
+
"stride": stride,
|
| 284 |
+
"dilation_growth": dilation_growth,
|
| 285 |
+
"channel_growth": channel_growth,
|
| 286 |
+
"channel_width": channel_width,
|
| 287 |
+
"stack_size": stack_size,
|
| 288 |
+
"cond_dim": cond_dim,
|
| 289 |
+
"grouped": grouped,
|
| 290 |
+
"causal": causal,
|
| 291 |
+
"skip_connections": skip_connections,
|
| 292 |
+
}
|
| 293 |
+
self.hparams= omegaconf.OmegaConf.create(self.hparams)
|
| 294 |
+
|
| 295 |
+
self.blocks = torch.nn.ModuleList()
|
| 296 |
+
for n in range(nblocks):
|
| 297 |
+
in_ch = out_ch if n > 0 else ninputs
|
| 298 |
+
|
| 299 |
+
if self.hparams.channel_growth > 1:
|
| 300 |
+
out_ch = in_ch * self.hparams.channel_growth
|
| 301 |
+
else:
|
| 302 |
+
out_ch = self.hparams.channel_width
|
| 303 |
+
|
| 304 |
+
dilation = self.hparams.dilation_growth ** (n % self.hparams.stack_size)
|
| 305 |
+
cur_stride = stride[n] if isinstance(stride, list) else stride
|
| 306 |
+
self.blocks.append(TCNBlock(in_ch,
|
| 307 |
+
out_ch,
|
| 308 |
+
kernel_size=self.hparams.kernel_size,
|
| 309 |
+
stride=cur_stride,
|
| 310 |
+
dilation=dilation,
|
| 311 |
+
padding="same" if self.hparams.causal else "valid",
|
| 312 |
+
causal=self.hparams.causal,
|
| 313 |
+
cond_dim=cond_dim,
|
| 314 |
+
grouped=self.hparams.grouped,
|
| 315 |
+
conditional=True ))
|
| 316 |
+
|
| 317 |
+
self.output = torch.nn.Conv1d(out_ch, noutputs, kernel_size=1)
|
| 318 |
+
|
| 319 |
+
def forward(self, x, cond):
|
| 320 |
+
# iterate over blocks passing conditioning
|
| 321 |
+
if self.use_CLAP:
|
| 322 |
+
with torch.no_grad():
|
| 323 |
+
cond = self.merge_CLAP_embeddings(x, cond)
|
| 324 |
+
|
| 325 |
+
for idx, block in enumerate(self.blocks):
|
| 326 |
+
# for SeFa
|
| 327 |
+
if isinstance(cond, list):
|
| 328 |
+
x = block(x, cond[idx])
|
| 329 |
+
else:
|
| 330 |
+
x = block(x, cond)
|
| 331 |
+
skips = 0
|
| 332 |
+
|
| 333 |
+
# out = torch.tanh(self.output(x + skips))
|
| 334 |
+
out = torch.clamp(self.output(x + skips), min=-1, max=1)
|
| 335 |
+
|
| 336 |
+
return out
|
| 337 |
+
|
| 338 |
+
def compute_receptive_field(self):
|
| 339 |
+
""" Compute the receptive field in samples."""
|
| 340 |
+
rf = self.hparams.kernel_size
|
| 341 |
+
for n in range(1,self.hparams.nblocks):
|
| 342 |
+
dilation = self.hparams.dilation_growth ** (n % self.hparams.stack_size)
|
| 343 |
+
rf = rf + ((self.hparams.kernel_size-1) * dilation)
|
| 344 |
+
return rf
|
| 345 |
+
|
| 346 |
+
|
networks/dit_multitrack.py
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# adapted from https://github.com/Stability-AI/stable-audio-tools/blob/main/stable_audio_tools/models/dit.py
|
| 3 |
+
|
| 4 |
+
import typing as tp
|
| 5 |
+
import math
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from einops import rearrange
|
| 9 |
+
from torch import nn
|
| 10 |
+
from torch.nn import functional as F
|
| 11 |
+
|
| 12 |
+
class FourierFeatures(nn.Module):
|
| 13 |
+
def __init__(self, in_features, out_features, std=1.):
|
| 14 |
+
super().__init__()
|
| 15 |
+
assert out_features % 2 == 0
|
| 16 |
+
self.weight = nn.Parameter(torch.randn(
|
| 17 |
+
[out_features // 2, in_features]) * std)
|
| 18 |
+
|
| 19 |
+
def forward(self, input):
|
| 20 |
+
f = 2 * math.pi * input @ self.weight.T
|
| 21 |
+
return torch.cat([f.cos(), f.sin()], dim=-1)
|
| 22 |
+
|
| 23 |
+
class OneHotPositionalEmbedding(nn.Module):
|
| 24 |
+
def __init__(self, max_seq_len):
|
| 25 |
+
super().__init__()
|
| 26 |
+
self.max_seq_len = max_seq_len
|
| 27 |
+
|
| 28 |
+
def forward(self, x, pos=None, seq_start_pos=None):
|
| 29 |
+
seq_len, device = x.shape[1], x.device
|
| 30 |
+
assert seq_len <= self.max_seq_len, f'you are passing in a sequence length of {seq_len} but your one-hot positional embedding has a max sequence length of {self.max_seq_len}'
|
| 31 |
+
|
| 32 |
+
if pos is None:
|
| 33 |
+
pos = torch.arange(seq_len, device=device)
|
| 34 |
+
|
| 35 |
+
if seq_start_pos is not None:
|
| 36 |
+
pos = (pos - seq_start_pos[..., None]).clamp(min=0)
|
| 37 |
+
|
| 38 |
+
pos_emb = F.one_hot(pos, num_classes=self.max_seq_len).to(x.dtype)
|
| 39 |
+
return pos_emb
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class DiffusionTransformer(nn.Module):
|
| 44 |
+
def __init__(self,
|
| 45 |
+
io_channels=32,
|
| 46 |
+
patch_size=1,
|
| 47 |
+
embed_dim=768,
|
| 48 |
+
cond_token_dim=0,
|
| 49 |
+
cond_token_proj_dim=64,
|
| 50 |
+
project_cond_tokens=False,
|
| 51 |
+
global_cond_dim=0,
|
| 52 |
+
project_global_cond=True,
|
| 53 |
+
input_concat_dim=0,
|
| 54 |
+
prepend_cond_dim=0,
|
| 55 |
+
depth=12,
|
| 56 |
+
num_heads=8,
|
| 57 |
+
transformer_type: tp.Literal["x-transformers", "continuous_transformer"] = "x-transformers",
|
| 58 |
+
global_cond_type: tp.Literal["prepend", "adaLN"] = "prepend",
|
| 59 |
+
timestep_cond_type: tp.Literal["global", "input_concat"] = "global",
|
| 60 |
+
timestep_embed_dim=None,
|
| 61 |
+
pos_emb_strategy="concatenation",
|
| 62 |
+
pos_emb_dim=None,
|
| 63 |
+
pos_emb_type="one-hot",
|
| 64 |
+
pos_emb_crossattn_strategy="concatenation",
|
| 65 |
+
pos_emb_crossattn_dim=None,
|
| 66 |
+
pos_emb_crossattn_type="one-hot",
|
| 67 |
+
use_taxonomy_in_pos_emb=True,
|
| 68 |
+
max_num_tracks=14,#used for one-hot positional embeddings
|
| 69 |
+
**kwargs):
|
| 70 |
+
|
| 71 |
+
super().__init__()
|
| 72 |
+
|
| 73 |
+
self.cond_token_dim = cond_token_dim
|
| 74 |
+
|
| 75 |
+
# Timestep embeddings
|
| 76 |
+
self.timestep_cond_type = timestep_cond_type
|
| 77 |
+
|
| 78 |
+
timestep_features_dim = 256
|
| 79 |
+
|
| 80 |
+
self.timestep_features = FourierFeatures(1, timestep_features_dim)
|
| 81 |
+
|
| 82 |
+
if timestep_cond_type == "global":
|
| 83 |
+
timestep_embed_dim = embed_dim
|
| 84 |
+
elif timestep_cond_type == "input_concat":
|
| 85 |
+
assert timestep_embed_dim is not None, "timestep_embed_dim must be specified if timestep_cond_type is input_concat"
|
| 86 |
+
input_concat_dim += timestep_embed_dim
|
| 87 |
+
|
| 88 |
+
self.to_timestep_embed = nn.Sequential(
|
| 89 |
+
nn.Linear(timestep_features_dim, timestep_embed_dim, bias=True),
|
| 90 |
+
nn.SiLU(),
|
| 91 |
+
nn.Linear(timestep_embed_dim, timestep_embed_dim, bias=True),
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
self.project_cond_tokens = project_cond_tokens
|
| 96 |
+
if cond_token_dim > 0:
|
| 97 |
+
# Conditioning tokens
|
| 98 |
+
|
| 99 |
+
if self.project_cond_tokens:
|
| 100 |
+
self.to_cond_embed = nn.Sequential(
|
| 101 |
+
nn.Linear(cond_token_dim, cond_token_proj_dim, bias=False),
|
| 102 |
+
nn.SiLU(),
|
| 103 |
+
nn.Linear(cond_token_proj_dim, cond_token_proj_dim, bias=False)
|
| 104 |
+
)
|
| 105 |
+
cond_embed_dim = cond_token_proj_dim
|
| 106 |
+
else:
|
| 107 |
+
cond_embed_dim = cond_token_dim
|
| 108 |
+
else:
|
| 109 |
+
cond_embed_dim = 0
|
| 110 |
+
|
| 111 |
+
if global_cond_dim > 0:
|
| 112 |
+
# Global conditioning
|
| 113 |
+
global_embed_dim = global_cond_dim if not project_global_cond else embed_dim
|
| 114 |
+
self.to_global_embed = nn.Sequential(
|
| 115 |
+
nn.Linear(global_cond_dim, global_embed_dim, bias=False),
|
| 116 |
+
nn.SiLU(),
|
| 117 |
+
nn.Linear(global_embed_dim, global_embed_dim, bias=False)
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
if prepend_cond_dim > 0:
|
| 121 |
+
# Prepend conditioning
|
| 122 |
+
self.to_prepend_embed = nn.Sequential(
|
| 123 |
+
nn.Linear(prepend_cond_dim, embed_dim, bias=False),
|
| 124 |
+
nn.SiLU(),
|
| 125 |
+
nn.Linear(embed_dim, embed_dim, bias=False)
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
self.input_concat_dim = input_concat_dim
|
| 129 |
+
|
| 130 |
+
dim_in = io_channels + self.input_concat_dim
|
| 131 |
+
|
| 132 |
+
self.patch_size = patch_size
|
| 133 |
+
|
| 134 |
+
# Transformer
|
| 135 |
+
|
| 136 |
+
self.transformer_type = transformer_type
|
| 137 |
+
|
| 138 |
+
self.global_cond_type = global_cond_type
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
if pos_emb_strategy == "concatenation":
|
| 142 |
+
|
| 143 |
+
assert pos_emb_dim is not None, "pos_emb_dim must be specified if pos_emb_strategy is concatenation"
|
| 144 |
+
if pos_emb_type == "one-hot":
|
| 145 |
+
# One-hot positional embeddings
|
| 146 |
+
self.pos_emb = OneHotPositionalEmbedding(pos_emb_dim-max_num_tracks)
|
| 147 |
+
self.extra_dim = pos_emb_dim
|
| 148 |
+
|
| 149 |
+
def concat_pos_emb(x, pos=None, seq_start_pos=None, taxonomy=None):
|
| 150 |
+
B, N, T, C = x.shape
|
| 151 |
+
pos_emb = self.pos_emb(x.view(-1,x.shape[-2], x.shape[-1]), pos=pos, seq_start_pos=seq_start_pos)
|
| 152 |
+
assert pos_emb.shape[-1] == pos_emb_dim-max_num_tracks, f"pos_emb shape mismatch: {pos_emb.shape[-1]} != {pos_emb_dim}"
|
| 153 |
+
assert pos_emb.shape[-2] == T, f"pos_emb sequence length mismatch: {pos_emb.shape[-2]} != {x.shape[-2]}"
|
| 154 |
+
|
| 155 |
+
pos_emb=pos_emb.unsqueeze(0).unsqueeze(0).expand(B,N,T,-1)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
assert pos_emb.ndim == 4, f"pos_emb must be 2D or 3D, got {pos_emb.ndim}"
|
| 159 |
+
assert pos_emb.shape[0] == x.shape[0], f"pos_emb batch size mismatch: {pos_emb.shape[0]} != {x.shape[0]}"
|
| 160 |
+
|
| 161 |
+
pos_emb_track=torch.zeros((B, N, T, max_num_tracks), device=x.device, dtype=x.dtype)
|
| 162 |
+
for i in range(B):
|
| 163 |
+
for j in range(N):
|
| 164 |
+
if use_taxonomy_in_pos_emb:
|
| 165 |
+
raise NotImplementedError("use_taxonomy_in_pos_emb is not implemented for pos_emb_type 'one-hot'")
|
| 166 |
+
assert taxonomy is not None, "taxonomy must be provided if use_taxonomy_in_pos_emb is True"
|
| 167 |
+
if taxonomy[i][j]=="92":
|
| 168 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([0], device=x.device), num_classes=3).to(x.dtype).expand(T, -1)
|
| 169 |
+
elif taxonomy[i][j]=="2":
|
| 170 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([1], device=x.device), num_classes=3).to(x.dtype).expand(T, -1)
|
| 171 |
+
elif taxonomy[i][j]=="11":
|
| 172 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([2], device=x.device), num_classes=3).to(x.dtype).expand(T, -1)
|
| 173 |
+
else:
|
| 174 |
+
if j >= max_num_tracks:
|
| 175 |
+
j= j% max_num_tracks
|
| 176 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([j], device=x.device), num_classes=max_num_tracks).to(x.dtype).expand(T, -1)
|
| 177 |
+
|
| 178 |
+
return torch.cat((x, pos_emb, pos_emb_track), dim=-1)
|
| 179 |
+
|
| 180 |
+
self.pos_emb_fn = concat_pos_emb
|
| 181 |
+
self.remove_pos_emb = lambda x: x[..., :-pos_emb_dim] if pos_emb_dim > 0 else x
|
| 182 |
+
|
| 183 |
+
else:
|
| 184 |
+
raise ValueError(f"Unknown pos_emb_type: {pos_emb_type}")
|
| 185 |
+
else:
|
| 186 |
+
raise ValueError(f"Unknown pos_emb_strategy: {pos_emb_strategy}")
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
if pos_emb_crossattn_strategy == "concatenation":
|
| 190 |
+
assert pos_emb_crossattn_dim is not None, "pos_emb_crossattn_dim must be specified if pos_emb_crossattn_strategy is concatenation"
|
| 191 |
+
if pos_emb_crossattn_type == "one-hot":
|
| 192 |
+
# One-hot positional embeddings for cross-attention
|
| 193 |
+
self.pos_emb_crossattn = OneHotPositionalEmbedding(pos_emb_crossattn_dim-max_num_tracks)
|
| 194 |
+
self.crossattn_extra_dim = pos_emb_crossattn_dim
|
| 195 |
+
|
| 196 |
+
if not self.project_cond_tokens:
|
| 197 |
+
def concat_pos_emb_crossattn(x, pos=None, seq_start_pos=None, taxonomy=None):
|
| 198 |
+
B, N, T, C = x.shape
|
| 199 |
+
|
| 200 |
+
pos_emb = self.pos_emb_crossattn(x.view(-1, T,C), pos=pos, seq_start_pos=seq_start_pos)
|
| 201 |
+
assert pos_emb.shape[-1] == pos_emb_crossattn_dim-max_num_tracks, f"pos_emb shape mismatch: {pos_emb.shape[-1]} != {pos_emb_crossattn_dim}"
|
| 202 |
+
assert pos_emb.shape[-2] == T, f"pos_emb sequence length mismatch: {pos_emb.shape[-2]} != {x.shape[-2]}"
|
| 203 |
+
|
| 204 |
+
pos_emb=pos_emb.unsqueeze(0).unsqueeze(0).expand(B,N,T,-1)
|
| 205 |
+
|
| 206 |
+
#if pos_emb.ndim == 3:
|
| 207 |
+
assert pos_emb.ndim == 4, f"pos_emb must be 2D or 3D, got {pos_emb.ndim}"
|
| 208 |
+
assert pos_emb.shape[0] == x.shape[0], f"pos_emb batch size mismatch: {pos_emb.shape[0]} != {x.shape[0]}"
|
| 209 |
+
|
| 210 |
+
pos_emb_track=torch.zeros((B, N, T, max_num_tracks), device=x.device, dtype=x.dtype)
|
| 211 |
+
for i in range(B):
|
| 212 |
+
for j in range(N):
|
| 213 |
+
if use_taxonomy_in_pos_emb:
|
| 214 |
+
raise NotImplementedError("use_taxonomy_in_pos_emb is not implemented for pos_emb_crossattn_type 'one-hot'")
|
| 215 |
+
assert taxonomy is not None, "taxonomy must be provided if use_taxonomy_in_pos_emb is True"
|
| 216 |
+
if taxonomy[i][j]=="92":
|
| 217 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([0], device=x.device), num_classes=3).to(x.dtype).expand(T, -1)
|
| 218 |
+
elif taxonomy[i][j]=="2":
|
| 219 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([1], device=x.device), num_classes=3).to(x.dtype).expand(T, -1)
|
| 220 |
+
elif taxonomy[i][j]=="11":
|
| 221 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([2], device=x.device), num_classes=3).to(x.dtype).expand(T, -1)
|
| 222 |
+
else:
|
| 223 |
+
if j >= max_num_tracks:
|
| 224 |
+
j= j% max_num_tracks
|
| 225 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([j], device=x.device), num_classes=max_num_tracks).to(x.dtype).expand(T, -1)
|
| 226 |
+
|
| 227 |
+
return torch.cat((x, pos_emb, pos_emb_track), dim=-1)
|
| 228 |
+
else:
|
| 229 |
+
def concat_pos_emb_crossattn(x, pos=None, seq_start_pos=None, taxonomy=None):
|
| 230 |
+
#print("x shape",x.shape)
|
| 231 |
+
|
| 232 |
+
B, N, T, C = x.shape
|
| 233 |
+
|
| 234 |
+
assert T*C== cond_token_dim, f"cond_token_proj_dim must match T*C, got {cond_token_dim} != {T*C}"
|
| 235 |
+
|
| 236 |
+
#rehape to B, N, 1, T*C
|
| 237 |
+
x= rearrange(x, "b n t c -> b n 1 (t c)")
|
| 238 |
+
|
| 239 |
+
#pos_emb = self.pos_emb_crossattn(x.view(-1, T,C), pos=pos, seq_start_pos=seq_start_pos)
|
| 240 |
+
#assert pos_emb.shape[-1] == pos_emb_crossattn_dim-3, f"pos_emb shape mismatch: {pos_emb.shape[-1]} != {pos_emb_crossattn_dim}"
|
| 241 |
+
#assert pos_emb.shape[-2] == T, f"pos_emb sequence length mismatch: {pos_emb.shape[-2]} != {x.shape[-2]}"
|
| 242 |
+
|
| 243 |
+
#pos_emb=pos_emb.unsqueeze(0).unsqueeze(0).expand(B,N,T,-1)
|
| 244 |
+
|
| 245 |
+
#if pos_emb.ndim == 3:
|
| 246 |
+
#assert pos_emb.ndim == 4, f"pos_emb must be 2D or 3D, got {pos_emb.ndim}"
|
| 247 |
+
#assert pos_emb.shape[0] == x.shape[0], f"pos_emb batch size mismatch: {pos_emb.shape[0]} != {x.shape[0]}"
|
| 248 |
+
|
| 249 |
+
x=rearrange(x, "b n 1 c -> (b n) 1 c")
|
| 250 |
+
x=self.to_cond_embed(x)
|
| 251 |
+
x=rearrange(x, "(b n) 1 c -> b n 1 c", b=B, n=N)
|
| 252 |
+
|
| 253 |
+
pos_emb_track=torch.zeros((B, N, 1, pos_emb_crossattn_dim), device=x.device, dtype=x.dtype)
|
| 254 |
+
for i in range(B):
|
| 255 |
+
for j in range(N):
|
| 256 |
+
if use_taxonomy_in_pos_emb:
|
| 257 |
+
assert taxonomy is not None, "taxonomy must be provided if use_taxonomy_in_pos_emb is True"
|
| 258 |
+
if taxonomy[i][j]=="92":
|
| 259 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([0], device=x.device), num_classes=pos_emb_crossattn_dim).to(x.dtype)
|
| 260 |
+
elif taxonomy[i][j]=="2":
|
| 261 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([1], device=x.device), num_classes=pos_emb_crossattn_dim).to(x.dtype)
|
| 262 |
+
elif taxonomy[i][j]=="11":
|
| 263 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([2], device=x.device), num_classes=pos_emb_crossattn_dim).to(x.dtype)
|
| 264 |
+
else:
|
| 265 |
+
pos_emb_track[i, j, :, :]= F.one_hot(torch.tensor([j], device=x.device), num_classes=pos_emb_crossattn_dim).to(x.dtype)
|
| 266 |
+
|
| 267 |
+
return torch.cat((x, pos_emb_track), dim=-1)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
self.pos_emb_crossattn_fn = concat_pos_emb_crossattn
|
| 272 |
+
|
| 273 |
+
else:
|
| 274 |
+
raise ValueError(f"Unknown pos_emb_type: {pos_emb_crossattn_type}")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
global_dim = None
|
| 279 |
+
|
| 280 |
+
if self.global_cond_type == "adaLN":
|
| 281 |
+
# The global conditioning is projected to the embed_dim already at this point
|
| 282 |
+
global_dim = embed_dim
|
| 283 |
+
|
| 284 |
+
from networks.transformer import ContinuousTransformer
|
| 285 |
+
|
| 286 |
+
self.transformer = ContinuousTransformer(
|
| 287 |
+
dim=embed_dim,
|
| 288 |
+
depth=depth,
|
| 289 |
+
num_heads= num_heads,
|
| 290 |
+
dim_in=dim_in + pos_emb_dim,
|
| 291 |
+
dim_out=io_channels ,
|
| 292 |
+
cross_attend = cond_token_dim > 0,
|
| 293 |
+
cond_token_dim = cond_embed_dim + pos_emb_crossattn_dim,
|
| 294 |
+
global_cond_dim=global_dim,
|
| 295 |
+
**kwargs
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
self.preprocess_conv = nn.Conv1d(dim_in, dim_in, 1, bias=False)
|
| 300 |
+
nn.init.zeros_(self.preprocess_conv.weight)
|
| 301 |
+
self.postprocess_conv = nn.Conv1d(io_channels, io_channels, 1, bias=False)
|
| 302 |
+
nn.init.zeros_(self.postprocess_conv.weight)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def _forward(
|
| 308 |
+
self,
|
| 309 |
+
x,
|
| 310 |
+
t,
|
| 311 |
+
mask=None,
|
| 312 |
+
cross_attn_cond=None,
|
| 313 |
+
cross_attn_cond_mask=None,
|
| 314 |
+
input_concat_cond=None,
|
| 315 |
+
global_embed=None,
|
| 316 |
+
prepend_cond=None,
|
| 317 |
+
prepend_cond_mask=None,
|
| 318 |
+
return_info=False,
|
| 319 |
+
**kwargs):
|
| 320 |
+
|
| 321 |
+
t=t.squeeze(1)
|
| 322 |
+
|
| 323 |
+
#if cross_attn_cond is not None:
|
| 324 |
+
# cross_attn_cond = self.to_cond_embed(cross_attn_cond)
|
| 325 |
+
|
| 326 |
+
if global_embed is not None:
|
| 327 |
+
# Project the global conditioning to the embedding dimension
|
| 328 |
+
global_embed = self.to_global_embed(global_embed)
|
| 329 |
+
|
| 330 |
+
prepend_inputs = None
|
| 331 |
+
prepend_mask = None
|
| 332 |
+
prepend_length = 0
|
| 333 |
+
if prepend_cond is not None:
|
| 334 |
+
# Project the prepend conditioning to the embedding dimension
|
| 335 |
+
prepend_cond = self.to_prepend_embed(prepend_cond)
|
| 336 |
+
|
| 337 |
+
prepend_inputs = prepend_cond
|
| 338 |
+
if prepend_cond_mask is not None:
|
| 339 |
+
prepend_mask = prepend_cond_mask
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# Get the batch of timestep embeddings
|
| 343 |
+
timestep_embed = self.to_timestep_embed(self.timestep_features(t[:, None])) # (b, embed_dim)
|
| 344 |
+
|
| 345 |
+
# Timestep embedding is considered a global embedding. Add to the global conditioning if it exists
|
| 346 |
+
|
| 347 |
+
if self.timestep_cond_type == "global":
|
| 348 |
+
if global_embed is not None:
|
| 349 |
+
global_embed = global_embed + timestep_embed
|
| 350 |
+
else:
|
| 351 |
+
global_embed = timestep_embed
|
| 352 |
+
elif self.timestep_cond_type == "input_concat":
|
| 353 |
+
x = torch.cat([x, timestep_embed.unsqueeze(1).expand(-1, -1, x.shape[2])], dim=1)
|
| 354 |
+
|
| 355 |
+
# Add the global_embed to the prepend inputs if there is no global conditioning support in the transformer
|
| 356 |
+
if self.global_cond_type == "prepend" and global_embed is not None:
|
| 357 |
+
if prepend_inputs is None:
|
| 358 |
+
# Prepend inputs are just the global embed, and the mask is all ones
|
| 359 |
+
prepend_inputs = global_embed.unsqueeze(1)
|
| 360 |
+
prepend_mask = torch.ones((x.shape[0], 1), device=x.device, dtype=torch.bool)
|
| 361 |
+
else:
|
| 362 |
+
# Prepend inputs are the prepend conditioning + the global embed
|
| 363 |
+
prepend_inputs = torch.cat([prepend_inputs, global_embed.unsqueeze(1)], dim=1)
|
| 364 |
+
prepend_mask = torch.cat([prepend_mask, torch.ones((x.shape[0], 1), device=x.device, dtype=torch.bool)], dim=1)
|
| 365 |
+
|
| 366 |
+
prepend_length = prepend_inputs.shape[1]
|
| 367 |
+
|
| 368 |
+
extra_args = {}
|
| 369 |
+
|
| 370 |
+
if self.global_cond_type == "adaLN":
|
| 371 |
+
extra_args["global_cond"] = global_embed
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
output = self.transformer(x, prepend_embeds=prepend_inputs, context=cross_attn_cond, context_mask=cross_attn_cond_mask, mask=mask, prepend_mask=prepend_mask, return_info=return_info, **extra_args, **kwargs)
|
| 375 |
+
|
| 376 |
+
if return_info:
|
| 377 |
+
output, info = output
|
| 378 |
+
|
| 379 |
+
output=output[:,prepend_length:,:]
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
if return_info:
|
| 383 |
+
return output, info
|
| 384 |
+
|
| 385 |
+
return output
|
| 386 |
+
|
| 387 |
+
def forward(
|
| 388 |
+
self,
|
| 389 |
+
x,
|
| 390 |
+
t,
|
| 391 |
+
cross_attn_cond=None,
|
| 392 |
+
cross_attn_cond_mask=None,
|
| 393 |
+
input_concat_cond=None,
|
| 394 |
+
global_embed=None,
|
| 395 |
+
taxonomy=None,
|
| 396 |
+
mask=None,
|
| 397 |
+
return_info=False,
|
| 398 |
+
**kwargs):
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
model_dtype = next(self.parameters()).dtype
|
| 402 |
+
|
| 403 |
+
x = x.to(model_dtype)
|
| 404 |
+
|
| 405 |
+
t = t.to(model_dtype)
|
| 406 |
+
|
| 407 |
+
if cross_attn_cond is not None:
|
| 408 |
+
cross_attn_cond = cross_attn_cond.to(model_dtype)
|
| 409 |
+
|
| 410 |
+
if input_concat_cond is not None:
|
| 411 |
+
input_concat_cond = input_concat_cond.to(model_dtype)
|
| 412 |
+
# Interpolate input_concat_cond to the same length as x
|
| 413 |
+
assert input_concat_cond.ndim == 4, f"input_concat_cond must be 4D, got {input_concat_cond.ndim}"
|
| 414 |
+
assert input_concat_cond.shape[0] == x.shape[0]
|
| 415 |
+
assert input_concat_cond.shape[1] == x.shape[1]
|
| 416 |
+
assert input_concat_cond.shape[-1] == x.shape[-1]
|
| 417 |
+
assert input_concat_cond.shape[-2] == self.input_concat_dim, f"input_concat_cond shape mismatch: {input_concat_cond.shape[-2]} != {self.input_concat_dim}"
|
| 418 |
+
|
| 419 |
+
x = torch.cat([x, input_concat_cond], dim=-2)
|
| 420 |
+
|
| 421 |
+
if global_embed is not None:
|
| 422 |
+
global_embed = global_embed.to(model_dtype)
|
| 423 |
+
|
| 424 |
+
if cross_attn_cond_mask is not None:
|
| 425 |
+
cross_attn_cond_mask = cross_attn_cond_mask.bool()
|
| 426 |
+
|
| 427 |
+
#cross_attn_cond_mask = None # Temporarily disabling conditioning masks due to kernel issue for flash attention
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
orig_shape = x.shape
|
| 431 |
+
x= rearrange(x, "b n c t -> (b n) c t")
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
x = self.preprocess_conv(x) + x
|
| 435 |
+
|
| 436 |
+
x=x.view(orig_shape)
|
| 437 |
+
|
| 438 |
+
x= rearrange(x, "b n c t -> b n t c")
|
| 439 |
+
#shape of contecxt is already [B, N, T, C] so no need to rearrange
|
| 440 |
+
|
| 441 |
+
orig_shape = x.shape
|
| 442 |
+
|
| 443 |
+
x= self.pos_emb_fn(x, taxonomy=taxonomy)
|
| 444 |
+
cross_attn_cond= self.pos_emb_crossattn_fn(cross_attn_cond, taxonomy=taxonomy)
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
x=rearrange(x, "b n t c -> b (n t) c")
|
| 448 |
+
|
| 449 |
+
cross_attn_cond_orig_shape = cross_attn_cond.shape
|
| 450 |
+
cross_attn_cond = rearrange(cross_attn_cond, "b n t c -> b (n t) c")
|
| 451 |
+
|
| 452 |
+
# rehape to [B, N \times T, C] for the transformer
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
# mask has shape [B, N ], I need to expand it to [B, N, T] for the convolution
|
| 456 |
+
mask= mask.unsqueeze(-1).expand(orig_shape[0], orig_shape[1], orig_shape[2])
|
| 457 |
+
mask= rearrange(mask, "b n t -> b (n t)")
|
| 458 |
+
|
| 459 |
+
cross_attn_cond_mask = cross_attn_cond_mask.unsqueeze(-1).expand(cross_attn_cond_orig_shape[0], cross_attn_cond_orig_shape[1], cross_attn_cond_orig_shape[2])
|
| 460 |
+
cross_attn_cond_mask = rearrange(cross_attn_cond_mask, "b n t -> b (n t)")
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
out= self._forward(
|
| 464 |
+
x,
|
| 465 |
+
t,
|
| 466 |
+
cross_attn_cond=cross_attn_cond,
|
| 467 |
+
cross_attn_cond_mask=cross_attn_cond_mask,
|
| 468 |
+
input_concat_cond=input_concat_cond,
|
| 469 |
+
global_embed=global_embed,
|
| 470 |
+
mask=mask,
|
| 471 |
+
return_info=return_info,
|
| 472 |
+
**kwargs
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
#print("out shape", out.shape)
|
| 476 |
+
|
| 477 |
+
out = rearrange(out, "b t c -> b c t")
|
| 478 |
+
out= self.postprocess_conv(out) + out
|
| 479 |
+
out = rearrange(out, "b c t -> b t c")
|
| 480 |
+
|
| 481 |
+
#print("out shape after postprocess", out.shape)
|
| 482 |
+
|
| 483 |
+
#now we reshape...
|
| 484 |
+
out = rearrange(out, "b (n t) c -> b n t c", n=orig_shape[1], t=orig_shape[2])
|
| 485 |
+
|
| 486 |
+
out=rearrange(out, "b n t c -> b n c t")
|
| 487 |
+
|
| 488 |
+
return out
|
networks/transformer.py
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#adapted from https://github.com/Stability-AI/stable-audio-tools/blob/main/stable_audio_tools/models/transformer.py
|
| 2 |
+
|
| 3 |
+
from functools import reduce, partial
|
| 4 |
+
from packaging import version
|
| 5 |
+
|
| 6 |
+
from einops import rearrange, repeat
|
| 7 |
+
from einops.layers.torch import Rearrange
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from torch import nn, einsum
|
| 11 |
+
from torch.amp import autocast
|
| 12 |
+
from torch.nn.utils.parametrizations import weight_norm
|
| 13 |
+
from typing import Callable, Literal
|
| 14 |
+
|
| 15 |
+
#from .utils import compile
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
enable_torch_compile = os.environ.get("ENABLE_TORCH_COMPILE", "0") == "1"
|
| 19 |
+
|
| 20 |
+
def compile(function, *args, **kwargs):
|
| 21 |
+
|
| 22 |
+
if enable_torch_compile:
|
| 23 |
+
try:
|
| 24 |
+
return torch.compile(function, *args, **kwargs)
|
| 25 |
+
except RuntimeError:
|
| 26 |
+
return function
|
| 27 |
+
|
| 28 |
+
return function
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
from flash_attn import flash_attn_func, flash_attn_kvpacked_func
|
| 32 |
+
except ImportError as e:
|
| 33 |
+
print(e)
|
| 34 |
+
print('flash_attn not installed, disabling Flash Attention')
|
| 35 |
+
flash_attn_kvpacked_func = None
|
| 36 |
+
flash_attn_func = None
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
import natten
|
| 40 |
+
except ImportError:
|
| 41 |
+
natten = None
|
| 42 |
+
|
| 43 |
+
def checkpoint(function, *args, **kwargs):
|
| 44 |
+
kwargs.setdefault("use_reentrant", False)
|
| 45 |
+
return torch.utils.checkpoint.checkpoint(function, *args, **kwargs)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# Copied and modified from https://github.com/lucidrains/x-transformers/blob/main/x_transformers/attend.py under MIT License
|
| 49 |
+
# License can be found in LICENSES/LICENSE_XTRANSFORMERS.txt
|
| 50 |
+
|
| 51 |
+
def create_causal_mask(i, j, device):
|
| 52 |
+
return torch.ones((i, j), device = device, dtype = torch.bool).triu(j - i + 1)
|
| 53 |
+
|
| 54 |
+
def or_reduce(masks):
|
| 55 |
+
head, *body = masks
|
| 56 |
+
for rest in body:
|
| 57 |
+
head = head | rest
|
| 58 |
+
return head
|
| 59 |
+
|
| 60 |
+
class FourierFeatures(nn.Module):
|
| 61 |
+
def __init__(self, in_features, out_features, std=1.):
|
| 62 |
+
super().__init__()
|
| 63 |
+
assert out_features % 2 == 0
|
| 64 |
+
self.weight = nn.Parameter(torch.randn(
|
| 65 |
+
[out_features // 2, in_features]) * std)
|
| 66 |
+
|
| 67 |
+
def forward(self, input):
|
| 68 |
+
f = 2 * math.pi * input @ self.weight.T
|
| 69 |
+
return torch.cat([f.cos(), f.sin()], dim=-1)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class AbsolutePositionalEmbedding(nn.Module):
|
| 73 |
+
def __init__(self, dim, max_seq_len, fixed = False, seed = 42):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.scale = dim ** -0.5
|
| 76 |
+
self.max_seq_len = max_seq_len
|
| 77 |
+
self.fixed=fixed
|
| 78 |
+
if fixed:
|
| 79 |
+
torch.manual_seed(seed)
|
| 80 |
+
emb = torch.randn(max_seq_len, dim)
|
| 81 |
+
self.register_buffer('emb', emb) # replaces nn.Embedding
|
| 82 |
+
else:
|
| 83 |
+
self.emb = nn.Embedding(max_seq_len, dim)
|
| 84 |
+
|
| 85 |
+
def forward(self, x, pos = None, seq_start_pos = None):
|
| 86 |
+
seq_len, device = x.shape[1], x.device
|
| 87 |
+
assert seq_len <= self.max_seq_len, f'you are passing in a sequence length of {seq_len} but your absolute positional embedding has a max sequence length of {self.max_seq_len}'
|
| 88 |
+
|
| 89 |
+
if pos is None:
|
| 90 |
+
pos = torch.arange(seq_len, device = device)
|
| 91 |
+
|
| 92 |
+
if seq_start_pos is not None:
|
| 93 |
+
pos = (pos - seq_start_pos[..., None]).clamp(min = 0)
|
| 94 |
+
|
| 95 |
+
if not self.fixed:
|
| 96 |
+
pos_emb = self.emb(pos)
|
| 97 |
+
else:
|
| 98 |
+
pos_emb = self.emb[pos]
|
| 99 |
+
pos_emb = pos_emb * self.scale
|
| 100 |
+
return pos_emb
|
| 101 |
+
|
| 102 |
+
class ScaledSinusoidalEmbedding(nn.Module):
|
| 103 |
+
def __init__(self, dim, theta = 10000):
|
| 104 |
+
super().__init__()
|
| 105 |
+
assert (dim % 2) == 0, 'dimension must be divisible by 2'
|
| 106 |
+
self.scale = nn.Parameter(torch.ones(1) * dim ** -0.5)
|
| 107 |
+
|
| 108 |
+
half_dim = dim // 2
|
| 109 |
+
freq_seq = torch.arange(half_dim).float() / half_dim
|
| 110 |
+
inv_freq = theta ** -freq_seq
|
| 111 |
+
self.register_buffer('inv_freq', inv_freq, persistent = False)
|
| 112 |
+
|
| 113 |
+
def forward(self, x, pos = None, seq_start_pos = None):
|
| 114 |
+
seq_len, device = x.shape[1], x.device
|
| 115 |
+
|
| 116 |
+
if pos is None:
|
| 117 |
+
pos = torch.arange(seq_len, device = device)
|
| 118 |
+
|
| 119 |
+
if seq_start_pos is not None:
|
| 120 |
+
pos = pos - seq_start_pos[..., None]
|
| 121 |
+
|
| 122 |
+
emb = einsum('i, j -> i j', pos, self.inv_freq)
|
| 123 |
+
emb = torch.cat((emb.sin(), emb.cos()), dim = -1)
|
| 124 |
+
return emb * self.scale
|
| 125 |
+
|
| 126 |
+
class RotaryEmbedding(nn.Module):
|
| 127 |
+
def __init__(
|
| 128 |
+
self,
|
| 129 |
+
dim,
|
| 130 |
+
use_xpos = False,
|
| 131 |
+
scale_base = 512,
|
| 132 |
+
interpolation_factor = 1.,
|
| 133 |
+
base = 10000,
|
| 134 |
+
base_rescale_factor = 1.
|
| 135 |
+
):
|
| 136 |
+
super().__init__()
|
| 137 |
+
# proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning
|
| 138 |
+
# has some connection to NTK literature
|
| 139 |
+
# https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/
|
| 140 |
+
base *= base_rescale_factor ** (dim / (dim - 2))
|
| 141 |
+
|
| 142 |
+
inv_freq = 1. / (base ** (torch.arange(0, dim, 2).float() / dim))
|
| 143 |
+
self.register_buffer('inv_freq', inv_freq)
|
| 144 |
+
|
| 145 |
+
assert interpolation_factor >= 1.
|
| 146 |
+
self.interpolation_factor = interpolation_factor
|
| 147 |
+
|
| 148 |
+
if not use_xpos:
|
| 149 |
+
self.register_buffer('scale', None)
|
| 150 |
+
return
|
| 151 |
+
|
| 152 |
+
scale = (torch.arange(0, dim, 2) + 0.4 * dim) / (1.4 * dim)
|
| 153 |
+
|
| 154 |
+
self.scale_base = scale_base
|
| 155 |
+
self.register_buffer('scale', scale)
|
| 156 |
+
|
| 157 |
+
def forward_from_seq_len(self, seq_len):
|
| 158 |
+
device = self.inv_freq.device
|
| 159 |
+
|
| 160 |
+
t = torch.arange(seq_len, device = device)
|
| 161 |
+
return self.forward(t)
|
| 162 |
+
|
| 163 |
+
@autocast("cuda", enabled = False)
|
| 164 |
+
def forward(self, t):
|
| 165 |
+
device = self.inv_freq.device
|
| 166 |
+
|
| 167 |
+
t = t.to(torch.float32)
|
| 168 |
+
|
| 169 |
+
t = t / self.interpolation_factor
|
| 170 |
+
|
| 171 |
+
freqs = torch.einsum('i , j -> i j', t, self.inv_freq)
|
| 172 |
+
freqs = torch.cat((freqs, freqs), dim = -1)
|
| 173 |
+
|
| 174 |
+
if self.scale is None:
|
| 175 |
+
return freqs, 1.
|
| 176 |
+
|
| 177 |
+
power = (torch.arange(seq_len, device = device) - (seq_len // 2)) / self.scale_base
|
| 178 |
+
scale = self.scale ** rearrange(power, 'n -> n 1')
|
| 179 |
+
scale = torch.cat((scale, scale), dim = -1)
|
| 180 |
+
|
| 181 |
+
return freqs, scale
|
| 182 |
+
|
| 183 |
+
def rotate_half(x):
|
| 184 |
+
x = rearrange(x, '... (j d) -> ... j d', j = 2)
|
| 185 |
+
x1, x2 = x.unbind(dim = -2)
|
| 186 |
+
return torch.cat((-x2, x1), dim = -1)
|
| 187 |
+
|
| 188 |
+
@autocast("cuda", enabled = False)
|
| 189 |
+
def apply_rotary_pos_emb(t, freqs, scale = 1):
|
| 190 |
+
out_dtype = t.dtype
|
| 191 |
+
|
| 192 |
+
# cast to float32 if necessary for numerical stability
|
| 193 |
+
dtype = reduce(torch.promote_types, (t.dtype, freqs.dtype, torch.float32))
|
| 194 |
+
rot_dim, seq_len = freqs.shape[-1], t.shape[-2]
|
| 195 |
+
freqs, t = freqs.to(dtype), t.to(dtype)
|
| 196 |
+
freqs = freqs[-seq_len:, :]
|
| 197 |
+
|
| 198 |
+
if t.ndim == 4 and freqs.ndim == 3:
|
| 199 |
+
freqs = rearrange(freqs, 'b n d -> b 1 n d')
|
| 200 |
+
|
| 201 |
+
# partial rotary embeddings, Wang et al. GPT-J
|
| 202 |
+
t, t_unrotated = t[..., :rot_dim], t[..., rot_dim:]
|
| 203 |
+
t = (t * freqs.cos() * scale) + (rotate_half(t) * freqs.sin() * scale)
|
| 204 |
+
|
| 205 |
+
t, t_unrotated = t.to(out_dtype), t_unrotated.to(out_dtype)
|
| 206 |
+
|
| 207 |
+
return torch.cat((t, t_unrotated), dim = -1)
|
| 208 |
+
|
| 209 |
+
# norms
|
| 210 |
+
class LayerNorm(nn.Module):
|
| 211 |
+
def __init__(self, dim, bias=False, fix_scale=False, force_fp32=False, eps=1e-5):
|
| 212 |
+
"""
|
| 213 |
+
bias-less layernorm has been shown to be more stable. most newer models have moved towards rmsnorm, also bias-less
|
| 214 |
+
"""
|
| 215 |
+
super().__init__()
|
| 216 |
+
|
| 217 |
+
if fix_scale:
|
| 218 |
+
self.register_buffer("gamma", torch.ones(dim))
|
| 219 |
+
else:
|
| 220 |
+
self.gamma = nn.Parameter(torch.ones(dim))
|
| 221 |
+
|
| 222 |
+
if bias:
|
| 223 |
+
self.beta = nn.Parameter(torch.zeros(dim))
|
| 224 |
+
else:
|
| 225 |
+
self.register_buffer("beta", torch.zeros(dim))
|
| 226 |
+
|
| 227 |
+
self.eps = eps
|
| 228 |
+
|
| 229 |
+
self.force_fp32 = force_fp32
|
| 230 |
+
|
| 231 |
+
def forward(self, x):
|
| 232 |
+
if not self.force_fp32:
|
| 233 |
+
return F.layer_norm(x, x.shape[-1:], weight=self.gamma, bias=self.beta, eps=self.eps)
|
| 234 |
+
else:
|
| 235 |
+
output = F.layer_norm(x.float(), x.shape[-1:], weight=self.gamma.float(), bias=self.beta.float(), eps=self.eps)
|
| 236 |
+
return output.to(x.dtype)
|
| 237 |
+
|
| 238 |
+
class LayerScale(nn.Module):
|
| 239 |
+
def __init__(self, dim, init_val = 1e-2):
|
| 240 |
+
super().__init__()
|
| 241 |
+
self.scale = nn.Parameter(torch.full([dim], init_val))
|
| 242 |
+
def forward(self, x):
|
| 243 |
+
return x * self.scale
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
# feedforward
|
| 247 |
+
|
| 248 |
+
class GLU(nn.Module):
|
| 249 |
+
def __init__(
|
| 250 |
+
self,
|
| 251 |
+
dim_in,
|
| 252 |
+
dim_out,
|
| 253 |
+
activation: Callable,
|
| 254 |
+
use_conv = False,
|
| 255 |
+
conv_kernel_size = 3,
|
| 256 |
+
):
|
| 257 |
+
super().__init__()
|
| 258 |
+
self.act = activation
|
| 259 |
+
self.proj = nn.Linear(dim_in, dim_out * 2) if not use_conv else nn.Conv1d(dim_in, dim_out * 2, conv_kernel_size, padding = (conv_kernel_size // 2))
|
| 260 |
+
self.use_conv = use_conv
|
| 261 |
+
|
| 262 |
+
def forward(self, x):
|
| 263 |
+
if self.use_conv:
|
| 264 |
+
x = rearrange(x, 'b n d -> b d n')
|
| 265 |
+
x = self.proj(x)
|
| 266 |
+
x = rearrange(x, 'b d n -> b n d')
|
| 267 |
+
else:
|
| 268 |
+
x = self.proj(x)
|
| 269 |
+
|
| 270 |
+
x, gate = x.chunk(2, dim = -1)
|
| 271 |
+
return x * self.act(gate)
|
| 272 |
+
|
| 273 |
+
class FeedForward(nn.Module):
|
| 274 |
+
def __init__(
|
| 275 |
+
self,
|
| 276 |
+
dim,
|
| 277 |
+
dim_out = None,
|
| 278 |
+
mult = 4,
|
| 279 |
+
no_bias = False,
|
| 280 |
+
glu = True,
|
| 281 |
+
use_conv = False,
|
| 282 |
+
conv_kernel_size = 3,
|
| 283 |
+
zero_init_output = True,
|
| 284 |
+
):
|
| 285 |
+
super().__init__()
|
| 286 |
+
inner_dim = int(dim * mult)
|
| 287 |
+
|
| 288 |
+
# Default to SwiGLU
|
| 289 |
+
|
| 290 |
+
activation = nn.SiLU()
|
| 291 |
+
|
| 292 |
+
dim_out = dim if dim_out is None else dim_out
|
| 293 |
+
|
| 294 |
+
if glu:
|
| 295 |
+
linear_in = GLU(dim, inner_dim, activation)
|
| 296 |
+
else:
|
| 297 |
+
linear_in = nn.Sequential(
|
| 298 |
+
Rearrange('b n d -> b d n') if use_conv else nn.Identity(),
|
| 299 |
+
nn.Linear(dim, inner_dim, bias = not no_bias) if not use_conv else nn.Conv1d(dim, inner_dim, conv_kernel_size, padding = (conv_kernel_size // 2), bias = not no_bias),
|
| 300 |
+
Rearrange('b n d -> b d n') if use_conv else nn.Identity(),
|
| 301 |
+
activation
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
linear_out = nn.Linear(inner_dim, dim_out, bias = not no_bias) if not use_conv else nn.Conv1d(inner_dim, dim_out, conv_kernel_size, padding = (conv_kernel_size // 2), bias = not no_bias)
|
| 305 |
+
|
| 306 |
+
# init last linear layer to 0
|
| 307 |
+
if zero_init_output:
|
| 308 |
+
nn.init.zeros_(linear_out.weight)
|
| 309 |
+
if not no_bias:
|
| 310 |
+
nn.init.zeros_(linear_out.bias)
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
self.ff = nn.Sequential(
|
| 314 |
+
linear_in,
|
| 315 |
+
Rearrange('b d n -> b n d') if use_conv else nn.Identity(),
|
| 316 |
+
linear_out,
|
| 317 |
+
Rearrange('b n d -> b d n') if use_conv else nn.Identity(),
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
#@compile
|
| 321 |
+
def forward(self, x):
|
| 322 |
+
return self.ff(x)
|
| 323 |
+
|
| 324 |
+
class Attention(nn.Module):
|
| 325 |
+
def __init__(
|
| 326 |
+
self,
|
| 327 |
+
dim,
|
| 328 |
+
dim_heads = 64,
|
| 329 |
+
dim_context = None,
|
| 330 |
+
causal = False,
|
| 331 |
+
zero_init_output=True,
|
| 332 |
+
qk_norm: Literal['l2', 'ln', 'none'] = 'none',
|
| 333 |
+
natten_kernel_size = None,
|
| 334 |
+
sliding_window = [-1, -1],
|
| 335 |
+
feat_scale = False
|
| 336 |
+
):
|
| 337 |
+
super().__init__()
|
| 338 |
+
self.dim = dim
|
| 339 |
+
self.dim_heads = dim_heads
|
| 340 |
+
self.causal = causal
|
| 341 |
+
|
| 342 |
+
dim_kv = dim_context if dim_context is not None else dim
|
| 343 |
+
|
| 344 |
+
self.num_heads = dim // dim_heads
|
| 345 |
+
self.kv_heads = dim_kv // dim_heads
|
| 346 |
+
#print("num_heads", self.num_heads, "kv_heads", self.kv_heads)
|
| 347 |
+
#print("dim", dim, "dim_heads", dim_heads, "dim_kv", dim_kv, dim/dim_heads, dim_kv/dim_heads)
|
| 348 |
+
assert dim % dim_heads == 0, f'dim {dim} must be divisible by dim_heads {dim_heads}'
|
| 349 |
+
assert dim_kv % dim_heads == 0, f'dim_kv {dim_kv} must be divisible by dim_heads {dim_heads}'
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
if dim_context is not None:
|
| 353 |
+
self.to_q = nn.Linear(dim, dim, bias=False)
|
| 354 |
+
self.to_kv = nn.Linear(dim_kv, dim_kv * 2, bias=False)
|
| 355 |
+
else:
|
| 356 |
+
self.to_qkv = nn.Linear(dim, dim * 3, bias=False)
|
| 357 |
+
|
| 358 |
+
self.to_out = nn.Linear(dim, dim, bias=False)
|
| 359 |
+
|
| 360 |
+
if zero_init_output:
|
| 361 |
+
nn.init.zeros_(self.to_out.weight)
|
| 362 |
+
|
| 363 |
+
if qk_norm not in ['l2', 'ln', 'none']:
|
| 364 |
+
raise ValueError(f'qk_norm must be one of ["l2", "ln", "none"], got {qk_norm}')
|
| 365 |
+
|
| 366 |
+
self.qk_norm = qk_norm
|
| 367 |
+
|
| 368 |
+
if self.qk_norm == "ln":
|
| 369 |
+
self.q_norm = nn.LayerNorm(dim_heads, elementwise_affine=True, eps=1.0e-6)
|
| 370 |
+
self.k_norm = nn.LayerNorm(dim_heads, elementwise_affine=True, eps=1.0e-6)
|
| 371 |
+
|
| 372 |
+
# Using 1d neighborhood attention
|
| 373 |
+
self.natten_kernel_size = natten_kernel_size
|
| 374 |
+
if natten_kernel_size is not None:
|
| 375 |
+
return
|
| 376 |
+
|
| 377 |
+
self.use_pt_flash = torch.cuda.is_available() and version.parse(torch.__version__) >= version.parse('2.0.0')
|
| 378 |
+
|
| 379 |
+
self.use_fa_flash = torch.cuda.is_available() and flash_attn_func is not None
|
| 380 |
+
|
| 381 |
+
self.sdp_kwargs = dict(
|
| 382 |
+
enable_flash = True,
|
| 383 |
+
enable_math = True,
|
| 384 |
+
enable_mem_efficient = True
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
self.sliding_window = sliding_window
|
| 388 |
+
if not (sliding_window[0] == -1 and sliding_window[1] == -1) and not self.use_fa_flash:
|
| 389 |
+
print('Sliding window is being used, but Flash Attention is not. Please install Flash Attention to get correct results')
|
| 390 |
+
|
| 391 |
+
self.feat_scale = feat_scale
|
| 392 |
+
|
| 393 |
+
if self.feat_scale:
|
| 394 |
+
self.lambda_dc = nn.Parameter(torch.zeros(dim))
|
| 395 |
+
self.lambda_hf = nn.Parameter(torch.zeros(dim))
|
| 396 |
+
|
| 397 |
+
def flash_attn(
|
| 398 |
+
self,
|
| 399 |
+
q,
|
| 400 |
+
k,
|
| 401 |
+
v,
|
| 402 |
+
mask = None,
|
| 403 |
+
causal = None
|
| 404 |
+
):
|
| 405 |
+
batch, heads, q_len, _, k_len, device = *q.shape, k.shape[-2], q.device
|
| 406 |
+
kv_heads = k.shape[1]
|
| 407 |
+
# Recommended for multi-query single-key-value attention by Tri Dao
|
| 408 |
+
# kv shape torch.Size([1, 512, 64]) -> torch.Size([1, 8, 512, 64])
|
| 409 |
+
|
| 410 |
+
if heads != kv_heads:
|
| 411 |
+
# Repeat interleave kv_heads to match q_heads
|
| 412 |
+
heads_per_kv_head = heads // kv_heads
|
| 413 |
+
assert heads % kv_heads == 0, f'heads {heads} must be divisible by kv_heads {kv_heads}'
|
| 414 |
+
k, v = map(lambda t: t.repeat_interleave(heads_per_kv_head, dim = 1), (k, v))
|
| 415 |
+
|
| 416 |
+
if k.ndim == 3:
|
| 417 |
+
k = rearrange(k, 'b ... -> b 1 ...').expand_as(q)
|
| 418 |
+
|
| 419 |
+
if v.ndim == 3:
|
| 420 |
+
v = rearrange(v, 'b ... -> b 1 ...').expand_as(q)
|
| 421 |
+
|
| 422 |
+
causal = self.causal if causal is None else causal
|
| 423 |
+
|
| 424 |
+
if q_len == 1 and causal:
|
| 425 |
+
causal = False
|
| 426 |
+
|
| 427 |
+
if mask is not None:
|
| 428 |
+
assert mask.ndim == 4
|
| 429 |
+
mask = mask.expand(batch, heads, q_len, k_len)
|
| 430 |
+
|
| 431 |
+
# handle kv cache - this should be bypassable in updated flash attention 2
|
| 432 |
+
|
| 433 |
+
if k_len > q_len and causal:
|
| 434 |
+
causal_mask = self.create_causal_mask(q_len, k_len, device = device)
|
| 435 |
+
if mask is None:
|
| 436 |
+
mask = ~causal_mask
|
| 437 |
+
else:
|
| 438 |
+
mask = mask & ~causal_mask
|
| 439 |
+
causal = False
|
| 440 |
+
|
| 441 |
+
# manually handle causal mask, if another mask was given
|
| 442 |
+
|
| 443 |
+
row_is_entirely_masked = None
|
| 444 |
+
|
| 445 |
+
if mask is not None and causal:
|
| 446 |
+
causal_mask = self.create_causal_mask(q_len, k_len, device = device)
|
| 447 |
+
mask = mask & ~causal_mask
|
| 448 |
+
|
| 449 |
+
# protect against an entire row being masked out
|
| 450 |
+
|
| 451 |
+
row_is_entirely_masked = ~mask.any(dim = -1)
|
| 452 |
+
mask[..., 0] = mask[..., 0] | row_is_entirely_masked
|
| 453 |
+
|
| 454 |
+
causal = False
|
| 455 |
+
|
| 456 |
+
#with torch.backends.cuda.sdp_kernel(**self.sdp_kwargs):
|
| 457 |
+
#print("q.shape", q.shape, "k.shape", k.shape, "v.shape", v.shape, "mask.shape", mask.shape if mask is not None else None, "causal", causal)
|
| 458 |
+
out = F.scaled_dot_product_attention(
|
| 459 |
+
q, k, v,
|
| 460 |
+
attn_mask = mask,
|
| 461 |
+
is_causal = causal
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
# for a row that is entirely masked out, should zero out the output of that row token
|
| 465 |
+
|
| 466 |
+
if row_is_entirely_masked is not None:
|
| 467 |
+
out = out.masked_fill(row_is_entirely_masked[..., None], 0.)
|
| 468 |
+
|
| 469 |
+
return out
|
| 470 |
+
|
| 471 |
+
@compile
|
| 472 |
+
def apply_qk_layernorm(self, q, k):
|
| 473 |
+
q = self.q_norm(q)
|
| 474 |
+
k = self.k_norm(k)
|
| 475 |
+
return q, k
|
| 476 |
+
|
| 477 |
+
#@compile
|
| 478 |
+
def forward(
|
| 479 |
+
self,
|
| 480 |
+
x,
|
| 481 |
+
context = None,
|
| 482 |
+
mask = None,
|
| 483 |
+
context_mask = None,
|
| 484 |
+
rotary_pos_emb = None,
|
| 485 |
+
causal = None
|
| 486 |
+
):
|
| 487 |
+
h, kv_h, has_context = self.num_heads, self.kv_heads, context is not None
|
| 488 |
+
|
| 489 |
+
#print("h", h, "kv_h", kv_h, "has_context", has_context)
|
| 490 |
+
#print("mask", mask, "context_mask", context_mask)
|
| 491 |
+
|
| 492 |
+
kv_input = context if has_context else x
|
| 493 |
+
|
| 494 |
+
if hasattr(self, 'to_q'):
|
| 495 |
+
# Use separate linear projections for q and k/v
|
| 496 |
+
q = self.to_q(x)
|
| 497 |
+
q = rearrange(q, 'b n (h d) -> b h n d', h = h)
|
| 498 |
+
|
| 499 |
+
k, v = self.to_kv(kv_input).chunk(2, dim=-1)
|
| 500 |
+
|
| 501 |
+
k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = kv_h), (k, v))
|
| 502 |
+
else:
|
| 503 |
+
# Use fused linear projection
|
| 504 |
+
q, k, v = self.to_qkv(x).chunk(3, dim=-1)
|
| 505 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), (q, k, v))
|
| 506 |
+
|
| 507 |
+
# Normalize q and k for cosine sim attention
|
| 508 |
+
if self.qk_norm == "l2":
|
| 509 |
+
q = F.normalize(q, dim=-1)
|
| 510 |
+
k = F.normalize(k, dim=-1)
|
| 511 |
+
elif self.qk_norm == "ln":
|
| 512 |
+
q, k = self.apply_qk_layernorm(q, k)
|
| 513 |
+
|
| 514 |
+
if rotary_pos_emb is not None and not has_context:
|
| 515 |
+
freqs, _ = rotary_pos_emb
|
| 516 |
+
|
| 517 |
+
q_dtype = q.dtype
|
| 518 |
+
k_dtype = k.dtype
|
| 519 |
+
|
| 520 |
+
q = q.to(torch.float32)
|
| 521 |
+
k = k.to(torch.float32)
|
| 522 |
+
freqs = freqs.to(torch.float32)
|
| 523 |
+
|
| 524 |
+
q = apply_rotary_pos_emb(q, freqs)
|
| 525 |
+
k = apply_rotary_pos_emb(k, freqs)
|
| 526 |
+
|
| 527 |
+
q = q.to(q_dtype)
|
| 528 |
+
k = k.to(k_dtype)
|
| 529 |
+
|
| 530 |
+
input_mask = context_mask
|
| 531 |
+
|
| 532 |
+
if input_mask is None and not has_context:
|
| 533 |
+
input_mask = mask
|
| 534 |
+
|
| 535 |
+
# determine masking
|
| 536 |
+
masks = []
|
| 537 |
+
final_attn_mask = None # The mask that will be applied to the attention matrix, taking all masks into account
|
| 538 |
+
|
| 539 |
+
if input_mask is not None:
|
| 540 |
+
input_mask = rearrange(input_mask, 'b j -> b 1 1 j')
|
| 541 |
+
masks.append(~input_mask)
|
| 542 |
+
|
| 543 |
+
# Other masks will be added here later
|
| 544 |
+
|
| 545 |
+
if len(masks) > 0:
|
| 546 |
+
final_attn_mask = ~or_reduce(masks)
|
| 547 |
+
|
| 548 |
+
n, device = q.shape[-2], q.device
|
| 549 |
+
|
| 550 |
+
causal = self.causal if causal is None else causal
|
| 551 |
+
|
| 552 |
+
if n == 1 and causal:
|
| 553 |
+
causal = False
|
| 554 |
+
|
| 555 |
+
if self.natten_kernel_size is not None:
|
| 556 |
+
if natten is None:
|
| 557 |
+
raise ImportError('natten not installed, please install natten to use neighborhood attention')
|
| 558 |
+
|
| 559 |
+
dtype_in = q.dtype
|
| 560 |
+
q, k, v = map(lambda t: t.to(torch.float32), (q, k, v))
|
| 561 |
+
|
| 562 |
+
attn = natten.functional.na1d_qk(q, k, kernel_size = self.natten_kernel_size, dilation=1)
|
| 563 |
+
|
| 564 |
+
if final_attn_mask is not None:
|
| 565 |
+
attn = attn.masked_fill(final_attn_mask, -torch.finfo(attn.dtype).max)
|
| 566 |
+
|
| 567 |
+
attn = F.softmax(attn, dim=-1, dtype=torch.float32)
|
| 568 |
+
|
| 569 |
+
out = natten.functional.na1d_av(attn, v, kernel_size = self.natten_kernel_size, dilation=1).to(dtype_in)
|
| 570 |
+
|
| 571 |
+
# Prioritize Flash Attention 2
|
| 572 |
+
elif self.use_fa_flash:
|
| 573 |
+
assert final_attn_mask is None, 'masking not yet supported for Flash Attention 2'
|
| 574 |
+
# Flash Attention 2 requires FP16 inputs
|
| 575 |
+
fa_dtype_in = q.dtype
|
| 576 |
+
|
| 577 |
+
q, k, v = map(lambda t: rearrange(t, 'b h n d -> b n h d'), (q, k, v))
|
| 578 |
+
|
| 579 |
+
if fa_dtype_in != torch.float16 and fa_dtype_in != torch.bfloat16:
|
| 580 |
+
q, k, v = map(lambda t: t.to(torch.float16), (q, k, v))
|
| 581 |
+
|
| 582 |
+
out = flash_attn_func(q, k, v, causal = causal, window_size=self.sliding_window)
|
| 583 |
+
|
| 584 |
+
out = rearrange(out.to(fa_dtype_in), 'b n h d -> b h n d')
|
| 585 |
+
|
| 586 |
+
# Fall back to PyTorch implementation
|
| 587 |
+
elif self.use_pt_flash:
|
| 588 |
+
out = self.flash_attn(q, k, v, causal = causal, mask = final_attn_mask)
|
| 589 |
+
|
| 590 |
+
else:
|
| 591 |
+
# Fall back to custom implementation
|
| 592 |
+
|
| 593 |
+
if h != kv_h:
|
| 594 |
+
# Repeat interleave kv_heads to match q_heads
|
| 595 |
+
heads_per_kv_head = h // kv_h
|
| 596 |
+
k, v = map(lambda t: t.repeat_interleave(heads_per_kv_head, dim = 1), (k, v))
|
| 597 |
+
|
| 598 |
+
scale = 1. / (q.shape[-1] ** 0.5)
|
| 599 |
+
|
| 600 |
+
kv_einsum_eq = 'b j d' if k.ndim == 3 else 'b h j d'
|
| 601 |
+
|
| 602 |
+
dots = einsum(f'b h i d, {kv_einsum_eq} -> b h i j', q, k) * scale
|
| 603 |
+
|
| 604 |
+
i, j, dtype = *dots.shape[-2:], dots.dtype
|
| 605 |
+
|
| 606 |
+
mask_value = -torch.finfo(dots.dtype).max
|
| 607 |
+
|
| 608 |
+
if final_attn_mask is not None:
|
| 609 |
+
dots = dots.masked_fill(~final_attn_mask, mask_value)
|
| 610 |
+
|
| 611 |
+
if causal:
|
| 612 |
+
causal_mask = self.create_causal_mask(i, j, device = device)
|
| 613 |
+
dots = dots.masked_fill(causal_mask, mask_value)
|
| 614 |
+
|
| 615 |
+
attn = F.softmax(dots, dim=-1, dtype=torch.float32)
|
| 616 |
+
attn = attn.type(dtype)
|
| 617 |
+
|
| 618 |
+
out = einsum(f'b h i j, {kv_einsum_eq} -> b h i d', attn, v)
|
| 619 |
+
|
| 620 |
+
# merge heads
|
| 621 |
+
out = rearrange(out, ' b h n d -> b n (h d)')
|
| 622 |
+
|
| 623 |
+
# Communicate between heads
|
| 624 |
+
|
| 625 |
+
# with autocast(enabled = False):
|
| 626 |
+
# out_dtype = out.dtype
|
| 627 |
+
# out = out.to(torch.float32)
|
| 628 |
+
# out = self.to_out(out).to(out_dtype)
|
| 629 |
+
out = self.to_out(out)
|
| 630 |
+
|
| 631 |
+
if self.feat_scale:
|
| 632 |
+
out_dc = out.mean(dim=-2, keepdim=True)
|
| 633 |
+
out_hf = out - out_dc
|
| 634 |
+
|
| 635 |
+
# Selectively modulate DC and high frequency components
|
| 636 |
+
out = out + self.lambda_dc * out_dc + self.lambda_hf * out_hf
|
| 637 |
+
|
| 638 |
+
if mask is not None:
|
| 639 |
+
mask = rearrange(mask, 'b n -> b n 1')
|
| 640 |
+
out = out.masked_fill(~mask, 0.)
|
| 641 |
+
|
| 642 |
+
return out
|
| 643 |
+
|
| 644 |
+
class ConformerModule(nn.Module):
|
| 645 |
+
def __init__(
|
| 646 |
+
self,
|
| 647 |
+
dim,
|
| 648 |
+
norm_kwargs = {},
|
| 649 |
+
):
|
| 650 |
+
|
| 651 |
+
super().__init__()
|
| 652 |
+
|
| 653 |
+
self.dim = dim
|
| 654 |
+
|
| 655 |
+
self.in_norm = LayerNorm(dim, **norm_kwargs)
|
| 656 |
+
self.pointwise_conv = nn.Conv1d(dim, dim, kernel_size=1, bias=False)
|
| 657 |
+
self.glu = GLU(dim, dim, nn.SiLU())
|
| 658 |
+
self.depthwise_conv = nn.Conv1d(dim, dim, kernel_size=17, groups=dim, padding=8, bias=False)
|
| 659 |
+
self.mid_norm = LayerNorm(dim, **norm_kwargs) # This is a batch norm in the original but I don't like batch norm
|
| 660 |
+
self.swish = nn.SiLU()
|
| 661 |
+
self.pointwise_conv_2 = nn.Conv1d(dim, dim, kernel_size=1, bias=False)
|
| 662 |
+
|
| 663 |
+
#@compile
|
| 664 |
+
def forward(self, x):
|
| 665 |
+
x = self.in_norm(x)
|
| 666 |
+
x = rearrange(x, 'b n d -> b d n')
|
| 667 |
+
x = self.pointwise_conv(x)
|
| 668 |
+
x = rearrange(x, 'b d n -> b n d')
|
| 669 |
+
x = self.glu(x)
|
| 670 |
+
x = rearrange(x, 'b n d -> b d n')
|
| 671 |
+
x = self.depthwise_conv(x)
|
| 672 |
+
x = rearrange(x, 'b d n -> b n d')
|
| 673 |
+
x = self.mid_norm(x)
|
| 674 |
+
x = self.swish(x)
|
| 675 |
+
x = rearrange(x, 'b n d -> b d n')
|
| 676 |
+
x = self.pointwise_conv_2(x)
|
| 677 |
+
x = rearrange(x, 'b d n -> b n d')
|
| 678 |
+
|
| 679 |
+
return x
|
| 680 |
+
|
| 681 |
+
class TransformerBlock(nn.Module):
|
| 682 |
+
def __init__(
|
| 683 |
+
self,
|
| 684 |
+
dim,
|
| 685 |
+
dim_heads = 64,
|
| 686 |
+
cross_attend = False,
|
| 687 |
+
dim_context = None,
|
| 688 |
+
global_cond_dim = None,
|
| 689 |
+
causal = False,
|
| 690 |
+
zero_init_branch_outputs = True,
|
| 691 |
+
conformer = False,
|
| 692 |
+
layer_ix = -1,
|
| 693 |
+
remove_norms = False,
|
| 694 |
+
add_rope = False,
|
| 695 |
+
layer_scale = False,
|
| 696 |
+
attn_kwargs = {},
|
| 697 |
+
ff_kwargs = {},
|
| 698 |
+
norm_kwargs = {}
|
| 699 |
+
):
|
| 700 |
+
|
| 701 |
+
super().__init__()
|
| 702 |
+
self.dim = dim
|
| 703 |
+
self.dim_heads = dim_heads
|
| 704 |
+
self.cross_attend = cross_attend
|
| 705 |
+
self.dim_context = dim_context
|
| 706 |
+
self.causal = causal
|
| 707 |
+
|
| 708 |
+
if layer_scale and zero_init_branch_outputs:
|
| 709 |
+
print('zero_init_branch_outputs is redundant with layer_scale, setting zero_init_branch_outputs to False')
|
| 710 |
+
zero_init_branch_outputs = False
|
| 711 |
+
|
| 712 |
+
self.pre_norm = LayerNorm(dim,**norm_kwargs) if not remove_norms else nn.Identity()
|
| 713 |
+
|
| 714 |
+
self.add_rope = add_rope
|
| 715 |
+
|
| 716 |
+
self.self_attn = Attention(
|
| 717 |
+
dim,
|
| 718 |
+
dim_heads = dim_heads,
|
| 719 |
+
causal = causal,
|
| 720 |
+
zero_init_output=zero_init_branch_outputs,
|
| 721 |
+
**attn_kwargs
|
| 722 |
+
)
|
| 723 |
+
|
| 724 |
+
self.self_attn_scale = LayerScale(dim) if layer_scale else nn.Identity()
|
| 725 |
+
|
| 726 |
+
self.cross_attend = cross_attend
|
| 727 |
+
if cross_attend:
|
| 728 |
+
self.cross_attend_norm = LayerNorm(dim, **norm_kwargs) if not remove_norms else nn.Identity()
|
| 729 |
+
self.cross_attn = Attention(
|
| 730 |
+
dim,
|
| 731 |
+
dim_heads = dim_heads,
|
| 732 |
+
dim_context=dim_context,
|
| 733 |
+
causal = causal,
|
| 734 |
+
zero_init_output=zero_init_branch_outputs,
|
| 735 |
+
**attn_kwargs
|
| 736 |
+
)
|
| 737 |
+
self.cross_attn_scale = LayerScale(dim) if layer_scale else nn.Identity()
|
| 738 |
+
|
| 739 |
+
self.ff_norm = LayerNorm(dim, **norm_kwargs) if not remove_norms else nn.Identity()
|
| 740 |
+
self.ff = FeedForward(dim, zero_init_output=zero_init_branch_outputs, **ff_kwargs)
|
| 741 |
+
self.ff_scale = LayerScale(dim) if layer_scale else nn.Identity()
|
| 742 |
+
|
| 743 |
+
self.layer_ix = layer_ix
|
| 744 |
+
|
| 745 |
+
self.conformer = None
|
| 746 |
+
if conformer:
|
| 747 |
+
self.conformer = ConformerModule(dim, norm_kwargs=norm_kwargs)
|
| 748 |
+
self.conformer_scale = LayerScale(dim) if layer_scale else nn.Identity()
|
| 749 |
+
|
| 750 |
+
self.global_cond_dim = global_cond_dim
|
| 751 |
+
|
| 752 |
+
if global_cond_dim is not None:
|
| 753 |
+
self.to_scale_shift_gate = nn.Parameter(torch.randn(6*dim)/dim**0.5)
|
| 754 |
+
|
| 755 |
+
self.rope = RotaryEmbedding(max(dim_heads // 2, 32)) if add_rope else None
|
| 756 |
+
|
| 757 |
+
@compile
|
| 758 |
+
def forward(
|
| 759 |
+
self,
|
| 760 |
+
x,
|
| 761 |
+
context = None,
|
| 762 |
+
global_cond=None,
|
| 763 |
+
mask = None,
|
| 764 |
+
context_mask = None,
|
| 765 |
+
rotary_pos_emb = None
|
| 766 |
+
):
|
| 767 |
+
if rotary_pos_emb is None and self.add_rope:
|
| 768 |
+
rotary_pos_emb = self.rope.forward_from_seq_len(x.shape[-2])
|
| 769 |
+
|
| 770 |
+
if self.global_cond_dim is not None and self.global_cond_dim > 0 and global_cond is not None:
|
| 771 |
+
|
| 772 |
+
scale_self, shift_self, gate_self, scale_ff, shift_ff, gate_ff = (self.to_scale_shift_gate + global_cond).unsqueeze(1).chunk(6, dim=-1)
|
| 773 |
+
|
| 774 |
+
# self-attention with adaLN
|
| 775 |
+
residual = x
|
| 776 |
+
x = self.pre_norm(x)
|
| 777 |
+
x = x * (1 + scale_self) + shift_self
|
| 778 |
+
x = self.self_attn(x, mask = mask, rotary_pos_emb = rotary_pos_emb)
|
| 779 |
+
x = x * torch.sigmoid(1 - gate_self)
|
| 780 |
+
x = self.self_attn_scale(x)
|
| 781 |
+
x = x + residual
|
| 782 |
+
|
| 783 |
+
#print("self.cross_attend", self.cross_attend)
|
| 784 |
+
#print("context", context.shape)
|
| 785 |
+
if context is not None and self.cross_attend:
|
| 786 |
+
#print("mask", mask)
|
| 787 |
+
#print("context_mask", context_mask)
|
| 788 |
+
|
| 789 |
+
x = x + self.cross_attn_scale(self.cross_attn(self.cross_attend_norm(x), context = context, context_mask = context_mask))
|
| 790 |
+
|
| 791 |
+
if self.conformer is not None:
|
| 792 |
+
x = x + self.conformer_scale(self.conformer(x))
|
| 793 |
+
|
| 794 |
+
# feedforward with adaLN
|
| 795 |
+
residual = x
|
| 796 |
+
x = self.ff_norm(x)
|
| 797 |
+
x = x * (1 + scale_ff) + shift_ff
|
| 798 |
+
x = self.ff(x)
|
| 799 |
+
x = x * torch.sigmoid(1 - gate_ff)
|
| 800 |
+
x = self.ff_scale(x)
|
| 801 |
+
x = x + residual
|
| 802 |
+
|
| 803 |
+
else:
|
| 804 |
+
x = x + self.self_attn_scale(self.self_attn(self.pre_norm(x), mask = mask, rotary_pos_emb = rotary_pos_emb))
|
| 805 |
+
|
| 806 |
+
if context is not None and self.cross_attend:
|
| 807 |
+
#print(x.shape, context.shape, context_mask.shape if context_mask is not None else None)
|
| 808 |
+
#print(self.dim, self.dim_context, self.dim_heads)
|
| 809 |
+
x = x + self.cross_attn_scale(self.cross_attn(self.cross_attend_norm(x), context = context, context_mask = context_mask))
|
| 810 |
+
|
| 811 |
+
if self.conformer is not None:
|
| 812 |
+
x = x + self.conformer_scale(self.conformer(x))
|
| 813 |
+
|
| 814 |
+
x = x + self.ff_scale(self.ff(self.ff_norm(x)))
|
| 815 |
+
return x
|
| 816 |
+
|
| 817 |
+
class ContinuousTransformer(nn.Module):
|
| 818 |
+
def __init__(
|
| 819 |
+
self,
|
| 820 |
+
dim,
|
| 821 |
+
depth,
|
| 822 |
+
*,
|
| 823 |
+
dim_in = None,
|
| 824 |
+
dim_out = None,
|
| 825 |
+
num_heads = 64,
|
| 826 |
+
cross_attend=False,
|
| 827 |
+
cond_token_dim=None,
|
| 828 |
+
final_cross_attn_ix=-1,
|
| 829 |
+
global_cond_dim=None,
|
| 830 |
+
causal=False,
|
| 831 |
+
zero_init_branch_outputs=True,
|
| 832 |
+
conformer=False,
|
| 833 |
+
**kwargs
|
| 834 |
+
):
|
| 835 |
+
|
| 836 |
+
super().__init__()
|
| 837 |
+
|
| 838 |
+
self.dim = dim
|
| 839 |
+
self.depth = depth
|
| 840 |
+
self.causal = causal
|
| 841 |
+
self.layers = nn.ModuleList([])
|
| 842 |
+
|
| 843 |
+
self.project_in = nn.Linear(dim_in, dim, bias=False) if dim_in is not None else nn.Identity()
|
| 844 |
+
self.project_out = nn.Linear(dim, dim_out, bias=False) if dim_out is not None else nn.Identity()
|
| 845 |
+
|
| 846 |
+
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
self.global_cond_embedder = None
|
| 850 |
+
if global_cond_dim is not None:
|
| 851 |
+
self.global_cond_embedder = nn.Sequential(
|
| 852 |
+
nn.Linear(global_cond_dim, dim),
|
| 853 |
+
nn.SiLU(),
|
| 854 |
+
nn.Linear(dim, dim * 6)
|
| 855 |
+
)
|
| 856 |
+
|
| 857 |
+
self.final_cross_attn_ix = final_cross_attn_ix
|
| 858 |
+
|
| 859 |
+
for i in range(depth):
|
| 860 |
+
should_cross_attend = cross_attend and (self.final_cross_attn_ix == -1 or i <= (self.final_cross_attn_ix))
|
| 861 |
+
self.layers.append(
|
| 862 |
+
TransformerBlock(
|
| 863 |
+
dim,
|
| 864 |
+
dim_heads = (dim) // num_heads,
|
| 865 |
+
cross_attend = should_cross_attend,
|
| 866 |
+
dim_context = cond_token_dim,
|
| 867 |
+
global_cond_dim = global_cond_dim,
|
| 868 |
+
causal = causal,
|
| 869 |
+
zero_init_branch_outputs = zero_init_branch_outputs,
|
| 870 |
+
conformer=conformer,
|
| 871 |
+
layer_ix=i,
|
| 872 |
+
**kwargs
|
| 873 |
+
)
|
| 874 |
+
)
|
| 875 |
+
|
| 876 |
+
|
| 877 |
+
def forward(
|
| 878 |
+
self,
|
| 879 |
+
x,
|
| 880 |
+
mask = None,
|
| 881 |
+
prepend_embeds = None,
|
| 882 |
+
prepend_mask = None,
|
| 883 |
+
global_cond = None,
|
| 884 |
+
return_info = False,
|
| 885 |
+
context = None,
|
| 886 |
+
context_mask = None,
|
| 887 |
+
**kwargs
|
| 888 |
+
):
|
| 889 |
+
batch, seq, device = *x.shape[:2], x.device
|
| 890 |
+
|
| 891 |
+
model_dtype = next(self.parameters()).dtype
|
| 892 |
+
x = x.to(model_dtype)
|
| 893 |
+
|
| 894 |
+
info = {
|
| 895 |
+
"hidden_states": [],
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
|
| 899 |
+
x = self.project_in(x)
|
| 900 |
+
|
| 901 |
+
if prepend_embeds is not None:
|
| 902 |
+
prepend_length, prepend_dim = prepend_embeds.shape[1:]
|
| 903 |
+
|
| 904 |
+
assert prepend_dim == x.shape[-1], 'prepend dimension must match sequence dimension'
|
| 905 |
+
|
| 906 |
+
x = torch.cat((prepend_embeds, x), dim = -2)
|
| 907 |
+
|
| 908 |
+
if prepend_mask is not None or mask is not None:
|
| 909 |
+
mask = mask if mask is not None else torch.ones((batch, seq), device = device, dtype = torch.bool)
|
| 910 |
+
prepend_mask = prepend_mask if prepend_mask is not None else torch.ones((batch, prepend_length), device = device, dtype = torch.bool)
|
| 911 |
+
|
| 912 |
+
mask = torch.cat((prepend_mask, mask), dim = -1)
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
if global_cond is not None and self.global_cond_embedder is not None:
|
| 916 |
+
global_cond = self.global_cond_embedder(global_cond)
|
| 917 |
+
|
| 918 |
+
# Iterate over the transformer layers
|
| 919 |
+
for layer in self.layers:
|
| 920 |
+
#x = layer(x, rotary_pos_emb = rotary_pos_emb, global_cond=global_cond, **kwargs)
|
| 921 |
+
x = checkpoint(layer, x, rotary_pos_emb = None, global_cond=global_cond, context=context, mask=mask, context_mask=context_mask, **kwargs)
|
| 922 |
+
|
| 923 |
+
if return_info:
|
| 924 |
+
info["hidden_states"].append(x)
|
| 925 |
+
|
| 926 |
+
x = self.project_out(x)
|
| 927 |
+
|
| 928 |
+
|
| 929 |
+
if return_info:
|
| 930 |
+
return x, info
|
| 931 |
+
|
| 932 |
+
return x
|
requirements.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git+https://github.com/TEAMuP-dev/pyharp.git@v0.3.0
|
| 2 |
+
torch
|
| 3 |
+
torchaudio
|
| 4 |
+
omegaconf>=2.3.0
|
| 5 |
+
hydra-core>=1.3.0
|
| 6 |
+
numpy
|
| 7 |
+
scipy
|
| 8 |
+
soundfile
|
| 9 |
+
pyloudnorm
|
| 10 |
+
einops
|
| 11 |
+
packaging
|
| 12 |
+
librosa
|
| 13 |
+
transformers
|
| 14 |
+
wget
|
| 15 |
+
huggingface-hub
|
utils/MSS_loss.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Implementation of objective functions used in the task 'ITO-Master'
|
| 3 |
+
https://github.com/SonyResearch/ITO-Master/blob/master/ito_master/modules/loss.py
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import math
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
import auraloss
|
| 12 |
+
import torchaudio
|
| 13 |
+
import warnings
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
import os
|
| 17 |
+
import sys
|
| 18 |
+
|
| 19 |
+
currentdir = os.path.dirname(os.path.realpath(__file__))
|
| 20 |
+
sys.path.append(os.path.dirname(currentdir))
|
| 21 |
+
|
| 22 |
+
class FrontEnd(nn.Module):
|
| 23 |
+
def __init__(self, channel='stereo', \
|
| 24 |
+
n_fft=2048, \
|
| 25 |
+
n_mels=128, \
|
| 26 |
+
sample_rate=44100, \
|
| 27 |
+
hop_length=None, \
|
| 28 |
+
win_length=None, \
|
| 29 |
+
window="hann", \
|
| 30 |
+
eps=1e-7, \
|
| 31 |
+
device=torch.device("cpu")):
|
| 32 |
+
super(FrontEnd, self).__init__()
|
| 33 |
+
self.channel = channel
|
| 34 |
+
self.n_fft = n_fft
|
| 35 |
+
self.n_mels = n_mels
|
| 36 |
+
self.sample_rate = sample_rate
|
| 37 |
+
self.hop_length = n_fft//4 if hop_length==None else hop_length
|
| 38 |
+
self.win_length = n_fft if win_length==None else win_length
|
| 39 |
+
self.eps = eps
|
| 40 |
+
if window=="hann":
|
| 41 |
+
self.window = torch.hann_window(window_length=self.win_length, periodic=True).to(device)
|
| 42 |
+
elif window=="hamming":
|
| 43 |
+
self.window = torch.hamming_window(window_length=self.win_length, periodic=True).to(device)
|
| 44 |
+
self.melscale_transform = torchaudio.transforms.MelScale(n_mels=self.n_mels, \
|
| 45 |
+
sample_rate=self.sample_rate, \
|
| 46 |
+
n_stft=self.n_fft//2+1).to(device)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def forward(self, input, mode):
|
| 50 |
+
# front-end function which channel-wise combines all demanded features
|
| 51 |
+
# input shape : batch x channel x raw waveform
|
| 52 |
+
# output shape : batch x channel x frequency x time
|
| 53 |
+
phase_output = None
|
| 54 |
+
|
| 55 |
+
front_output_list = []
|
| 56 |
+
for cur_mode in mode:
|
| 57 |
+
# Real & Imaginary
|
| 58 |
+
if cur_mode=="cplx":
|
| 59 |
+
if self.channel=="mono":
|
| 60 |
+
output = torch.stft(input, n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window)
|
| 61 |
+
elif self.channel=="stereo":
|
| 62 |
+
output_l = torch.stft(input[:,0], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window)
|
| 63 |
+
output_r = torch.stft(input[:,1], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window)
|
| 64 |
+
output = torch.cat((output_l, output_r), axis=-1)
|
| 65 |
+
if input.shape[-1] % round(self.n_fft/4) == 0:
|
| 66 |
+
output = output[:, :, :-1]
|
| 67 |
+
if self.n_fft % 2 == 0:
|
| 68 |
+
output = output[:, :-1]
|
| 69 |
+
front_output_list.append(output.permute(0, 3, 1, 2))
|
| 70 |
+
# Magnitude & Phase or Mel
|
| 71 |
+
elif "mag" in cur_mode or "mel" in cur_mode:
|
| 72 |
+
if self.channel=="mono":
|
| 73 |
+
cur_cplx = torch.stft(input, n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window, return_complex=True)
|
| 74 |
+
output = self.mag(cur_cplx).unsqueeze(-1)[..., 0:1]
|
| 75 |
+
if "mag_phase" in cur_mode:
|
| 76 |
+
phase = self.phase(cur_cplx)
|
| 77 |
+
if "mel" in cur_mode:
|
| 78 |
+
output = self.melscale_transform(output.squeeze(-1)).unsqueeze(-1)
|
| 79 |
+
elif self.channel=="stereo":
|
| 80 |
+
cplx_l = torch.stft(input[:,0], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window, return_complex=True)
|
| 81 |
+
cplx_r = torch.stft(input[:,1], n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, window=self.window, return_complex=True)
|
| 82 |
+
mag_l = self.mag(cplx_l).unsqueeze(-1)
|
| 83 |
+
mag_r = self.mag(cplx_r).unsqueeze(-1)
|
| 84 |
+
output = torch.cat((mag_l, mag_r), axis=-1)
|
| 85 |
+
if "mag_phase" in cur_mode:
|
| 86 |
+
phase_l = self.phase(cplx_l).unsqueeze(-1)
|
| 87 |
+
phase_r = self.phase(cplx_r).unsqueeze(-1)
|
| 88 |
+
output = torch.cat((mag_l, phase_l, mag_r, phase_r), axis=-1)
|
| 89 |
+
if "mel" in cur_mode:
|
| 90 |
+
output = torch.cat((self.melscale_transform(mag_l.squeeze(-1)).unsqueeze(-1), self.melscale_transform(mag_r.squeeze(-1)).unsqueeze(-1)), axis=-1)
|
| 91 |
+
|
| 92 |
+
if "log" in cur_mode:
|
| 93 |
+
output = torch.log(output+self.eps)
|
| 94 |
+
|
| 95 |
+
if input.shape[-1] % round(self.n_fft/4) == 0:
|
| 96 |
+
output = output[:, :, :-1]
|
| 97 |
+
if cur_mode!="mel" and self.n_fft % 2 == 0: # discard highest frequency
|
| 98 |
+
output = output[:, 1:]
|
| 99 |
+
front_output_list.append(output.permute(0, 3, 1, 2))
|
| 100 |
+
|
| 101 |
+
# combine all demanded features
|
| 102 |
+
if not front_output_list:
|
| 103 |
+
raise NameError("NameError at FrontEnd: check using features for front-end")
|
| 104 |
+
elif len(mode)!=1:
|
| 105 |
+
for i, cur_output in enumerate(front_output_list):
|
| 106 |
+
if i==0:
|
| 107 |
+
front_output = cur_output
|
| 108 |
+
else:
|
| 109 |
+
front_output = torch.cat((front_output, cur_output), axis=1)
|
| 110 |
+
else:
|
| 111 |
+
front_output = front_output_list[0]
|
| 112 |
+
|
| 113 |
+
return front_output
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def mag(self, cplx_input, eps=1e-07):
|
| 117 |
+
# mag_summed = cplx_input.pow(2.).sum(-1) + eps
|
| 118 |
+
mag_summed = cplx_input.real.pow(2.) + cplx_input.imag.pow(2.) + eps
|
| 119 |
+
return mag_summed.pow(0.5)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def phase(self, cplx_input, ):
|
| 123 |
+
return torch.atan2(cplx_input.imag, cplx_input.real)
|
| 124 |
+
# return torch.angle(cplx_input)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# Multi-Scale Spectral Loss proposed at the paper "DDSP: DIFFERENTIABLE DIGITAL SIGNAL PROCESSING" (https://arxiv.org/abs/2001.04643)
|
| 129 |
+
# we extend this loss by applying it to mid/side channels
|
| 130 |
+
class MultiScale_Spectral_Loss_MidSide_DDSP(nn.Module):
|
| 131 |
+
def __init__(self, mode='midside', \
|
| 132 |
+
reduce=True, \
|
| 133 |
+
n_filters=None, \
|
| 134 |
+
windows_size=None, \
|
| 135 |
+
hops_size=None, \
|
| 136 |
+
window="hann", \
|
| 137 |
+
eps=1e-7, \
|
| 138 |
+
device=torch.device("cpu")):
|
| 139 |
+
super(MultiScale_Spectral_Loss_MidSide_DDSP, self).__init__()
|
| 140 |
+
self.mode = mode
|
| 141 |
+
self.eps = eps
|
| 142 |
+
self.mid_weight = 0.5 # value in the range of 0.0 ~ 1.0
|
| 143 |
+
self.logmag_weight = 0.1
|
| 144 |
+
|
| 145 |
+
if n_filters is None:
|
| 146 |
+
n_filters = [4096, 2048, 1024, 512]
|
| 147 |
+
if windows_size is None:
|
| 148 |
+
windows_size = [4096, 2048, 1024, 512]
|
| 149 |
+
if hops_size is None:
|
| 150 |
+
hops_size = [1024, 512, 256, 128]
|
| 151 |
+
|
| 152 |
+
self.multiscales = []
|
| 153 |
+
for i in range(len(windows_size)):
|
| 154 |
+
cur_scale = {'window_size' : float(windows_size[i])}
|
| 155 |
+
if self.mode=='midside':
|
| 156 |
+
cur_scale['front_end'] = FrontEnd(channel='mono', \
|
| 157 |
+
n_fft=n_filters[i], \
|
| 158 |
+
hop_length=hops_size[i], \
|
| 159 |
+
win_length=windows_size[i], \
|
| 160 |
+
window=window, \
|
| 161 |
+
device=device)
|
| 162 |
+
elif self.mode=='ori':
|
| 163 |
+
cur_scale['front_end'] = FrontEnd(channel='stereo', \
|
| 164 |
+
n_fft=n_filters[i], \
|
| 165 |
+
hop_length=hops_size[i], \
|
| 166 |
+
win_length=windows_size[i], \
|
| 167 |
+
window=window, \
|
| 168 |
+
device=device)
|
| 169 |
+
self.multiscales.append(cur_scale)
|
| 170 |
+
|
| 171 |
+
self.reduce=reduce
|
| 172 |
+
self.objective_l1 = nn.L1Loss(reduce=reduce)
|
| 173 |
+
self.objective_l2 = nn.MSELoss(reduce=reduce)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def forward(self, est_targets, targets):
|
| 177 |
+
if self.mode=='midside':
|
| 178 |
+
return self.forward_midside(est_targets, targets)
|
| 179 |
+
elif self.mode=='ori':
|
| 180 |
+
return self.forward_ori(est_targets, targets)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def forward_ori(self, est_targets, targets):
|
| 184 |
+
if self.reduce:
|
| 185 |
+
total_mag_loss = 0.0
|
| 186 |
+
total_logmag_loss = 0.0
|
| 187 |
+
else:
|
| 188 |
+
total_mag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
|
| 189 |
+
total_logmag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
|
| 190 |
+
for cur_scale in self.multiscales:
|
| 191 |
+
est_mag = cur_scale['front_end'](est_targets, mode=["mag"])
|
| 192 |
+
tgt_mag = cur_scale['front_end'](targets, mode=["mag"])
|
| 193 |
+
|
| 194 |
+
mag_loss = self.magnitude_loss(est_mag, tgt_mag)
|
| 195 |
+
logmag_loss = self.log_magnitude_loss(est_mag, tgt_mag)
|
| 196 |
+
if self.reduce:
|
| 197 |
+
total_mag_loss += mag_loss
|
| 198 |
+
total_logmag_loss += logmag_loss
|
| 199 |
+
else:
|
| 200 |
+
total_logmag_loss += logmag_loss.mean((1, 2, 3)).unsqueeze(-1)
|
| 201 |
+
total_mag_loss += mag_loss.mean((1, 2, 3)).unsqueeze(-1)
|
| 202 |
+
# return total_loss
|
| 203 |
+
return (1-self.logmag_weight)*total_mag_loss + \
|
| 204 |
+
(self.logmag_weight)*total_logmag_loss
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def forward_midside(self, est_targets, targets):
|
| 208 |
+
est_mid, est_side = self.to_mid_side(est_targets)
|
| 209 |
+
tgt_mid, tgt_side = self.to_mid_side(targets)
|
| 210 |
+
if self.reduce:
|
| 211 |
+
total_mag_loss = 0.0
|
| 212 |
+
total_logmag_loss = 0.0
|
| 213 |
+
else:
|
| 214 |
+
total_logmag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
|
| 215 |
+
total_mag_loss=torch.zeros(est_targets.shape[0], 1).to(est_targets.device)
|
| 216 |
+
|
| 217 |
+
for cur_scale in self.multiscales:
|
| 218 |
+
est_mid_mag = cur_scale['front_end'](est_mid, mode=["mag"])
|
| 219 |
+
est_side_mag = cur_scale['front_end'](est_side, mode=["mag"])
|
| 220 |
+
tgt_mid_mag = cur_scale['front_end'](tgt_mid, mode=["mag"])
|
| 221 |
+
tgt_side_mag = cur_scale['front_end'](tgt_side, mode=["mag"])
|
| 222 |
+
|
| 223 |
+
mag_loss = self.mid_weight*self.magnitude_loss(est_mid_mag, tgt_mid_mag) + \
|
| 224 |
+
(1-self.mid_weight)*self.magnitude_loss(est_side_mag, tgt_side_mag)
|
| 225 |
+
logmag_loss = self.mid_weight*self.log_magnitude_loss(est_mid_mag, tgt_mid_mag) + \
|
| 226 |
+
(1-self.mid_weight)*self.log_magnitude_loss(est_side_mag, tgt_side_mag)
|
| 227 |
+
|
| 228 |
+
#take mean over all dimensions except batch
|
| 229 |
+
if self.reduce:
|
| 230 |
+
total_mag_loss += mag_loss
|
| 231 |
+
total_logmag_loss += logmag_loss
|
| 232 |
+
else:
|
| 233 |
+
total_mag_loss += mag_loss.mean((1, 2, 3)).unsqueeze(-1)
|
| 234 |
+
#mean over dims 1, 2, 3
|
| 235 |
+
total_logmag_loss += logmag_loss.mean((1, 2, 3)).unsqueeze(-1)
|
| 236 |
+
# return total_loss
|
| 237 |
+
return (1-self.logmag_weight)*total_mag_loss + \
|
| 238 |
+
(self.logmag_weight)*total_logmag_loss
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def to_mid_side(self, stereo_in):
|
| 242 |
+
mid = stereo_in[:,0] + stereo_in[:,1]
|
| 243 |
+
side = stereo_in[:,0] - stereo_in[:,1]
|
| 244 |
+
return mid, side
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def magnitude_loss(self, est_mag_spec, tgt_mag_spec):
|
| 248 |
+
if self.reduce:
|
| 249 |
+
return torch.norm(self.objective_l1(est_mag_spec, tgt_mag_spec))
|
| 250 |
+
else:
|
| 251 |
+
return self.objective_l1(est_mag_spec, tgt_mag_spec)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def log_magnitude_loss(self, est_mag_spec, tgt_mag_spec):
|
| 255 |
+
est_log_mag_spec = torch.log10(est_mag_spec+self.eps)
|
| 256 |
+
tgt_log_mag_spec = torch.log10(tgt_mag_spec+self.eps)
|
| 257 |
+
return self.objective_l2(est_log_mag_spec, tgt_log_mag_spec)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
|
utils/__init__.py
ADDED
|
File without changes
|
utils/collators.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
import torchaudio
|
| 6 |
+
|
| 7 |
+
def collate_multitrack_sim(batch, max_tracks=None):
|
| 8 |
+
|
| 9 |
+
x= [ data_i[0] for data_i in batch ] # x is a list of tensors, each tensor is a track
|
| 10 |
+
clusters=[ torch.tensor(data_i[1]) for data_i in batch ] # cluster is a list of tensors, each tensor is a cluster
|
| 11 |
+
taxonomies=[ data_i[2] for data_i in batch ] # taxonomy is a
|
| 12 |
+
|
| 13 |
+
if max_tracks is None:
|
| 14 |
+
max_tracks = max(track.shape[0] for track in x) # Find the maximum number of tracks in the batch
|
| 15 |
+
else:
|
| 16 |
+
if max_tracks > max(track.shape[0] for track in x):
|
| 17 |
+
print(f"Warning: max_tracks is set to {max_tracks}, but the maximum number of tracks in the batch is {max(track.shape[0] for track in x)}. I dont know what will happen, consider increasing max_tracks," )
|
| 18 |
+
|
| 19 |
+
# Pad each examples with zeros to the maximum length (dimension 0)
|
| 20 |
+
|
| 21 |
+
padded_x = []
|
| 22 |
+
padded_taxonomies = []
|
| 23 |
+
masks = torch.zeros((len(x), max_tracks), dtype=torch.bool) # Create a mask tensor
|
| 24 |
+
|
| 25 |
+
for i in range(len(x)):
|
| 26 |
+
current_x = x[i]
|
| 27 |
+
current_taxonomies = taxonomies[i]
|
| 28 |
+
|
| 29 |
+
# Get current number of tracks
|
| 30 |
+
current_tracks = current_x.shape[0]
|
| 31 |
+
|
| 32 |
+
masks[i, :current_tracks] = 1 # Set mask for current tracks
|
| 33 |
+
|
| 34 |
+
if current_tracks < max_tracks:
|
| 35 |
+
# Pad dimension N (first dimension)
|
| 36 |
+
# For a tensor of shape [N, C, L], we need to pad the first dimension
|
| 37 |
+
# F.pad expects padding as (pad_left, pad_right, pad_top, pad_bottom, pad_front, pad_back)
|
| 38 |
+
# To pad the first dimension, we need to specify padding for the third-from-last dimension
|
| 39 |
+
pad_size = (0, 0, # last dim (L): no padding
|
| 40 |
+
0, 0, # second-to-last dim (C): no padding
|
| 41 |
+
0, max_tracks - current_tracks) # third-to-last dim (N): pad at the end
|
| 42 |
+
|
| 43 |
+
padded_x.append(F.pad(current_x, pad_size))
|
| 44 |
+
|
| 45 |
+
# Pad taxonomies
|
| 46 |
+
padded_taxonomies.append(current_taxonomies + [None] * (max_tracks - len(current_taxonomies)))
|
| 47 |
+
elif current_tracks > max_tracks :
|
| 48 |
+
raise ValueError(f"Number of tracks {current_tracks} exceeds maximum allowed {max_tracks}. that is impossible")
|
| 49 |
+
else:
|
| 50 |
+
padded_x.append(current_x)
|
| 51 |
+
padded_taxonomies.append(current_taxonomies[:max_tracks])
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
x_stacked = torch.stack(padded_x, dim=0) # Shape: [B, max_tracks, C, L]
|
| 56 |
+
|
| 57 |
+
clusters_stacked = torch.stack(clusters, dim=0) # Shape: [B, max_tracks]
|
| 58 |
+
|
| 59 |
+
return {
|
| 60 |
+
'x': x_stacked,
|
| 61 |
+
'clusters': clusters_stacked,
|
| 62 |
+
'taxonomies': padded_taxonomies,
|
| 63 |
+
"masks": masks
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
def collate_multitrack(batch, max_tracks=None, sample_rate=None, segment_length=None, device=None):
|
| 67 |
+
|
| 68 |
+
x= [ data_i[0] for data_i in batch ] # x is a list of tensors, each tensor is a track
|
| 69 |
+
|
| 70 |
+
paths=[ data_i[1] for data_i in batch ] # paths is a list of paths to the audio files, each path is a string
|
| 71 |
+
fs= [ data_i[2] for data_i in batch ] # fs is a list of sample rates, each sample rate is an integer
|
| 72 |
+
|
| 73 |
+
if max_tracks is None:
|
| 74 |
+
max_tracks = max(track.shape[0] for track in x) # Find the maximum number of tracks in the batch
|
| 75 |
+
else:
|
| 76 |
+
if max_tracks < max(track.shape[0] for track in x):
|
| 77 |
+
print(f"Warning: max_tracks is set to {max_tracks}, but the maximum number of tracks in the batch is {max(track.shape[0] for track in x)}. I will crop the last tracks. I hope the order is random..." )
|
| 78 |
+
|
| 79 |
+
for i in range(len(x)):
|
| 80 |
+
if x[i].shape[0] > max_tracks:
|
| 81 |
+
print("cropping x[i] to max_tracks")
|
| 82 |
+
x[i] = x[i][:max_tracks]
|
| 83 |
+
|
| 84 |
+
is_x_none= any(x_i is None for x_i in x)
|
| 85 |
+
|
| 86 |
+
assert not (is_x_none ), "Either x or y should be None, but not both. This is a bug in the collator"
|
| 87 |
+
|
| 88 |
+
#now resample audio to 44100 Hz and cut to segment length
|
| 89 |
+
for i in range(len(x)):
|
| 90 |
+
x[i] = x[i].to(device) # Move to device
|
| 91 |
+
|
| 92 |
+
if fs[i] != sample_rate:
|
| 93 |
+
if fs[i] == 48000 and sample_rate == 44100:
|
| 94 |
+
#print(f"Resampling audio from {fs[i]} Hz to {sample_rate} Hz")
|
| 95 |
+
x[i]=torchaudio.functional.resample(x[i], orig_freq=160, new_freq=147)
|
| 96 |
+
else:
|
| 97 |
+
#print(f"Resampling audio from {fs[i]} Hz to {sample_rate} Hz")
|
| 98 |
+
x[i]=torchaudio.functional.resample(x[i], fs[i], sample_rate)
|
| 99 |
+
|
| 100 |
+
assert segment_length is not None, "segment_length should be set to the length of the audio segment in samples"
|
| 101 |
+
if x[i].shape[-1] > segment_length:
|
| 102 |
+
x[i] = x[i][..., :segment_length]
|
| 103 |
+
elif x[i].shape[-1] < segment_length:
|
| 104 |
+
raise ValueError(f"Audio length {x[i].shape[-1]} is less than segment length {segment_length}. Please check your data.")
|
| 105 |
+
|
| 106 |
+
# Pad each examples with zeros to the maximum length (dimension 0)
|
| 107 |
+
padded_x = []
|
| 108 |
+
masks = torch.zeros((len(x), max_tracks), dtype=torch.bool) # Create a mask tensor
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
for i in range(len(x)):
|
| 112 |
+
if not is_x_none:
|
| 113 |
+
current_x = x[i]
|
| 114 |
+
|
| 115 |
+
# Get current number of tracks
|
| 116 |
+
current_tracks = current_x.shape[0]
|
| 117 |
+
|
| 118 |
+
masks[i, :current_tracks] = 1 # Set mask for current tracks
|
| 119 |
+
|
| 120 |
+
if current_tracks < max_tracks:
|
| 121 |
+
# Pad dimension N (first dimension)
|
| 122 |
+
# For a tensor of shape [N, C, L], we need to pad the first dimension
|
| 123 |
+
# F.pad expects padding as (pad_left, pad_right, pad_top, pad_bottom, pad_front, pad_back)
|
| 124 |
+
# To pad the first dimension, we need to specify padding for the third-from-last dimension
|
| 125 |
+
pad_size = (0, 0, # last dim (L): no padding
|
| 126 |
+
0, 0, # second-to-last dim (C): no padding
|
| 127 |
+
0, max_tracks - current_tracks) # third-to-last dim (N): pad at the end
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
if not is_x_none:
|
| 131 |
+
padded_x.append(F.pad(current_x, pad_size))
|
| 132 |
+
|
| 133 |
+
elif current_tracks > max_tracks :
|
| 134 |
+
raise ValueError(f"Number of tracks {current_tracks} exceeds maximum allowed {max_tracks}. that is impossible")
|
| 135 |
+
else:
|
| 136 |
+
if not is_x_none:
|
| 137 |
+
padded_x.append(current_x)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
if not is_x_none:
|
| 141 |
+
x_stacked = torch.stack(padded_x, dim=0) # Shape: [B, max_tracks, C, L]
|
| 142 |
+
|
| 143 |
+
return {
|
| 144 |
+
'y': x_stacked.to(device), # Shape: [B, max_tracks, C, L]
|
| 145 |
+
"masks": masks.to(device), # Shape: [B, max_tracks]
|
| 146 |
+
"paths": paths,
|
| 147 |
+
"fs": fs
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
def collate_multitrack_paired(batch, max_tracks=None, sample_rate=None, segment_length=None, device=None):
|
| 151 |
+
|
| 152 |
+
x= [ data_i[0] for data_i in batch ] # x is a list of tensors, each tensor is a track
|
| 153 |
+
y= [ data_i[1] for data_i in batch ] # x is a list of tensors, each tensor is a track
|
| 154 |
+
|
| 155 |
+
taxonomies=[ data_i[2] for data_i in batch ] # taxonomy is a
|
| 156 |
+
|
| 157 |
+
paths=[ data_i[3] for data_i in batch ] # paths is a list of paths to the audio files, each path is a string
|
| 158 |
+
|
| 159 |
+
if max_tracks is None:
|
| 160 |
+
max_tracks = max(track.shape[0] for track in x) # Find the maximum number of tracks in the batch
|
| 161 |
+
else:
|
| 162 |
+
if max_tracks < max(track.shape[0] for track in x):
|
| 163 |
+
print(f"Warning: max_tracks is set to {max_tracks}, but the maximum number of tracks in the batch is {max(track.shape[0] for track in x)}. I will crop the last tracks. I hope the order is random..." )
|
| 164 |
+
|
| 165 |
+
for i in range(len(x)):
|
| 166 |
+
if x[i].shape[0] > max_tracks:
|
| 167 |
+
print("cropping x[i] to max_tracks")
|
| 168 |
+
x[i] = x[i][:max_tracks]
|
| 169 |
+
if y[i].shape[0] > max_tracks:
|
| 170 |
+
print("cropping y[i] to max_tracks")
|
| 171 |
+
y[i] = y[i][:max_tracks]
|
| 172 |
+
if len(taxonomies[i]) > max_tracks:
|
| 173 |
+
print("cropping taxonomies[i] to max_tracks")
|
| 174 |
+
taxonomies[i] = taxonomies[i][:max_tracks]
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
is_x_none= any(x_i is None for x_i in x)
|
| 179 |
+
is_y_none= any(y_i is None for y_i in y)
|
| 180 |
+
|
| 181 |
+
assert not (is_x_none and is_y_none), "Either x or y should be None, but not both. This is a bug in the collator"
|
| 182 |
+
|
| 183 |
+
# Pad each examples with zeros to the maximum length (dimension 0)
|
| 184 |
+
padded_x = []
|
| 185 |
+
padded_y = []
|
| 186 |
+
padded_taxonomies = []
|
| 187 |
+
masks = torch.zeros((len(x), max_tracks), dtype=torch.bool) # Create a mask tensor
|
| 188 |
+
|
| 189 |
+
for i in range(len(x)):
|
| 190 |
+
|
| 191 |
+
if not is_x_none:
|
| 192 |
+
current_x = x[i]
|
| 193 |
+
if not is_y_none:
|
| 194 |
+
current_y = y[i]
|
| 195 |
+
current_taxonomies = taxonomies[i]
|
| 196 |
+
|
| 197 |
+
# Get current number of tracks
|
| 198 |
+
if is_x_none:
|
| 199 |
+
current_tracks = current_y.shape[0]
|
| 200 |
+
else:
|
| 201 |
+
current_tracks = current_x.shape[0]
|
| 202 |
+
if not is_y_none:
|
| 203 |
+
assert current_tracks == current_y.shape[0], f"Number of tracks in x ({current_tracks}) does not match number of tracks in y ({current_y.shape[0]})"
|
| 204 |
+
|
| 205 |
+
masks[i, :current_tracks] = 1 # Set mask for current tracks
|
| 206 |
+
|
| 207 |
+
if current_tracks < max_tracks:
|
| 208 |
+
# Pad dimension N (first dimension)
|
| 209 |
+
# For a tensor of shape [N, C, L], we need to pad the first dimension
|
| 210 |
+
# F.pad expects padding as (pad_left, pad_right, pad_top, pad_bottom, pad_front, pad_back)
|
| 211 |
+
# To pad the first dimension, we need to specify padding for the third-from-last dimension
|
| 212 |
+
pad_size = (0, 0, # last dim (L): no padding
|
| 213 |
+
0, 0, # second-to-last dim (C): no padding
|
| 214 |
+
0, max_tracks - current_tracks) # third-to-last dim (N): pad at the end
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
if not is_x_none:
|
| 218 |
+
padded_x.append(F.pad(current_x, pad_size))
|
| 219 |
+
if not is_y_none:
|
| 220 |
+
padded_y.append(F.pad(current_y, pad_size))
|
| 221 |
+
|
| 222 |
+
# Pad taxonomies
|
| 223 |
+
padded_taxonomies.append(current_taxonomies + [None] * (max_tracks - len(current_taxonomies)))
|
| 224 |
+
elif current_tracks > max_tracks :
|
| 225 |
+
raise ValueError(f"Number of tracks {current_tracks} exceeds maximum allowed {max_tracks}. that is impossible")
|
| 226 |
+
else:
|
| 227 |
+
if not is_x_none:
|
| 228 |
+
padded_x.append(current_x)
|
| 229 |
+
if not is_y_none:
|
| 230 |
+
padded_y.append(current_y)
|
| 231 |
+
|
| 232 |
+
padded_taxonomies.append(current_taxonomies[:max_tracks])
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
if not is_x_none:
|
| 236 |
+
x_stacked = torch.stack(padded_x, dim=0) # Shape: [B, max_tracks, C, L]
|
| 237 |
+
|
| 238 |
+
if not is_y_none:
|
| 239 |
+
y_stacked = torch.stack(padded_y, dim=0) # Shape: [B, max_tracks, C, L]
|
| 240 |
+
|
| 241 |
+
if is_x_none:
|
| 242 |
+
return {
|
| 243 |
+
'y': y_stacked,
|
| 244 |
+
'taxonomies': padded_taxonomies,
|
| 245 |
+
"masks": masks,
|
| 246 |
+
"paths": paths
|
| 247 |
+
}
|
| 248 |
+
if is_y_none:
|
| 249 |
+
return {
|
| 250 |
+
'x': x_stacked,
|
| 251 |
+
'taxonomies': padded_taxonomies,
|
| 252 |
+
"masks": masks,
|
| 253 |
+
"paths": paths
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
return {
|
| 257 |
+
'x': x_stacked,
|
| 258 |
+
'y': y_stacked,
|
| 259 |
+
'taxonomies': padded_taxonomies,
|
| 260 |
+
"masks": masks,
|
| 261 |
+
"paths": paths
|
| 262 |
+
}
|
utils/common_audioeffects.py
ADDED
|
@@ -0,0 +1,1729 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Audio effects for data augmentation.
|
| 3 |
+
|
| 4 |
+
Several audio effects can be combined into an augmentation chain.
|
| 5 |
+
|
| 6 |
+
Important note: We assume that the parallelization during training is done using
|
| 7 |
+
multi-processing and not multi-threading. Hence, we do not need the
|
| 8 |
+
`@sox.sox_context()` decorators as discussed in this
|
| 9 |
+
[thread](https://github.com/pseeth/soxbindings/issues/4).
|
| 10 |
+
|
| 11 |
+
Section 2, TL21
|
| 12 |
+
AI Speech and Sound Group, SL1
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from itertools import permutations
|
| 16 |
+
import os
|
| 17 |
+
import io
|
| 18 |
+
import functools
|
| 19 |
+
import lameenc
|
| 20 |
+
import logging
|
| 21 |
+
import numpy as np
|
| 22 |
+
import pymixconsole as pymc
|
| 23 |
+
from pymixconsole.parameter import Parameter
|
| 24 |
+
from pymixconsole.parameter_list import ParameterList
|
| 25 |
+
from pymixconsole.processor import Processor
|
| 26 |
+
from random import shuffle
|
| 27 |
+
from scipy.signal import oaconvolve
|
| 28 |
+
import soundfile as sf
|
| 29 |
+
#import soxbindings as sox
|
| 30 |
+
from typing import List, Optional, Tuple, Union, Dict
|
| 31 |
+
from numba import jit
|
| 32 |
+
|
| 33 |
+
#from common_dataprocessing import sample_data
|
| 34 |
+
|
| 35 |
+
# prevent pysox from logging warnings regarding non-opimal timestretch factors
|
| 36 |
+
logging.getLogger('sox').setLevel(logging.ERROR)
|
| 37 |
+
|
| 38 |
+
# set maximum peak value if we pass a signal through SOX
|
| 39 |
+
MAX_SOX_PROCESSING_PEAK = 0.707
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def convert_audio2data(x):
|
| 43 |
+
"""
|
| 44 |
+
Convert audio data from the format it was stored in to float32.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
x (Numpy array): input with `x.dtype` either `np.int16`, `np.int32`, `np.float32` or `np.float64`.
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
Numpy array: output with values in [-1., 1.) where `dtype` is `np.float32`.
|
| 51 |
+
"""
|
| 52 |
+
if x.dtype in [np.float32, np.float64]:
|
| 53 |
+
return x.astype(dtype=np.float32)
|
| 54 |
+
else:
|
| 55 |
+
return (x.astype(dtype=np.float64) / (1. + np.iinfo(x.dtype).max)).astype(np.float32)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def sample_data(data: Tuple[int, Union[np.ndarray, functools.partial]],
|
| 60 |
+
start: int = 0, length: Optional[int] = None) -> np.ndarray:
|
| 61 |
+
"""
|
| 62 |
+
Load one stem specified by `data`.
|
| 63 |
+
|
| 64 |
+
Returns the audio beginning from `start` either up to the end (if `length` is None)
|
| 65 |
+
or until the provided `length`. For the case that `start + length > n_samples`, we do a wrap-around and
|
| 66 |
+
load the remaining samples from the beginning of `data`.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
data: Data with shape (n_samples, data).
|
| 70 |
+
start: Start index.
|
| 71 |
+
length: Length of sample. If `length` is not None, `length` samples are returned (possibly with a wrap-around).
|
| 72 |
+
Otherwise, everything until the end of `data` is returned.
|
| 73 |
+
|
| 74 |
+
Returns:
|
| 75 |
+
samples: data with shape `n_samples x n_channels`
|
| 76 |
+
"""
|
| 77 |
+
n_samples, audio = data
|
| 78 |
+
|
| 79 |
+
# determine whether we have to load the audio or whether it was already loaded
|
| 80 |
+
is_loaded = True if type(audio) is np.ndarray else False
|
| 81 |
+
|
| 82 |
+
# if `length` is not None, then only select subset
|
| 83 |
+
do_wrap_around = False
|
| 84 |
+
if length is not None:
|
| 85 |
+
if start + length > n_samples:
|
| 86 |
+
# we need to wrap around and concatenate `start:` and `:stop`
|
| 87 |
+
do_wrap_around = True
|
| 88 |
+
stop = length - (n_samples - start)
|
| 89 |
+
else:
|
| 90 |
+
# no wrap around as it is inside the array/file boundaries
|
| 91 |
+
stop = start + length
|
| 92 |
+
else:
|
| 93 |
+
stop = None
|
| 94 |
+
|
| 95 |
+
if is_loaded:
|
| 96 |
+
if not do_wrap_around:
|
| 97 |
+
samples = convert_audio2data(audio[start:stop])
|
| 98 |
+
else:
|
| 99 |
+
samples = np.vstack((convert_audio2data(audio[start:]),
|
| 100 |
+
convert_audio2data(audio[:stop])))
|
| 101 |
+
else:
|
| 102 |
+
if not do_wrap_around:
|
| 103 |
+
samples = convert_audio2data(audio(start=start, stop=stop)[0])
|
| 104 |
+
else:
|
| 105 |
+
samples = np.vstack((convert_audio2data(audio(start=start)[0]),
|
| 106 |
+
convert_audio2data(audio(stop=stop)[0])))
|
| 107 |
+
|
| 108 |
+
return samples
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
# Monkey-Patch `Processor` for convenience
|
| 112 |
+
# (a) Allow `None` as blocksize if processor can work on variable-length audio
|
| 113 |
+
def new_init(self, name, parameters, block_size, sample_rate=None, normalize=None, dtype='float32'):
|
| 114 |
+
"""
|
| 115 |
+
Initialize processor.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
self: Reference to object
|
| 119 |
+
name (str): Name of processor.
|
| 120 |
+
parameters (parameter_list): Parameters for this processor.
|
| 121 |
+
block_size (int): Size of blocks for blockwise processing.
|
| 122 |
+
Can also be `None` if full audio can be processed at once.
|
| 123 |
+
sample_rate (int): Sample rate of input audio. Use `None` if effect is independent of this value.
|
| 124 |
+
normalize (str): Defines, whether the processed signal is normalized.
|
| 125 |
+
Possible values are `'rms'`, `'max'` and `None`.
|
| 126 |
+
dtype (str): data type of samples
|
| 127 |
+
|
| 128 |
+
Raises:
|
| 129 |
+
ValueError: If `normalize` is not equal to `'rms'`, `'max'` or `False`.
|
| 130 |
+
"""
|
| 131 |
+
self.name = name
|
| 132 |
+
self.parameters = parameters
|
| 133 |
+
self.block_size = block_size
|
| 134 |
+
self.sample_rate = sample_rate
|
| 135 |
+
self.dtype = dtype
|
| 136 |
+
if normalize not in [None, 'rms', 'max']:
|
| 137 |
+
raise ValueError(f'Unknown value {normalize} for `normalize`. Must be either `rms`, `max` or `None`')
|
| 138 |
+
self.normalize = normalize
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# (b) make code simpler
|
| 142 |
+
def new_update(self, parameter_name):
|
| 143 |
+
"""
|
| 144 |
+
Update processor after randomization of parameters.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
self: Reference to object.
|
| 148 |
+
parameter_name (str): Parameter whose value has changed.
|
| 149 |
+
"""
|
| 150 |
+
pass
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
# (c) representation for nice print
|
| 154 |
+
def new_repr(self):
|
| 155 |
+
"""
|
| 156 |
+
Create human-readable representation.
|
| 157 |
+
|
| 158 |
+
Args:
|
| 159 |
+
self: Reference to object.
|
| 160 |
+
|
| 161 |
+
Returns:
|
| 162 |
+
string representation of object.
|
| 163 |
+
"""
|
| 164 |
+
return f'Processor(name={self.name!r}, parameters={self.parameters!r}'
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
Processor.__init__ = new_init
|
| 168 |
+
Processor.__repr__ = new_repr
|
| 169 |
+
Processor.update = new_update
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
class AugmentationChain:
|
| 173 |
+
"""Basic audio Fx chain which is used for data augmentation."""
|
| 174 |
+
|
| 175 |
+
def __init__(self,
|
| 176 |
+
fxs: Optional[List[Tuple[Union[Processor, 'AugmentationChain'], float]]] = [],
|
| 177 |
+
shuffle: Optional[bool] = False,
|
| 178 |
+
apply_to: Optional[str] = 'both'):
|
| 179 |
+
"""
|
| 180 |
+
Create augmentation chain from the dictionary `fxs`.
|
| 181 |
+
|
| 182 |
+
Args:
|
| 183 |
+
fxs (list of tuples): Each tuple has three elements:
|
| 184 |
+
First tuple element is an instance of `pymc.processor` or `AugmentationChain` that
|
| 185 |
+
we want to use for data augmentation.
|
| 186 |
+
Second element gives probability that effect should be applied.
|
| 187 |
+
shuffle (bool): If `True` then order of Fx are changed whenever chain is applied.
|
| 188 |
+
apply_to (str): Apply the chain to both input and target or one of them only.
|
| 189 |
+
Possible values are `'both'`, `'input'` and `target`.
|
| 190 |
+
|
| 191 |
+
Raises:
|
| 192 |
+
ValueError: If `apply_to` is not equal to `both`, `input` or `target`.
|
| 193 |
+
"""
|
| 194 |
+
self.fxs = fxs
|
| 195 |
+
self.shuffle = shuffle
|
| 196 |
+
if apply_to not in ['both', 'input', 'target']:
|
| 197 |
+
raise ValueError(f'Unknown value {apply_to} for `apply_to`. Must be `both`, `input` or `target`')
|
| 198 |
+
else:
|
| 199 |
+
self.apply_to = apply_to
|
| 200 |
+
|
| 201 |
+
def apply_processor(self, x, processor: Processor):
|
| 202 |
+
"""
|
| 203 |
+
Pass audio in `x` through `processor` and output the respective processed audio.
|
| 204 |
+
|
| 205 |
+
Args:
|
| 206 |
+
x (Numpy array): Input audio of shape `n_samples` x `n_channels`.
|
| 207 |
+
processor (Processor): Audio effect that we want to apply.
|
| 208 |
+
|
| 209 |
+
Returns:
|
| 210 |
+
Numpy array: Processed audio of shape `n_samples` x `n_channels` (same size as `x')
|
| 211 |
+
"""
|
| 212 |
+
n_samples_input = x.shape[0]
|
| 213 |
+
|
| 214 |
+
if processor.block_size is None:
|
| 215 |
+
y = processor.process(x)
|
| 216 |
+
else:
|
| 217 |
+
# make sure that n_samples is a multiple of `processor.block_size`
|
| 218 |
+
if x.shape[0] % processor.block_size != 0:
|
| 219 |
+
n_pad = processor.block_size - x.shape[0] % processor.block_size
|
| 220 |
+
x = np.pad(x, ((0, n_pad), (0, 0)), mode='reflective')
|
| 221 |
+
|
| 222 |
+
y = np.zeros_like(x)
|
| 223 |
+
for idx in range(0, x.shape[0], processor.block_size):
|
| 224 |
+
y[idx:idx+processor.block_size, :] = processor.process(x[idx:idx+processor.block_size, :])
|
| 225 |
+
|
| 226 |
+
if processor.normalize is not None:
|
| 227 |
+
if processor.normalize == 'rms': # normalize output energy such that it is the same as the input energy
|
| 228 |
+
scale = np.sqrt(np.mean(np.square(x)) / np.maximum(1e-7, np.mean(np.square(y))))
|
| 229 |
+
elif processor.normalize == 'max': # normalize output signal by its max. amplitude
|
| 230 |
+
scale = (1 + 1e-7)/(np.max(np.abs(y)) + 1e-7)
|
| 231 |
+
y *= scale
|
| 232 |
+
|
| 233 |
+
# return audio of same length as x
|
| 234 |
+
return y[:n_samples_input, :]
|
| 235 |
+
|
| 236 |
+
def __call__(self, input_x, target_x):
|
| 237 |
+
"""
|
| 238 |
+
Apply augmentation chain to audio in `input_x` and `target_x`.
|
| 239 |
+
|
| 240 |
+
Args:
|
| 241 |
+
input_x (Numpy array): Audio samples of shape `n_samples` x `n_channels`.
|
| 242 |
+
target_x (Numpy array): Audio samples of shape `n_samples` x `n_channels`.
|
| 243 |
+
|
| 244 |
+
Returns:
|
| 245 |
+
input_y (Numpy array): Processed audio of same shape as `input_x` where effects have been applied.
|
| 246 |
+
target_y (Numpy array): Processed audio of same shape as `target_x` where effects have been applied.
|
| 247 |
+
"""
|
| 248 |
+
# randomly shuffle effect order if `self.shuffle` is True
|
| 249 |
+
if self.shuffle:
|
| 250 |
+
shuffle(self.fxs)
|
| 251 |
+
|
| 252 |
+
input_y = input_x
|
| 253 |
+
target_y = target_x
|
| 254 |
+
|
| 255 |
+
# check whether we only need to process once later
|
| 256 |
+
if self.apply_to == 'both' and np.allclose(input_y, target_y):
|
| 257 |
+
is_input_equal_target = True
|
| 258 |
+
else:
|
| 259 |
+
is_input_equal_target = False
|
| 260 |
+
|
| 261 |
+
# apply effects with probabilities given in `self.fxs`
|
| 262 |
+
for fx, p in self.fxs:
|
| 263 |
+
if np.random.rand() < p:
|
| 264 |
+
if isinstance(fx, Processor):
|
| 265 |
+
# randomize all effect parameters (also calls `update()` for each processor)
|
| 266 |
+
fx.randomize()
|
| 267 |
+
# apply processor dependent on `apply_to`
|
| 268 |
+
if self.apply_to == 'both':
|
| 269 |
+
input_y = self.apply_processor(input_y, fx)
|
| 270 |
+
if not is_input_equal_target:
|
| 271 |
+
target_y = self.apply_processor(target_y, fx)
|
| 272 |
+
elif self.apply_to == 'input':
|
| 273 |
+
input_y = self.apply_processor(input_y, fx)
|
| 274 |
+
elif self.apply_to == 'target':
|
| 275 |
+
target_y = self.apply_processor(target_y, fx)
|
| 276 |
+
else:
|
| 277 |
+
# apply effect chain
|
| 278 |
+
if is_input_equal_target:
|
| 279 |
+
target_y = input_y
|
| 280 |
+
input_y, target_y = fx(input_y, target_y)
|
| 281 |
+
# check whether input and target are still the same
|
| 282 |
+
if not fx.apply_to == 'both':
|
| 283 |
+
is_input_equal_target = False
|
| 284 |
+
|
| 285 |
+
if is_input_equal_target:
|
| 286 |
+
target_y = input_y
|
| 287 |
+
|
| 288 |
+
return input_y, target_y
|
| 289 |
+
|
| 290 |
+
def __repr__(self):
|
| 291 |
+
"""
|
| 292 |
+
Human-readable representation.
|
| 293 |
+
|
| 294 |
+
Returns:
|
| 295 |
+
string representation of object.
|
| 296 |
+
"""
|
| 297 |
+
return f'AugmentationChain(fxs={self.fxs!r}, shuffle={self.shuffle!r})'
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DISTORTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 301 |
+
def hard_clip(x, threshold_dB, drive):
|
| 302 |
+
"""
|
| 303 |
+
Hard clip distortion.
|
| 304 |
+
|
| 305 |
+
Args:
|
| 306 |
+
x: input audio
|
| 307 |
+
threshold_dB: threshold
|
| 308 |
+
drive: drive
|
| 309 |
+
|
| 310 |
+
Returns:
|
| 311 |
+
(Numpy array): distorted audio
|
| 312 |
+
"""
|
| 313 |
+
drive_linear = np.power(10., drive / 20.).astype(np.float32)
|
| 314 |
+
threshold_linear = 10. ** (threshold_dB / 20.)
|
| 315 |
+
return np.clip(x * drive_linear, -threshold_linear, threshold_linear)
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def overdrive(x, drive, colour, sample_rate):
|
| 319 |
+
"""
|
| 320 |
+
Overdrive distortion.
|
| 321 |
+
|
| 322 |
+
Args:
|
| 323 |
+
x: input audio
|
| 324 |
+
drive: Controls the amount of distortion (dB).
|
| 325 |
+
colour: Controls the amount of even harmonic content in the output(dB)
|
| 326 |
+
sample_rate: sampling rate
|
| 327 |
+
|
| 328 |
+
Returns:
|
| 329 |
+
(Numpy array): distorted audio
|
| 330 |
+
"""
|
| 331 |
+
scale = np.max(np.abs(x))
|
| 332 |
+
if scale > MAX_SOX_PROCESSING_PEAK:
|
| 333 |
+
clips = True
|
| 334 |
+
x = x * (MAX_SOX_PROCESSING_PEAK / scale)
|
| 335 |
+
else:
|
| 336 |
+
clips = False
|
| 337 |
+
|
| 338 |
+
tfm = sox.Transformer()
|
| 339 |
+
tfm.overdrive(gain_db=drive, colour=colour)
|
| 340 |
+
y = tfm.build_array(input_array=x, sample_rate_in=sample_rate).astype(np.float32)
|
| 341 |
+
|
| 342 |
+
if clips:
|
| 343 |
+
y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
|
| 344 |
+
return y
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def hyperbolic_tangent(x, drive):
|
| 348 |
+
"""
|
| 349 |
+
Hyperbolic Tanh distortion.
|
| 350 |
+
|
| 351 |
+
Args:
|
| 352 |
+
x: input audio
|
| 353 |
+
drive: drive
|
| 354 |
+
|
| 355 |
+
Returns:
|
| 356 |
+
(Numpy array): distorted audio
|
| 357 |
+
"""
|
| 358 |
+
drive_linear = np.power(10., drive / 20.).astype(np.float32)
|
| 359 |
+
return np.tanh(2. * x * drive_linear)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def soft_sine(x, drive):
|
| 363 |
+
"""
|
| 364 |
+
Soft sine distortion.
|
| 365 |
+
|
| 366 |
+
Args:
|
| 367 |
+
x: input audio
|
| 368 |
+
drive: drive
|
| 369 |
+
|
| 370 |
+
Returns:
|
| 371 |
+
(Numpy array): distorted audio
|
| 372 |
+
"""
|
| 373 |
+
drive_linear = np.power(10., drive / 20.).astype(np.float32)
|
| 374 |
+
y = np.clip(x * drive_linear, -np.pi/4.0, np.pi/4.0)
|
| 375 |
+
return np.sin(2. * y)
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def bit_crusher(x, bits):
|
| 379 |
+
"""
|
| 380 |
+
Bit crusher distortion.
|
| 381 |
+
|
| 382 |
+
Args:
|
| 383 |
+
x: input audio
|
| 384 |
+
bits: bits
|
| 385 |
+
|
| 386 |
+
Returns:
|
| 387 |
+
(Numpy array): distorted audio
|
| 388 |
+
"""
|
| 389 |
+
return np.rint(x * (2 ** bits)) / (2 ** bits)
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
class Distortion(Processor):
|
| 393 |
+
"""
|
| 394 |
+
Distortion processor.
|
| 395 |
+
|
| 396 |
+
Processor parameters:
|
| 397 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 398 |
+
mode (str): Currently supports the following five modes: hard_clip, waveshaper, soft_sine, tanh, bit_crusher.
|
| 399 |
+
Each mode has different parameters such as threshold, factor, or bits.
|
| 400 |
+
threshold (float): threshold
|
| 401 |
+
drive (float): drive
|
| 402 |
+
factor (float): factor
|
| 403 |
+
limit_range (float): limit range
|
| 404 |
+
bits (int): bits
|
| 405 |
+
"""
|
| 406 |
+
|
| 407 |
+
def __init__(self, sample_rates, name='Distortion', parameters=None, **kwargs):
|
| 408 |
+
"""
|
| 409 |
+
Initialize processor.
|
| 410 |
+
|
| 411 |
+
Args:
|
| 412 |
+
sample_rates (list of ints): sample rates of audio.
|
| 413 |
+
name (str): Name of processor.
|
| 414 |
+
parameters (parameter_list): Parameters for this processor.
|
| 415 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 416 |
+
"""
|
| 417 |
+
super().__init__(name, None, block_size=None, **kwargs)
|
| 418 |
+
if not parameters:
|
| 419 |
+
self.parameters = ParameterList()
|
| 420 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 421 |
+
self.parameters.add(Parameter('mode', 'hard_clip', 'string',
|
| 422 |
+
options=['hard_clip',
|
| 423 |
+
'overdrive',
|
| 424 |
+
'soft_sine',
|
| 425 |
+
'tanh',
|
| 426 |
+
'bit_crusher']))
|
| 427 |
+
self.parameters.add(Parameter('threshold', 0.0, 'float',
|
| 428 |
+
units='dB', maximum=0.0, minimum=-20.0))
|
| 429 |
+
self.parameters.add(Parameter('drive', 0.0, 'float',
|
| 430 |
+
units='dB', maximum=20.0, minimum=0.0))
|
| 431 |
+
self.parameters.add(Parameter('colour', 20.0, 'float',
|
| 432 |
+
maximum=100.0, minimum=0.0))
|
| 433 |
+
self.parameters.add(Parameter('bits', 12, 'int',
|
| 434 |
+
maximum=12, minimum=8))
|
| 435 |
+
else:
|
| 436 |
+
self.parameters = parameters
|
| 437 |
+
|
| 438 |
+
def process(self, x):
|
| 439 |
+
"""
|
| 440 |
+
Process audio.
|
| 441 |
+
|
| 442 |
+
Args:
|
| 443 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 444 |
+
|
| 445 |
+
Returns:
|
| 446 |
+
(Numpy array): distorted audio of size `n_samples x n_channels`.
|
| 447 |
+
"""
|
| 448 |
+
if self.parameters.mode.value == 'hard_clip':
|
| 449 |
+
y = hard_clip(x, self.parameters.threshold.value, self.parameters.drive.value)
|
| 450 |
+
elif self.parameters.mode.value == 'overdrive':
|
| 451 |
+
y = overdrive(x, self.parameters.drive.value,
|
| 452 |
+
self.parameters.colour.value, self.parameters.sample_rate.value)
|
| 453 |
+
elif self.parameters.mode.value == 'soft_sine':
|
| 454 |
+
y = soft_sine(x, self.parameters.drive.value)
|
| 455 |
+
elif self.parameters.mode.value == 'tanh':
|
| 456 |
+
y = hyperbolic_tangent(x, self.parameters.drive.value)
|
| 457 |
+
elif self.parameters.mode.value == 'bit_crusher':
|
| 458 |
+
y = bit_crusher(x, self.parameters.bits.value)
|
| 459 |
+
|
| 460 |
+
return y
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EQUALISER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 464 |
+
class Equaliser(Processor):
|
| 465 |
+
"""
|
| 466 |
+
Five band parametric equaliser (two shelves and three central bands).
|
| 467 |
+
|
| 468 |
+
All gains are set in dB values and range from `MIN_GAIN` dB to `MAX_GAIN` dB.
|
| 469 |
+
This processor is implemented as cascade of five biquad IIR filters
|
| 470 |
+
that are implemented using the infamous cookbook formulae from RBJ.
|
| 471 |
+
|
| 472 |
+
Processor parameters:
|
| 473 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 474 |
+
low_shelf_gain (float), low_shelf_freq (float)
|
| 475 |
+
first_band_gain (float), first_band_freq (float), first_band_q (float)
|
| 476 |
+
second_band_gain (float), second_band_freq (float), second_band_q (float)
|
| 477 |
+
third_band_gain (float), third_band_freq (float), third_band_q (float)
|
| 478 |
+
|
| 479 |
+
original from https://github.com/csteinmetz1/pymixconsole/blob/master/pymixconsole/processors/equaliser.py
|
| 480 |
+
"""
|
| 481 |
+
|
| 482 |
+
def __init__(self, n_channels, sample_rates, gain_range=(-15.0, 15.0), q_range=(0.1, 2.0), hard_clip=False,
|
| 483 |
+
name='Equaliser', parameters=None, **kwargs):
|
| 484 |
+
"""
|
| 485 |
+
Initialize processor.
|
| 486 |
+
|
| 487 |
+
Args:
|
| 488 |
+
n_channels (int): Number of audio channels.
|
| 489 |
+
sample_rates (list of ints): Sample rates of audio.
|
| 490 |
+
gain_range (tuple of floats): minimum and maximum gain that can be used.
|
| 491 |
+
q_range (tuple of floats): minimum and maximum q value.
|
| 492 |
+
hard_clip (bool): Whether we clip to [-1, 1.] after processing.
|
| 493 |
+
name (str): Name of processor.
|
| 494 |
+
parameters (parameter_list): Parameters for this processor.
|
| 495 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 496 |
+
"""
|
| 497 |
+
super().__init__(name, parameters=parameters, block_size=None, **kwargs)
|
| 498 |
+
|
| 499 |
+
self.n_channels = n_channels
|
| 500 |
+
|
| 501 |
+
MIN_GAIN, MAX_GAIN = gain_range
|
| 502 |
+
MIN_Q, MAX_Q = q_range
|
| 503 |
+
|
| 504 |
+
if not parameters:
|
| 505 |
+
self.parameters = ParameterList()
|
| 506 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 507 |
+
# low shelf parameters -------
|
| 508 |
+
self.parameters.add(Parameter('low_shelf_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
|
| 509 |
+
self.parameters.add(Parameter('low_shelf_freq', 80.0, 'float', minimum=30.0, maximum=200.0))
|
| 510 |
+
# first band parameters ------
|
| 511 |
+
self.parameters.add(Parameter('first_band_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
|
| 512 |
+
self.parameters.add(Parameter('first_band_freq', 400.0, 'float', minimum=200.0, maximum=1000.0))
|
| 513 |
+
self.parameters.add(Parameter('first_band_q', 0.7, 'float', minimum=MIN_Q, maximum=MAX_Q))
|
| 514 |
+
# second band parameters -----
|
| 515 |
+
self.parameters.add(Parameter('second_band_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
|
| 516 |
+
self.parameters.add(Parameter('second_band_freq', 2000.0, 'float', minimum=1000.0, maximum=3000.0))
|
| 517 |
+
self.parameters.add(Parameter('second_band_q', 0.7, 'float', minimum=MIN_Q, maximum=MAX_Q))
|
| 518 |
+
# third band parameters ------
|
| 519 |
+
self.parameters.add(Parameter('third_band_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
|
| 520 |
+
self.parameters.add(Parameter('third_band_freq', 4000.0, 'float', minimum=3000.0, maximum=8000.0))
|
| 521 |
+
self.parameters.add(Parameter('third_band_q', 0.7, 'float', minimum=MIN_Q, maximum=MAX_Q))
|
| 522 |
+
# high shelf parameters ------
|
| 523 |
+
self.parameters.add(Parameter('high_shelf_gain', 0.0, 'float', minimum=MIN_GAIN, maximum=MAX_GAIN))
|
| 524 |
+
self.parameters.add(Parameter('high_shelf_freq', 8000.0, 'float', minimum=5000.0, maximum=10000.0))
|
| 525 |
+
else:
|
| 526 |
+
self.parameters = parameters
|
| 527 |
+
|
| 528 |
+
self.bands = ['low_shelf', 'first_band', 'second_band', 'third_band', 'high_shelf']
|
| 529 |
+
self.filters = self.setup_filters()
|
| 530 |
+
self.hard_clip = hard_clip
|
| 531 |
+
|
| 532 |
+
def setup_filters(self):
|
| 533 |
+
"""
|
| 534 |
+
Create IIR filters.
|
| 535 |
+
|
| 536 |
+
Returns:
|
| 537 |
+
IIR filters
|
| 538 |
+
"""
|
| 539 |
+
filters = {}
|
| 540 |
+
|
| 541 |
+
for band in self.bands:
|
| 542 |
+
|
| 543 |
+
G = getattr(self.parameters, band + '_gain').value
|
| 544 |
+
fc = getattr(self.parameters, band + '_freq').value
|
| 545 |
+
rate = self.parameters.sample_rate.value
|
| 546 |
+
|
| 547 |
+
if band in ['low_shelf', 'high_shelf']:
|
| 548 |
+
Q = 0.707
|
| 549 |
+
filter_type = band
|
| 550 |
+
else:
|
| 551 |
+
Q = getattr(self.parameters, band + '_q').value
|
| 552 |
+
filter_type = 'peaking'
|
| 553 |
+
|
| 554 |
+
filters[band] = pymc.components.iirfilter.IIRfilter(G, Q, fc, rate, filter_type, n_channels=self.n_channels)
|
| 555 |
+
|
| 556 |
+
return filters
|
| 557 |
+
|
| 558 |
+
def update_filter(self, band):
|
| 559 |
+
"""
|
| 560 |
+
Update filters.
|
| 561 |
+
|
| 562 |
+
Args:
|
| 563 |
+
band (str): Band that should be updated.
|
| 564 |
+
"""
|
| 565 |
+
self.filters[band].G = getattr(self.parameters, band + '_gain').value
|
| 566 |
+
self.filters[band].fc = getattr(self.parameters, band + '_freq').value
|
| 567 |
+
self.filters[band].rate = self.parameters.sample_rate.value
|
| 568 |
+
|
| 569 |
+
if band in ['first_band', 'second_band', 'third_band']:
|
| 570 |
+
self.filters[band].Q = getattr(self.parameters, band + '_q').value
|
| 571 |
+
|
| 572 |
+
def update(self, parameter_name=None):
|
| 573 |
+
"""
|
| 574 |
+
Update processor after randomization of parameters.
|
| 575 |
+
|
| 576 |
+
Args:
|
| 577 |
+
parameter_name (str): Parameter whose value has changed.
|
| 578 |
+
"""
|
| 579 |
+
if parameter_name is not None:
|
| 580 |
+
bands = ['_'.join(parameter_name.split('_')[:2])]
|
| 581 |
+
else:
|
| 582 |
+
bands = self.bands
|
| 583 |
+
|
| 584 |
+
for band in bands:
|
| 585 |
+
self.update_filter(band)
|
| 586 |
+
|
| 587 |
+
def process(self, x):
|
| 588 |
+
"""
|
| 589 |
+
Process audio.
|
| 590 |
+
|
| 591 |
+
Args:
|
| 592 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 593 |
+
|
| 594 |
+
Returns:
|
| 595 |
+
(Numpy array): equalized audio of size `n_samples x n_channels`.
|
| 596 |
+
"""
|
| 597 |
+
for _band, iirfilter in self.filters.items():
|
| 598 |
+
iirfilter.reset_state()
|
| 599 |
+
x = iirfilter.apply_filter(x)
|
| 600 |
+
|
| 601 |
+
if self.hard_clip:
|
| 602 |
+
x = np.clip(x, -1.0, 1.0)
|
| 603 |
+
|
| 604 |
+
# make sure that we have float32 as IIR filtering returns float64
|
| 605 |
+
x = x.astype(np.float32)
|
| 606 |
+
|
| 607 |
+
# make sure that we have two dimensions (if `n_channels == 1`)
|
| 608 |
+
if x.ndim == 1:
|
| 609 |
+
x = x[:, np.newaxis]
|
| 610 |
+
|
| 611 |
+
return x
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% COMPRESSOR %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 615 |
+
@jit(nopython=True)
|
| 616 |
+
def compressor_process(x, threshold, attack_time, release_time, ratio, makeup_gain, sample_rate):
|
| 617 |
+
"""
|
| 618 |
+
Apply compressor.
|
| 619 |
+
|
| 620 |
+
Args:
|
| 621 |
+
x (Numpy array): audio data.
|
| 622 |
+
threshold: threshold in dB.
|
| 623 |
+
attack_time: attack_time in ms.
|
| 624 |
+
release_time: release_time in ms.
|
| 625 |
+
ratio: ratio.
|
| 626 |
+
makeup_gain: makeup_gain.
|
| 627 |
+
sample_rate: sample rate.
|
| 628 |
+
|
| 629 |
+
Returns:
|
| 630 |
+
compressed audio.
|
| 631 |
+
"""
|
| 632 |
+
M = x.shape[0]
|
| 633 |
+
x_g = np.zeros(M)
|
| 634 |
+
x_l = np.zeros(M)
|
| 635 |
+
y_g = np.zeros(M)
|
| 636 |
+
y_l = np.zeros(M)
|
| 637 |
+
c = np.zeros(M)
|
| 638 |
+
yL_prev = 0.
|
| 639 |
+
|
| 640 |
+
alpha_attack = np.exp(-1/(0.001 * sample_rate * attack_time))
|
| 641 |
+
alpha_release = np.exp(-1/(0.001 * sample_rate * release_time))
|
| 642 |
+
|
| 643 |
+
for i in np.arange(M):
|
| 644 |
+
if np.abs(x[i]) < 0.000001:
|
| 645 |
+
x_g[i] = -120.0
|
| 646 |
+
else:
|
| 647 |
+
x_g[i] = 20 * np.log10(np.abs(x[i]))
|
| 648 |
+
|
| 649 |
+
if x_g[i] >= threshold:
|
| 650 |
+
y_g[i] = threshold + (x_g[i] - threshold) / ratio
|
| 651 |
+
else:
|
| 652 |
+
y_g[i] = x_g[i]
|
| 653 |
+
|
| 654 |
+
x_l[i] = x_g[i] - y_g[i]
|
| 655 |
+
|
| 656 |
+
if x_l[i] > yL_prev:
|
| 657 |
+
y_l[i] = alpha_attack * yL_prev + (1 - alpha_attack) * x_l[i]
|
| 658 |
+
else:
|
| 659 |
+
y_l[i] = alpha_release * yL_prev + (1 - alpha_release) * x_l[i]
|
| 660 |
+
|
| 661 |
+
c[i] = np.power(10.0, (makeup_gain - y_l[i]) / 20.0)
|
| 662 |
+
yL_prev = y_l[i]
|
| 663 |
+
|
| 664 |
+
y = x * c
|
| 665 |
+
|
| 666 |
+
return y
|
| 667 |
+
|
| 668 |
+
|
| 669 |
+
class Compressor(Processor):
|
| 670 |
+
"""
|
| 671 |
+
Single band stereo dynamic range compressor.
|
| 672 |
+
|
| 673 |
+
Processor parameters:
|
| 674 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 675 |
+
threshold (float)
|
| 676 |
+
attack_time (float)
|
| 677 |
+
release_time (float)
|
| 678 |
+
ratio (float)
|
| 679 |
+
makeup_gain (float)
|
| 680 |
+
"""
|
| 681 |
+
|
| 682 |
+
def __init__(self, sample_rates, name='Compressor', parameters=None, **kwargs):
|
| 683 |
+
"""
|
| 684 |
+
Initialize processor.
|
| 685 |
+
|
| 686 |
+
Args:
|
| 687 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 688 |
+
name (str): Name of processor.
|
| 689 |
+
parameters (parameter_list): Parameters for this processor.
|
| 690 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 691 |
+
"""
|
| 692 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 693 |
+
|
| 694 |
+
if not parameters:
|
| 695 |
+
self.parameters = ParameterList()
|
| 696 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 697 |
+
self.parameters.add(Parameter('threshold', 0.0, 'float', units='dB', minimum=-40.0, maximum=0.0))
|
| 698 |
+
self.parameters.add(Parameter('attack_time', 2.0, 'float', units='ms', minimum=0.03, maximum=30.0))
|
| 699 |
+
self.parameters.add(Parameter('release_time', 50.0, 'float', units='ms', minimum=50.0, maximum=100.0))
|
| 700 |
+
self.parameters.add(Parameter('ratio', 2.0, 'float', minimum=2.0, maximum=10.0))
|
| 701 |
+
self.parameters.add(Parameter('makeup_gain', 0.0, 'float', units='dB', minimum=-3.0, maximum=6.0))
|
| 702 |
+
else:
|
| 703 |
+
self.parameters = parameters
|
| 704 |
+
|
| 705 |
+
def process(self, x):
|
| 706 |
+
"""
|
| 707 |
+
Process audio.
|
| 708 |
+
|
| 709 |
+
Args:
|
| 710 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 711 |
+
|
| 712 |
+
Returns:
|
| 713 |
+
(Numpy array): compressed audio of size `n_samples x n_channels`.
|
| 714 |
+
"""
|
| 715 |
+
if not self.parameters.threshold.value == 0.0:
|
| 716 |
+
y = np.zeros_like(x)
|
| 717 |
+
|
| 718 |
+
for ch in range(x.shape[1]):
|
| 719 |
+
y[:, ch] = compressor_process(x[:, ch],
|
| 720 |
+
self.parameters.threshold.value,
|
| 721 |
+
self.parameters.attack_time.value,
|
| 722 |
+
self.parameters.release_time.value,
|
| 723 |
+
self.parameters.ratio.value,
|
| 724 |
+
self.parameters.makeup_gain.value,
|
| 725 |
+
self.parameters.sample_rate.value)
|
| 726 |
+
else:
|
| 727 |
+
y = x
|
| 728 |
+
|
| 729 |
+
return y
|
| 730 |
+
|
| 731 |
+
|
| 732 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%% CONVOLUTIONAL REVERB %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 733 |
+
class ConvolutionalReverb(Processor):
|
| 734 |
+
"""
|
| 735 |
+
Convolutional Reverb.
|
| 736 |
+
|
| 737 |
+
Important: Due to convolving the audio sequence with some impulse response, we should ignore the
|
| 738 |
+
first/last samples of the augmented audio sequence using `config['AUGMENTER_PADDING']`.
|
| 739 |
+
|
| 740 |
+
Processor parameters:
|
| 741 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 742 |
+
wet_dry (float): Wet/dry ratio.
|
| 743 |
+
decay (float): Applies a fade out to the impulse response.
|
| 744 |
+
pre_delay (float): Value in ms. Shifts the IR in time and allows.
|
| 745 |
+
A positive value produces a traditional delay between the dry signal and the wet.
|
| 746 |
+
A negative delay is, in reality, zero delay, but effectively trims off the start of IR,
|
| 747 |
+
so the reverb response begins at a point further in.
|
| 748 |
+
"""
|
| 749 |
+
|
| 750 |
+
def __init__(self, impulse_responses, sample_rates, name='ConvolutionalReverb', parameters=None, **kwargs):
|
| 751 |
+
"""
|
| 752 |
+
Initialize processor.
|
| 753 |
+
|
| 754 |
+
Args:
|
| 755 |
+
impulse_responses (list): List with impulse responses created by `common_dataprocessing.create_dataset`
|
| 756 |
+
sample_rates (list of ints): Sample rates that we should assume (used for fade-out computation)
|
| 757 |
+
name (str): Name of processor.
|
| 758 |
+
parameters (parameter_list): Parameters for this processor.
|
| 759 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 760 |
+
|
| 761 |
+
Raises:
|
| 762 |
+
ValueError: if no impulse responses are provided.
|
| 763 |
+
"""
|
| 764 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 765 |
+
|
| 766 |
+
if impulse_responses is None:
|
| 767 |
+
raise ValueError('List of impulse responses must be provided for ConvolutionalReverb processor.')
|
| 768 |
+
self.impulse_responses = impulse_responses
|
| 769 |
+
|
| 770 |
+
if not parameters:
|
| 771 |
+
self.parameters = ParameterList()
|
| 772 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 773 |
+
self.parameters.add(Parameter('index', 0, 'int', minimum=0, maximum=len(impulse_responses)))
|
| 774 |
+
self.parameters.add(Parameter('wet_dry', 1.0, 'float', minimum=0.1, maximum=1.0))
|
| 775 |
+
self.parameters.add(Parameter('decay', 1.0, 'float', minimum=0.1, maximum=1.0))
|
| 776 |
+
self.parameters.add(Parameter('pre_delay', 0, 'int', units='ms', minimum=-100, maximum=100))
|
| 777 |
+
else:
|
| 778 |
+
self.parameters = parameters
|
| 779 |
+
|
| 780 |
+
def update(self, parameter_name=None):
|
| 781 |
+
"""
|
| 782 |
+
Update processor after randomization of parameters.
|
| 783 |
+
|
| 784 |
+
Args:
|
| 785 |
+
parameter_name (str): Parameter whose value has changed.
|
| 786 |
+
"""
|
| 787 |
+
# copy IR from current index (to avoid modifying it in-place)
|
| 788 |
+
self.h = np.copy(sample_data(self.impulse_responses.loc[self.parameters.index.value]['impulse_response']))
|
| 789 |
+
|
| 790 |
+
# fade out the impulse based on the decay setting (starting from peak value) - constant 20ms fade-out
|
| 791 |
+
if self.parameters.decay.value < 1.:
|
| 792 |
+
idx_peak = np.argmax(np.max(np.abs(self.h), axis=1), axis=0)
|
| 793 |
+
fstart = np.minimum(self.h.shape[0],
|
| 794 |
+
idx_peak + int(self.parameters.decay.value * (self.h.shape[0] - idx_peak)))
|
| 795 |
+
fstop = np.minimum(self.h.shape[0], fstart + int(0.020*self.parameters.sample_rate.value))
|
| 796 |
+
flen = fstop - fstart
|
| 797 |
+
|
| 798 |
+
fade = np.arange(1, flen+1, dtype=self.dtype)/flen
|
| 799 |
+
fade = np.power(0.1, fade * 5)
|
| 800 |
+
self.h[fstart:fstop, :] *= fade[:, np.newaxis]
|
| 801 |
+
self.h = self.h[:fstop]
|
| 802 |
+
|
| 803 |
+
def process(self, x):
|
| 804 |
+
"""
|
| 805 |
+
Process audio.
|
| 806 |
+
|
| 807 |
+
Args:
|
| 808 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 809 |
+
|
| 810 |
+
Returns:
|
| 811 |
+
(Numpy array): reverbed audio of size `n_samples x n_channels`.
|
| 812 |
+
"""
|
| 813 |
+
# reshape IR to the correct size
|
| 814 |
+
n_channels = x.shape[1]
|
| 815 |
+
if self.h.shape[1] == 1 and n_channels > 1:
|
| 816 |
+
self.h = np.hstack([self.h] * n_channels) # repeat mono IR for multi-channel input
|
| 817 |
+
if self.h.shape[1] > 1 and n_channels == 1:
|
| 818 |
+
self.h = self.h[:, np.random.randint(self.h.shape[1]), np.newaxis] # randomly choose one IR channel
|
| 819 |
+
|
| 820 |
+
if self.parameters.wet_dry.value == 0.0:
|
| 821 |
+
return x
|
| 822 |
+
else:
|
| 823 |
+
# perform convolution to get wet signal
|
| 824 |
+
y = oaconvolve(x, self.h, mode='full', axes=0)
|
| 825 |
+
|
| 826 |
+
# cut out wet signal (compensating for the delay that the IR is introducing + predelay)
|
| 827 |
+
idx = np.argmax(np.max(np.abs(self.h), axis=1), axis=0)
|
| 828 |
+
idx += int(0.001 * self.parameters.pre_delay.value * self.parameters.sample_rate.value)
|
| 829 |
+
|
| 830 |
+
idx = np.clip(idx, 0, self.h.shape[0]-1)
|
| 831 |
+
|
| 832 |
+
y = y[idx:idx+x.shape[0], :]
|
| 833 |
+
|
| 834 |
+
# return weighted sum of dry and wet signal
|
| 835 |
+
return (1.0 - self.parameters.wet_dry.value) * x + self.parameters.wet_dry.value * y
|
| 836 |
+
|
| 837 |
+
|
| 838 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%% HAAS EFFECT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 839 |
+
def haas_process(x, delay, feedback, wet_channel):
|
| 840 |
+
"""
|
| 841 |
+
Add Haas effect to audio.
|
| 842 |
+
|
| 843 |
+
Args:
|
| 844 |
+
x (Numpy array): input audio.
|
| 845 |
+
delay: Delay that we apply to one of the channels (in samples).
|
| 846 |
+
feedback: Feedback value.
|
| 847 |
+
wet_channel: Which channel we process (`left` or `right`).
|
| 848 |
+
|
| 849 |
+
Returns:
|
| 850 |
+
(Numpy array): Audio with Haas effect.
|
| 851 |
+
"""
|
| 852 |
+
y = np.copy(x)
|
| 853 |
+
if wet_channel == 'left':
|
| 854 |
+
y[:, 0] += feedback * np.roll(x[:, 0], delay)
|
| 855 |
+
elif wet_channel == 'right':
|
| 856 |
+
y[:, 1] += feedback * np.roll(x[:, 1], delay)
|
| 857 |
+
|
| 858 |
+
return y
|
| 859 |
+
|
| 860 |
+
|
| 861 |
+
class Haas(Processor):
|
| 862 |
+
"""
|
| 863 |
+
Haas Effect Processor.
|
| 864 |
+
|
| 865 |
+
Randomly selects one channel and applies a short delay to it.
|
| 866 |
+
|
| 867 |
+
Important: This audio effect uses `np.roll` to perform the shift of one channel. Hence, you should use
|
| 868 |
+
`config['AUGMENTER_PADDING']` to ignore the first samples of the augmented audio sequence.
|
| 869 |
+
|
| 870 |
+
Processor parameters:
|
| 871 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 872 |
+
delay (float)
|
| 873 |
+
feedback (float)
|
| 874 |
+
wet_channel (string)
|
| 875 |
+
"""
|
| 876 |
+
|
| 877 |
+
def __init__(self, sample_rates, delay_range=(-0.040, 0.040), name='Haas', parameters=None, **kwargs):
|
| 878 |
+
"""
|
| 879 |
+
Initialize processor.
|
| 880 |
+
|
| 881 |
+
Args:
|
| 882 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 883 |
+
delay_range (tuple of floats): minimum/maximum delay in milliseconds for Haas effect.
|
| 884 |
+
name (str): Name of processor.
|
| 885 |
+
parameters (parameter_list): Parameters for this processor.
|
| 886 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 887 |
+
"""
|
| 888 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 889 |
+
|
| 890 |
+
if not parameters:
|
| 891 |
+
self.parameters = ParameterList()
|
| 892 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 893 |
+
self.parameters.add(Parameter('delay', delay_range[1], 'float', units='ms',
|
| 894 |
+
minimum=delay_range[0], maximum=delay_range[1]))
|
| 895 |
+
self.parameters.add(Parameter('feedback', 0.35, 'float', minimum=0.33, maximum=0.66))
|
| 896 |
+
self.parameters.add(Parameter('wet_channel', 'left', 'string', options=['left', 'right']))
|
| 897 |
+
else:
|
| 898 |
+
self.parameters = parameters
|
| 899 |
+
|
| 900 |
+
def process(self, x):
|
| 901 |
+
"""
|
| 902 |
+
Process audio.
|
| 903 |
+
|
| 904 |
+
Args:
|
| 905 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 906 |
+
|
| 907 |
+
Returns:
|
| 908 |
+
(Numpy array): audio with Haas effect of size `n_samples x n_channels`.
|
| 909 |
+
"""
|
| 910 |
+
assert x.shape[1] == 1 or x.shape[1] == 2, 'Haas effect only works with monaural or stereo audio.'
|
| 911 |
+
|
| 912 |
+
if x.shape[1] < 2:
|
| 913 |
+
x = np.repeat(x, 2, axis=1)
|
| 914 |
+
|
| 915 |
+
y = haas_process(x, int(self.parameters.delay.value * self.parameters.sample_rate.value),
|
| 916 |
+
self.parameters.feedback.value, self.parameters.wet_channel.value)
|
| 917 |
+
|
| 918 |
+
return y
|
| 919 |
+
|
| 920 |
+
|
| 921 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PANNER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 922 |
+
class Panner(Processor):
|
| 923 |
+
"""
|
| 924 |
+
Simple stereo panner (adjusting amplitude and delay).
|
| 925 |
+
|
| 926 |
+
If input is mono, output is stereo.
|
| 927 |
+
Original edited from https://github.com/csteinmetz1/pymixconsole/blob/master/pymixconsole/processors/panner.py
|
| 928 |
+
|
| 929 |
+
Important: This audio effect uses `np.roll` to perform the shift of one channel. Hence, you should use
|
| 930 |
+
`config['AUGMENTER_PADDING']` to ignore the first samples of the augmented audio sequence.
|
| 931 |
+
|
| 932 |
+
Processor parameters:
|
| 933 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 934 |
+
pan (float): Panning angle. Can take values in [0, 1] where `0` corresponds to fully panned to left
|
| 935 |
+
and `1` corresponds to fully panned to the right.
|
| 936 |
+
pan_law (str): Pan law to be used for amplitude panning. Can be '-4.5dB', 'linear' or 'constant_power'.
|
| 937 |
+
pan_mode (str): Scheme that is used for panning. Can be 'amplitude', 'delay' or 'both'.
|
| 938 |
+
pan_maxdelay (float): Maximum delay if we pan a source fully to the left/right.
|
| 939 |
+
For example `2. / 343.` is the maximum delay if the microphones are 2 meters apart.
|
| 940 |
+
"""
|
| 941 |
+
|
| 942 |
+
def __init__(self, sample_rates, maxdelay_range=(0, 2. / 343.0), name='Panner', parameters=None, **kwargs):
|
| 943 |
+
"""
|
| 944 |
+
Initialize processor.
|
| 945 |
+
|
| 946 |
+
Args:
|
| 947 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 948 |
+
maxdelay_range (tuple of floats): minimum/maximum delay for panning effect. `2. / 343.` corresponds to
|
| 949 |
+
the maximum delay if we have a microphone array where the microphones are 2 meters apart.
|
| 950 |
+
name (str): Name of processor.
|
| 951 |
+
parameters (parameter_list): Parameters for this processor.
|
| 952 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 953 |
+
"""
|
| 954 |
+
# default processor class constructor
|
| 955 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 956 |
+
|
| 957 |
+
if not parameters:
|
| 958 |
+
self.parameters = ParameterList()
|
| 959 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 960 |
+
self.parameters.add(Parameter('pan', 0.5, 'float', minimum=0.1, maximum=0.9))
|
| 961 |
+
self.parameters.add(Parameter('pan_law', '-4.5dB', 'string',
|
| 962 |
+
options=['-4.5dB', 'linear', 'constant_power']))
|
| 963 |
+
self.parameters.add(Parameter('pan_mode', 'amplitude', 'string',
|
| 964 |
+
options=['amplitude', 'delay', 'both']))
|
| 965 |
+
self.parameters.add(Parameter('pan_maxdelay', (maxdelay_range[0] + maxdelay_range[1]) / 2., 'float',
|
| 966 |
+
units='ms', minimum=maxdelay_range[0], maximum=maxdelay_range[1]))
|
| 967 |
+
else:
|
| 968 |
+
self.parameters = parameters
|
| 969 |
+
|
| 970 |
+
# setup the coefficents based on default params
|
| 971 |
+
self.update()
|
| 972 |
+
|
| 973 |
+
def _calculate_pan_coefficents(self):
|
| 974 |
+
"""
|
| 975 |
+
Calculate panning coefficients from the chosen pan law.
|
| 976 |
+
|
| 977 |
+
Based on the set pan law determine the gain value
|
| 978 |
+
to apply for the left and right channel to achieve panning effect.
|
| 979 |
+
This operates on the assumption that the input channel is mono.
|
| 980 |
+
The output data will be stereo at the moment, but could be expanded
|
| 981 |
+
to a higher channel count format.
|
| 982 |
+
The panning value is in the range [0, 1], where
|
| 983 |
+
0 means the signal is panned completely to the left, and
|
| 984 |
+
1 means the signal is apanned copletely to the right.
|
| 985 |
+
|
| 986 |
+
Raises:
|
| 987 |
+
ValueError: `self.parameters.pan_law` is not supported.
|
| 988 |
+
"""
|
| 989 |
+
self.gains = np.zeros(2, dtype=self.dtype)
|
| 990 |
+
|
| 991 |
+
# first scale the linear [0, 1] to [0, pi/2]
|
| 992 |
+
theta = self.parameters.pan.value * (np.pi/2)
|
| 993 |
+
|
| 994 |
+
if self.parameters.pan_law.value == 'linear':
|
| 995 |
+
self.gains[0] = ((np.pi/2) - theta) * (2/np.pi)
|
| 996 |
+
self.gains[1] = theta * (2/np.pi)
|
| 997 |
+
elif self.parameters.pan_law.value == 'constant_power':
|
| 998 |
+
self.gains[0] = np.cos(theta)
|
| 999 |
+
self.gains[1] = np.sin(theta)
|
| 1000 |
+
elif self.parameters.pan_law.value == '-4.5dB':
|
| 1001 |
+
self.gains[0] = np.sqrt(((np.pi/2) - theta) * (2/np.pi) * np.cos(theta))
|
| 1002 |
+
self.gains[1] = np.sqrt(theta * (2/np.pi) * np.sin(theta))
|
| 1003 |
+
else:
|
| 1004 |
+
raise ValueError(f'Invalid pan_law {self.parameters.pan_law.value}.')
|
| 1005 |
+
|
| 1006 |
+
def _calculate_pan_delay(self):
|
| 1007 |
+
"""Calculate delay for the chosen pan angle."""
|
| 1008 |
+
self.shifts = np.zeros(2, dtype=np.int32)
|
| 1009 |
+
|
| 1010 |
+
# compute overall shift that we need between the two channels
|
| 1011 |
+
pan_maxdelay_samples = self.parameters.pan_maxdelay.value * self.parameters.sample_rate.value
|
| 1012 |
+
shift = 2 * int(pan_maxdelay_samples * np.abs(self.parameters.pan.value - 0.5))
|
| 1013 |
+
|
| 1014 |
+
if self.parameters.pan.value < 0.5:
|
| 1015 |
+
# panning to the left
|
| 1016 |
+
self.shifts[0] = -shift // 2
|
| 1017 |
+
self.shifts[1] = shift - shift // 2
|
| 1018 |
+
else:
|
| 1019 |
+
self.shifts[0] = shift - shift // 2
|
| 1020 |
+
self.shifts[1] = -shift // 2
|
| 1021 |
+
|
| 1022 |
+
def process(self, x):
|
| 1023 |
+
"""
|
| 1024 |
+
Process audio.
|
| 1025 |
+
|
| 1026 |
+
Args:
|
| 1027 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1028 |
+
|
| 1029 |
+
Returns:
|
| 1030 |
+
(Numpy array): panned audio of size `n_samples x n_channels`.
|
| 1031 |
+
"""
|
| 1032 |
+
assert x.shape[1] == 1 or x.shape[1] == 2, 'Panner only works with monaural or stereo audio.'
|
| 1033 |
+
|
| 1034 |
+
# convert to stereo if signal is monaural
|
| 1035 |
+
if x.shape[1] < 2:
|
| 1036 |
+
x = np.repeat(x, 2, axis=1)
|
| 1037 |
+
|
| 1038 |
+
y = np.copy(x)
|
| 1039 |
+
if self.parameters.pan_mode.value in ['delay', 'both']:
|
| 1040 |
+
y[:, 0] = np.roll(y[:, 0], self.shifts[0])
|
| 1041 |
+
y[:, 1] = np.roll(y[:, 1], self.shifts[1])
|
| 1042 |
+
|
| 1043 |
+
if self.parameters.pan_mode.value in ['amplitude', 'both']:
|
| 1044 |
+
y *= self.gains
|
| 1045 |
+
|
| 1046 |
+
return y
|
| 1047 |
+
|
| 1048 |
+
def update(self, parameter_name=None):
|
| 1049 |
+
"""
|
| 1050 |
+
Update processor after randomization of parameters.
|
| 1051 |
+
|
| 1052 |
+
Args:
|
| 1053 |
+
parameter_name (str): Parameter whose value has changed.
|
| 1054 |
+
"""
|
| 1055 |
+
self._calculate_pan_coefficents()
|
| 1056 |
+
self._calculate_pan_delay()
|
| 1057 |
+
|
| 1058 |
+
|
| 1059 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GAIN %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1060 |
+
class Gain(Processor):
|
| 1061 |
+
"""
|
| 1062 |
+
Gain Processor.
|
| 1063 |
+
|
| 1064 |
+
Applies gain in dB and optionally inverts polarity.
|
| 1065 |
+
|
| 1066 |
+
Processor parameters:
|
| 1067 |
+
gain (float): Gain that should be applied (dB scale).
|
| 1068 |
+
invert (bool): If True, then we also invert the waveform (all channels jointly).
|
| 1069 |
+
"""
|
| 1070 |
+
|
| 1071 |
+
def __init__(self, name='Gain', parameters=None, **kwargs):
|
| 1072 |
+
"""
|
| 1073 |
+
Initialize processor.
|
| 1074 |
+
|
| 1075 |
+
Args:
|
| 1076 |
+
name (str): Name of processor.
|
| 1077 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1078 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1079 |
+
"""
|
| 1080 |
+
super().__init__(name, parameters=parameters, block_size=None, **kwargs)
|
| 1081 |
+
|
| 1082 |
+
if not parameters:
|
| 1083 |
+
self.parameters = ParameterList()
|
| 1084 |
+
self.parameters.add(Parameter('gain', 1.0, 'float', units='dB', minimum=-6.0, maximum=6.0))
|
| 1085 |
+
self.parameters.add(Parameter('invert', False, 'bool'))
|
| 1086 |
+
else:
|
| 1087 |
+
self.parameters = parameters
|
| 1088 |
+
|
| 1089 |
+
def process(self, x):
|
| 1090 |
+
"""
|
| 1091 |
+
Process audio.
|
| 1092 |
+
|
| 1093 |
+
Args:
|
| 1094 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1095 |
+
|
| 1096 |
+
Returns:
|
| 1097 |
+
(Numpy array): gain-augmented audio of size `n_samples x n_channels`.
|
| 1098 |
+
"""
|
| 1099 |
+
gain = 10 ** (self.parameters.gain.value / 20.)
|
| 1100 |
+
if self.parameters.invert.value:
|
| 1101 |
+
gain = -gain
|
| 1102 |
+
return gain * x
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
# %%%%%%%%%%%%%%%%%%%%%%% SIMPLE CHANNEL SWAP %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1106 |
+
class SwapChannels(Processor):
|
| 1107 |
+
"""
|
| 1108 |
+
Swap channels in multi-channel audio.
|
| 1109 |
+
|
| 1110 |
+
Processor parameters:
|
| 1111 |
+
index (int) Selects the permutation that we are using.
|
| 1112 |
+
Please note that "no permutation" is one of the permutations in `self.permutations` at index `0`.
|
| 1113 |
+
Hence, this effect should be applied with probability "1.".
|
| 1114 |
+
"""
|
| 1115 |
+
|
| 1116 |
+
def __init__(self, n_channels, name='SwapChannels', parameters=None, **kwargs):
|
| 1117 |
+
"""
|
| 1118 |
+
Initialize processor.
|
| 1119 |
+
|
| 1120 |
+
Args:
|
| 1121 |
+
n_channels (int): Number of channels in audio that we want to process.
|
| 1122 |
+
name (str): Name of processor.
|
| 1123 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1124 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1125 |
+
"""
|
| 1126 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1127 |
+
|
| 1128 |
+
self.permutations = tuple(permutations(range(n_channels), n_channels))
|
| 1129 |
+
|
| 1130 |
+
if not parameters:
|
| 1131 |
+
self.parameters = ParameterList()
|
| 1132 |
+
self.parameters.add(Parameter('index', 0, 'int', minimum=0, maximum=len(self.permutations)))
|
| 1133 |
+
else:
|
| 1134 |
+
self.parameters = parameters
|
| 1135 |
+
|
| 1136 |
+
def process(self, x):
|
| 1137 |
+
"""
|
| 1138 |
+
Process audio.
|
| 1139 |
+
|
| 1140 |
+
Args:
|
| 1141 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1142 |
+
|
| 1143 |
+
Returns:
|
| 1144 |
+
(Numpy array): channel-swapped audio of size `n_samples x n_channels`.
|
| 1145 |
+
"""
|
| 1146 |
+
return x[:, self.permutations[self.parameters.index.value]]
|
| 1147 |
+
|
| 1148 |
+
|
| 1149 |
+
# %%%%%%%%%%%%%%%%%%%%%%% Monauralize %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1150 |
+
class Monauralize(Processor):
|
| 1151 |
+
"""
|
| 1152 |
+
Monauralizes audio (i.e., removes spatial information).
|
| 1153 |
+
|
| 1154 |
+
Process parameters:
|
| 1155 |
+
seed_channel (int): channel that we use for overwriting the others.
|
| 1156 |
+
`-1` refers to using the mean over all channels.
|
| 1157 |
+
"""
|
| 1158 |
+
|
| 1159 |
+
def __init__(self, n_channels, name='Monauralize', parameters=None, **kwargs):
|
| 1160 |
+
"""
|
| 1161 |
+
Initialize processor.
|
| 1162 |
+
|
| 1163 |
+
Args:
|
| 1164 |
+
n_channels (int): Number of channels in audio that we want to process.
|
| 1165 |
+
name (str): Name of processor.
|
| 1166 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1167 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1168 |
+
"""
|
| 1169 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1170 |
+
|
| 1171 |
+
if not parameters:
|
| 1172 |
+
self.parameters = ParameterList()
|
| 1173 |
+
self.parameters.add(Parameter('seed_channel', 0, 'int', minimum=-1, maximum=n_channels))
|
| 1174 |
+
else:
|
| 1175 |
+
self.parameters = parameters
|
| 1176 |
+
|
| 1177 |
+
def process(self, x):
|
| 1178 |
+
"""
|
| 1179 |
+
Process audio.
|
| 1180 |
+
|
| 1181 |
+
Args:
|
| 1182 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1183 |
+
|
| 1184 |
+
Returns:
|
| 1185 |
+
(Numpy array): monauralized audio of size `n_samples x n_channels`.
|
| 1186 |
+
"""
|
| 1187 |
+
tile_reps = (1, x.shape[1])
|
| 1188 |
+
if self.parameters.seed_channel.value == -1:
|
| 1189 |
+
# use average as seed
|
| 1190 |
+
return np.tile(np.mean(x, axis=1, keepdims=True), tile_reps)
|
| 1191 |
+
else:
|
| 1192 |
+
# use one channel as seed
|
| 1193 |
+
return np.tile(x[:, [self.parameters.seed_channel.value]], tile_reps)
|
| 1194 |
+
|
| 1195 |
+
|
| 1196 |
+
# %%%%%%%%%%%%%%%%%%%%%%% ChunkShuffle %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1197 |
+
class ChunkShuffle(Processor):
|
| 1198 |
+
"""
|
| 1199 |
+
Split audio into chunks and randomly re-arrange it.
|
| 1200 |
+
|
| 1201 |
+
Process parameters:
|
| 1202 |
+
n_chunks (int): number of chunks into which we split
|
| 1203 |
+
"""
|
| 1204 |
+
|
| 1205 |
+
def __init__(self, name='ChunkShuffle', parameters=None, **kwargs):
|
| 1206 |
+
"""
|
| 1207 |
+
Initialize processor.
|
| 1208 |
+
|
| 1209 |
+
Args:
|
| 1210 |
+
name (str): Name of processor.
|
| 1211 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1212 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1213 |
+
"""
|
| 1214 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1215 |
+
|
| 1216 |
+
if not parameters:
|
| 1217 |
+
self.parameters = ParameterList()
|
| 1218 |
+
self.parameters.add(Parameter('n_chunks', 2, 'int', minimum=2, maximum=6))
|
| 1219 |
+
else:
|
| 1220 |
+
self.parameters = parameters
|
| 1221 |
+
|
| 1222 |
+
def process(self, x):
|
| 1223 |
+
"""
|
| 1224 |
+
Process audio.
|
| 1225 |
+
|
| 1226 |
+
Args:
|
| 1227 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1228 |
+
|
| 1229 |
+
Returns:
|
| 1230 |
+
(Numpy array): chunk-shuffled audio of size `n_samples x n_channels`.
|
| 1231 |
+
"""
|
| 1232 |
+
# determine random chunk boundaries
|
| 1233 |
+
boundaries = np.sort(np.random.randint(x.shape[0], size=self.parameters.n_chunks.value-1))
|
| 1234 |
+
|
| 1235 |
+
# create chunks
|
| 1236 |
+
chunks = np.split(x, boundaries)
|
| 1237 |
+
|
| 1238 |
+
# shuffle them
|
| 1239 |
+
np.random.shuffle(chunks)
|
| 1240 |
+
|
| 1241 |
+
# return chunks in new random order
|
| 1242 |
+
return np.concatenate(chunks)
|
| 1243 |
+
|
| 1244 |
+
|
| 1245 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PITCH SHIFT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1246 |
+
class PitchShift(Processor):
|
| 1247 |
+
"""
|
| 1248 |
+
Simple pitch shifter using SoX and soxbindings (https://github.com/pseeth/soxbindings).
|
| 1249 |
+
|
| 1250 |
+
Processor parameters:
|
| 1251 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 1252 |
+
steps (float): Pitch shift as positive/negative semitones
|
| 1253 |
+
quick (bool): If True, this effect will run faster but with lower sound quality.
|
| 1254 |
+
"""
|
| 1255 |
+
|
| 1256 |
+
def __init__(self, sample_rates, fix_length=True, name='PitchShift', parameters=None, **kwargs):
|
| 1257 |
+
"""
|
| 1258 |
+
Initialize processor.
|
| 1259 |
+
|
| 1260 |
+
Args:
|
| 1261 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 1262 |
+
fix_length (bool): If True, then output has same length as input.
|
| 1263 |
+
name (str): Name of processor.
|
| 1264 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1265 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1266 |
+
"""
|
| 1267 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1268 |
+
|
| 1269 |
+
if not parameters:
|
| 1270 |
+
self.parameters = ParameterList()
|
| 1271 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 1272 |
+
self.parameters.add(Parameter('steps', 0.0, 'float', minimum=-6., maximum=6.))
|
| 1273 |
+
self.parameters.add(Parameter('quick', False, 'bool'))
|
| 1274 |
+
else:
|
| 1275 |
+
self.parameters = parameters
|
| 1276 |
+
|
| 1277 |
+
self.fix_length = fix_length
|
| 1278 |
+
self.clips = False
|
| 1279 |
+
|
| 1280 |
+
def process(self, x):
|
| 1281 |
+
"""
|
| 1282 |
+
Process audio.
|
| 1283 |
+
|
| 1284 |
+
Args:
|
| 1285 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1286 |
+
|
| 1287 |
+
Returns:
|
| 1288 |
+
(Numpy array): pitch-shifted audio of size `n_samples x n_channels`.
|
| 1289 |
+
"""
|
| 1290 |
+
if self.parameters.steps.value == 0.0:
|
| 1291 |
+
y = x
|
| 1292 |
+
else:
|
| 1293 |
+
scale = np.max(np.abs(x))
|
| 1294 |
+
if scale > MAX_SOX_PROCESSING_PEAK:
|
| 1295 |
+
clips = True
|
| 1296 |
+
x = x * (MAX_SOX_PROCESSING_PEAK / scale)
|
| 1297 |
+
else:
|
| 1298 |
+
clips = False
|
| 1299 |
+
|
| 1300 |
+
tfm = sox.Transformer()
|
| 1301 |
+
tfm.pitch(self.parameters.steps.value, quick=bool(self.parameters.quick.value))
|
| 1302 |
+
y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
|
| 1303 |
+
|
| 1304 |
+
if clips:
|
| 1305 |
+
y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
|
| 1306 |
+
|
| 1307 |
+
if self.fix_length:
|
| 1308 |
+
n_samples_input = x.shape[0]
|
| 1309 |
+
n_samples_output = y.shape[0]
|
| 1310 |
+
if n_samples_input < n_samples_output:
|
| 1311 |
+
idx1 = (n_samples_output - n_samples_input) // 2
|
| 1312 |
+
idx2 = idx1 + n_samples_input
|
| 1313 |
+
y = y[idx1:idx2]
|
| 1314 |
+
elif n_samples_input > n_samples_output:
|
| 1315 |
+
n_pad = n_samples_input - n_samples_output
|
| 1316 |
+
y = np.pad(y, ((n_pad//2, n_pad - n_pad//2), (0, 0)))
|
| 1317 |
+
|
| 1318 |
+
return y
|
| 1319 |
+
|
| 1320 |
+
|
| 1321 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TIME STRETCH %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1322 |
+
class TimeStretch(Processor):
|
| 1323 |
+
"""
|
| 1324 |
+
Simple time stretcher using SoX and soxbindings (https://github.com/pseeth/soxbindings).
|
| 1325 |
+
|
| 1326 |
+
Processor parameters:
|
| 1327 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 1328 |
+
factor (float): Time stretch factor.
|
| 1329 |
+
quick (bool): If True, this effect will run faster but with lower sound quality.
|
| 1330 |
+
stretch_type (str): Algorithm used for stretching (`tempo` or `stretch`).
|
| 1331 |
+
audio_type (str): Sets which time segments are most optmial when finding
|
| 1332 |
+
the best overlapping points for time stretching.
|
| 1333 |
+
"""
|
| 1334 |
+
|
| 1335 |
+
def __init__(self, sample_rates, fix_length=True, name='TimeStretch', parameters=None, **kwargs):
|
| 1336 |
+
"""
|
| 1337 |
+
Initialize processor.
|
| 1338 |
+
|
| 1339 |
+
Args:
|
| 1340 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 1341 |
+
fix_length (bool): If True, then output has same length as input.
|
| 1342 |
+
name (str): Name of processor.
|
| 1343 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1344 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1345 |
+
"""
|
| 1346 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1347 |
+
|
| 1348 |
+
if not parameters:
|
| 1349 |
+
self.parameters = ParameterList()
|
| 1350 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 1351 |
+
self.parameters.add(Parameter('factor', 1.0, 'float', minimum=1/1.33, maximum=1.33))
|
| 1352 |
+
self.parameters.add(Parameter('quick', False, 'bool'))
|
| 1353 |
+
self.parameters.add(Parameter('stretch_type', 'tempo', 'string', options=['tempo', 'stretch']))
|
| 1354 |
+
self.parameters.add(Parameter('audio_type', 'l', 'string', options=['m', 's', 'l']))
|
| 1355 |
+
else:
|
| 1356 |
+
self.parameters = parameters
|
| 1357 |
+
|
| 1358 |
+
self.fix_length = fix_length
|
| 1359 |
+
|
| 1360 |
+
def process(self, x):
|
| 1361 |
+
"""
|
| 1362 |
+
Process audio.
|
| 1363 |
+
|
| 1364 |
+
Args:
|
| 1365 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1366 |
+
|
| 1367 |
+
Returns:
|
| 1368 |
+
(Numpy array): time-stretched audio of size `n_samples x n_channels`.
|
| 1369 |
+
"""
|
| 1370 |
+
if self.parameters.factor.value == 1.0:
|
| 1371 |
+
y = x
|
| 1372 |
+
else:
|
| 1373 |
+
scale = np.max(np.abs(x))
|
| 1374 |
+
if scale > MAX_SOX_PROCESSING_PEAK:
|
| 1375 |
+
clips = True
|
| 1376 |
+
x = x * (MAX_SOX_PROCESSING_PEAK / scale)
|
| 1377 |
+
else:
|
| 1378 |
+
clips = False
|
| 1379 |
+
|
| 1380 |
+
tfm = sox.Transformer()
|
| 1381 |
+
if self.parameters.stretch_type.value == 'stretch':
|
| 1382 |
+
tfm.stretch(self.parameters.factor.value)
|
| 1383 |
+
elif self.parameters.stretch_type.value == 'tempo':
|
| 1384 |
+
tfm.tempo(1/self.parameters.factor.value,
|
| 1385 |
+
audio_type=self.parameters.audio_type.value,
|
| 1386 |
+
quick=bool(self.parameters.quick.value))
|
| 1387 |
+
y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
|
| 1388 |
+
|
| 1389 |
+
if clips:
|
| 1390 |
+
y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
|
| 1391 |
+
|
| 1392 |
+
if self.fix_length:
|
| 1393 |
+
n_samples_input = x.shape[0]
|
| 1394 |
+
n_samples_output = y.shape[0]
|
| 1395 |
+
if n_samples_input < n_samples_output:
|
| 1396 |
+
idx1 = (n_samples_output - n_samples_input) // 2
|
| 1397 |
+
idx2 = idx1 + n_samples_input
|
| 1398 |
+
y = y[idx1:idx2]
|
| 1399 |
+
elif n_samples_input > n_samples_output:
|
| 1400 |
+
n_pad = n_samples_input - n_samples_output
|
| 1401 |
+
y = np.pad(y, ((n_pad//2, n_pad - n_pad//2), (0, 0)))
|
| 1402 |
+
|
| 1403 |
+
return y
|
| 1404 |
+
|
| 1405 |
+
|
| 1406 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PLAYBACK SPEED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1407 |
+
class PlaybackSpeed(Processor):
|
| 1408 |
+
"""
|
| 1409 |
+
Simple playback speed effect using SoX and soxbindings (https://github.com/pseeth/soxbindings).
|
| 1410 |
+
|
| 1411 |
+
Processor parameters:
|
| 1412 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 1413 |
+
factor (float): Playback speed factor.
|
| 1414 |
+
"""
|
| 1415 |
+
|
| 1416 |
+
def __init__(self, sample_rates, fix_length=True, name='PlaybackSpeed', parameters=None, **kwargs):
|
| 1417 |
+
"""
|
| 1418 |
+
Initialize processor.
|
| 1419 |
+
|
| 1420 |
+
Args:
|
| 1421 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 1422 |
+
fix_length (bool): If True, then output has same length as input.
|
| 1423 |
+
name (str): Name of processor.
|
| 1424 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1425 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1426 |
+
"""
|
| 1427 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1428 |
+
|
| 1429 |
+
if not parameters:
|
| 1430 |
+
self.parameters = ParameterList()
|
| 1431 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 1432 |
+
self.parameters.add(Parameter('factor', 1.0, 'float', minimum=1./1.33, maximum=1.33))
|
| 1433 |
+
else:
|
| 1434 |
+
self.parameters = parameters
|
| 1435 |
+
|
| 1436 |
+
self.fix_length = fix_length
|
| 1437 |
+
|
| 1438 |
+
def process(self, x):
|
| 1439 |
+
"""
|
| 1440 |
+
Process audio.
|
| 1441 |
+
|
| 1442 |
+
Args:
|
| 1443 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1444 |
+
|
| 1445 |
+
Returns:
|
| 1446 |
+
(Numpy array): resampled audio of size `n_samples x n_channels`.
|
| 1447 |
+
"""
|
| 1448 |
+
if self.parameters.factor.value == 1.0:
|
| 1449 |
+
y = x
|
| 1450 |
+
else:
|
| 1451 |
+
scale = np.max(np.abs(x))
|
| 1452 |
+
if scale > MAX_SOX_PROCESSING_PEAK:
|
| 1453 |
+
clips = True
|
| 1454 |
+
x = x * (MAX_SOX_PROCESSING_PEAK / scale)
|
| 1455 |
+
else:
|
| 1456 |
+
clips = False
|
| 1457 |
+
|
| 1458 |
+
tfm = sox.Transformer()
|
| 1459 |
+
tfm.speed(self.parameters.factor.value)
|
| 1460 |
+
y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
|
| 1461 |
+
|
| 1462 |
+
if clips:
|
| 1463 |
+
y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
|
| 1464 |
+
|
| 1465 |
+
if self.fix_length:
|
| 1466 |
+
n_samples_input = x.shape[0]
|
| 1467 |
+
n_samples_output = y.shape[0]
|
| 1468 |
+
if n_samples_input < n_samples_output:
|
| 1469 |
+
idx1 = (n_samples_output - n_samples_input) // 2
|
| 1470 |
+
idx2 = idx1 + n_samples_input
|
| 1471 |
+
y = y[idx1:idx2]
|
| 1472 |
+
elif n_samples_input > n_samples_output:
|
| 1473 |
+
n_pad = n_samples_input - n_samples_output
|
| 1474 |
+
y = np.pad(y, ((n_pad//2, n_pad - n_pad//2), (0, 0)))
|
| 1475 |
+
|
| 1476 |
+
return y
|
| 1477 |
+
|
| 1478 |
+
|
| 1479 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% BEND %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1480 |
+
class Bend(Processor):
|
| 1481 |
+
"""
|
| 1482 |
+
Simple bend effect using SoX and soxbindings (https://github.com/pseeth/soxbindings).
|
| 1483 |
+
|
| 1484 |
+
Processor parameters:
|
| 1485 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 1486 |
+
n_bends (int): Number of segments or intervals to pitch shift
|
| 1487 |
+
"""
|
| 1488 |
+
|
| 1489 |
+
def __init__(self, sample_rates, pitch_range=(-600, 600), fix_length=True, name='Bend', parameters=None, **kwargs):
|
| 1490 |
+
"""
|
| 1491 |
+
Initialize processor.
|
| 1492 |
+
|
| 1493 |
+
Args:
|
| 1494 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 1495 |
+
pitch_range (tuple of ints): min and max pitch bending ranges in cents
|
| 1496 |
+
fix_length (bool): If True, then output has same length as input.
|
| 1497 |
+
name (str): Name of processor.
|
| 1498 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1499 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1500 |
+
"""
|
| 1501 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1502 |
+
|
| 1503 |
+
if not parameters:
|
| 1504 |
+
self.parameters = ParameterList()
|
| 1505 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 1506 |
+
self.parameters.add(Parameter('n_bends', 2, 'int', minimum=2, maximum=10))
|
| 1507 |
+
else:
|
| 1508 |
+
self.parameters = parameters
|
| 1509 |
+
self.pitch_range_min, self.pitch_range_max = pitch_range
|
| 1510 |
+
|
| 1511 |
+
def process(self, x):
|
| 1512 |
+
"""
|
| 1513 |
+
Process audio.
|
| 1514 |
+
|
| 1515 |
+
Args:
|
| 1516 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1517 |
+
|
| 1518 |
+
Returns:
|
| 1519 |
+
(Numpy array): pitch-bended audio of size `n_samples x n_channels`.
|
| 1520 |
+
"""
|
| 1521 |
+
n_bends = self.parameters.n_bends.value
|
| 1522 |
+
max_length = x.shape[0] / self.parameters.sample_rate.value
|
| 1523 |
+
|
| 1524 |
+
# Generates random non-overlapping segments
|
| 1525 |
+
delta = 1. / self.parameters.sample_rate.value
|
| 1526 |
+
boundaries = np.sort(delta + np.random.rand(n_bends-1) * (max_length - delta))
|
| 1527 |
+
|
| 1528 |
+
start, end = np.zeros(n_bends), np.zeros(n_bends)
|
| 1529 |
+
start[0] = delta
|
| 1530 |
+
for i, b in enumerate(boundaries):
|
| 1531 |
+
end[i] = b
|
| 1532 |
+
start[i+1] = b
|
| 1533 |
+
end[-1] = max_length
|
| 1534 |
+
|
| 1535 |
+
# randomly sample pitch-shifts in cents
|
| 1536 |
+
cents = np.random.randint(self.pitch_range_min, self.pitch_range_max+1, n_bends)
|
| 1537 |
+
|
| 1538 |
+
# remove segment if cent value is zero or start == end (as SoX does not allow such values)
|
| 1539 |
+
idx_keep = np.logical_and(cents != 0, start != end)
|
| 1540 |
+
n_bends, start, end, cents = sum(idx_keep), start[idx_keep], end[idx_keep], cents[idx_keep]
|
| 1541 |
+
|
| 1542 |
+
scale = np.max(np.abs(x))
|
| 1543 |
+
if scale > MAX_SOX_PROCESSING_PEAK:
|
| 1544 |
+
clips = True
|
| 1545 |
+
x = x * (MAX_SOX_PROCESSING_PEAK / scale)
|
| 1546 |
+
else:
|
| 1547 |
+
clips = False
|
| 1548 |
+
|
| 1549 |
+
tfm = sox.Transformer()
|
| 1550 |
+
tfm.bend(n_bends=int(n_bends), start_times=list(start), end_times=list(end), cents=list(cents))
|
| 1551 |
+
y = tfm.build_array(input_array=x, sample_rate_in=self.parameters.sample_rate.value).astype(np.float32)
|
| 1552 |
+
|
| 1553 |
+
if clips:
|
| 1554 |
+
y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
|
| 1555 |
+
|
| 1556 |
+
return y
|
| 1557 |
+
|
| 1558 |
+
|
| 1559 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% MASTERING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1560 |
+
class Mastering(Processor):
|
| 1561 |
+
"""
|
| 1562 |
+
Mastering Processor.
|
| 1563 |
+
|
| 1564 |
+
Models different mastering effects, i.e., ways how we deal with sample values outside [-1, 1].
|
| 1565 |
+
|
| 1566 |
+
Processor parameters:
|
| 1567 |
+
method (str): Method that should be applied. Can take the following values:
|
| 1568 |
+
`none`: Do nothing. E.g., useful if training a network for VST plugin where DAW can also handle peaks
|
| 1569 |
+
larger/smaller than `+1`/`-1`.
|
| 1570 |
+
`scale`: Scale to max-abs peak to avoid clipping.
|
| 1571 |
+
`hardclip`: Apply hardclipping to [-1, 1].
|
| 1572 |
+
`softclip`: Apply softclipping to [-1, 1] using tanh.
|
| 1573 |
+
"""
|
| 1574 |
+
|
| 1575 |
+
def __init__(self, name='Mastering', parameters=None, maximum_amplitude=1.0, **kwargs):
|
| 1576 |
+
"""
|
| 1577 |
+
Initialize processor.
|
| 1578 |
+
|
| 1579 |
+
Args:
|
| 1580 |
+
name (str): Name of processor.
|
| 1581 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1582 |
+
maximum_amplitude (float): Maximum amplitude after applying the mastering processor.
|
| 1583 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1584 |
+
"""
|
| 1585 |
+
super().__init__(name, parameters=parameters, block_size=None, **kwargs)
|
| 1586 |
+
|
| 1587 |
+
if not parameters:
|
| 1588 |
+
self.parameters = ParameterList()
|
| 1589 |
+
self.parameters.add(Parameter('method', 'none', 'string',
|
| 1590 |
+
options=['none', 'scale', 'hardclip', 'softclip']))
|
| 1591 |
+
else:
|
| 1592 |
+
self.parameters = parameters
|
| 1593 |
+
|
| 1594 |
+
self.maximum_amplitude = maximum_amplitude
|
| 1595 |
+
|
| 1596 |
+
def process(self, x):
|
| 1597 |
+
"""
|
| 1598 |
+
Process audio.
|
| 1599 |
+
|
| 1600 |
+
Args:
|
| 1601 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1602 |
+
|
| 1603 |
+
Returns:
|
| 1604 |
+
(Numpy array): mastered audio of size `n_samples x n_channels`.
|
| 1605 |
+
"""
|
| 1606 |
+
y = np.copy(x)
|
| 1607 |
+
if self.parameters.method.value == 'none':
|
| 1608 |
+
return y
|
| 1609 |
+
elif self.parameters.method.value == 'scale':
|
| 1610 |
+
maxabs = np.maximum(self.maximum_amplitude, np.max(np.abs(y)))
|
| 1611 |
+
return y / (maxabs / self.maximum_amplitude)
|
| 1612 |
+
elif self.parameters.method.value == 'hardclip':
|
| 1613 |
+
return np.clip(y, -self.maximum_amplitude, self.maximum_amplitude)
|
| 1614 |
+
elif self.parameters.method.value == 'softclip':
|
| 1615 |
+
return self.maximum_amplitude * np.tanh(y)
|
| 1616 |
+
|
| 1617 |
+
|
| 1618 |
+
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% MP3 COMPRESSION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
| 1619 |
+
class MP3Compression(Processor):
|
| 1620 |
+
"""
|
| 1621 |
+
Models the effect of an mp3 compression using LAME.
|
| 1622 |
+
|
| 1623 |
+
LAME adds some delay at the beginning - we therefore truncate the first samples
|
| 1624 |
+
|
| 1625 |
+
Processor parameters:
|
| 1626 |
+
sample_rate (int): Current (assumed) sample rate of the audio.
|
| 1627 |
+
quality (int): in the range from 2 ... 7 (2 = highest, 7 = fastest)
|
| 1628 |
+
bitrate (int): supported bitrates are
|
| 1629 |
+
[8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320]
|
| 1630 |
+
"""
|
| 1631 |
+
|
| 1632 |
+
def __init__(self, sample_rates, fix_length=True, name='MP3Compression', parameters=None, **kwargs):
|
| 1633 |
+
"""
|
| 1634 |
+
Initialize processor.
|
| 1635 |
+
|
| 1636 |
+
Args:
|
| 1637 |
+
sample_rates (list of ints): Sample rates of input audio.
|
| 1638 |
+
fix_length (bool): If True, then output has same length as input.
|
| 1639 |
+
name (str): Name of processor.
|
| 1640 |
+
parameters (parameter_list): Parameters for this processor.
|
| 1641 |
+
**kwargs (dict): Keyword arguments that are passed to the Processor class.
|
| 1642 |
+
"""
|
| 1643 |
+
super().__init__(name=name, parameters=parameters, block_size=None, **kwargs)
|
| 1644 |
+
|
| 1645 |
+
if not parameters:
|
| 1646 |
+
supported_bitrates = [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320]
|
| 1647 |
+
self.parameters = ParameterList()
|
| 1648 |
+
self.parameters.add(Parameter('sample_rate', 44100, 'string', options=sample_rates))
|
| 1649 |
+
self.parameters.add(Parameter('quality', 2, 'string', options=[2, 3, 4, 5, 6, 7]))
|
| 1650 |
+
self.parameters.add(Parameter('bitrate', 96, 'string', options=supported_bitrates))
|
| 1651 |
+
else:
|
| 1652 |
+
self.parameters = parameters
|
| 1653 |
+
|
| 1654 |
+
self.fix_length = fix_length
|
| 1655 |
+
|
| 1656 |
+
def process(self, x):
|
| 1657 |
+
"""
|
| 1658 |
+
Process audio.
|
| 1659 |
+
|
| 1660 |
+
Args:
|
| 1661 |
+
x (Numpy array): input audio of size `n_samples x n_channels`.
|
| 1662 |
+
|
| 1663 |
+
Returns:
|
| 1664 |
+
(Numpy array): MP3 compressed audio of size `n_samples x n_channels`.
|
| 1665 |
+
"""
|
| 1666 |
+
# scale if max-abs peak is larger than `MAX_SOX_PROCESSING_PEAK`
|
| 1667 |
+
scale = np.max(np.abs(x))
|
| 1668 |
+
if scale > MAX_SOX_PROCESSING_PEAK:
|
| 1669 |
+
clips = True
|
| 1670 |
+
x = x * (MAX_SOX_PROCESSING_PEAK / scale)
|
| 1671 |
+
else:
|
| 1672 |
+
clips = False
|
| 1673 |
+
|
| 1674 |
+
# convert to int16
|
| 1675 |
+
x_int = (x * np.iinfo(np.int16).max).astype(np.int16)
|
| 1676 |
+
|
| 1677 |
+
encoder = lameenc.Encoder()
|
| 1678 |
+
encoder.set_bit_rate(self.parameters.bitrate.value)
|
| 1679 |
+
encoder.set_in_sample_rate(self.parameters.sample_rate.value)
|
| 1680 |
+
encoder.set_channels(x.shape[1])
|
| 1681 |
+
encoder.set_quality(self.parameters.quality.value)
|
| 1682 |
+
encoder.silence()
|
| 1683 |
+
|
| 1684 |
+
mp3_data = encoder.encode(x_int.tobytes())
|
| 1685 |
+
mp3_data += encoder.flush()
|
| 1686 |
+
|
| 1687 |
+
memory_file = io.BytesIO(initial_bytes=mp3_data)
|
| 1688 |
+
y, fs = sf.read(memory_file, always_2d=True, dtype=np.float32)
|
| 1689 |
+
|
| 1690 |
+
# resample to original sampling rate if LAME changed it
|
| 1691 |
+
if fs != self.parameters.sample_rate.value:
|
| 1692 |
+
tfm = sox.Transformer()
|
| 1693 |
+
tfm.speed(fs / self.parameters.sample_rate.value)
|
| 1694 |
+
y = tfm.build_array(input_array=y, sample_rate_in=fs).astype(np.float32)
|
| 1695 |
+
|
| 1696 |
+
if clips:
|
| 1697 |
+
y *= scale / MAX_SOX_PROCESSING_PEAK # rescale output to original scale
|
| 1698 |
+
|
| 1699 |
+
if self.fix_length:
|
| 1700 |
+
# LAME adds some samples at the beginning - remove them here
|
| 1701 |
+
n_samples_input = x.shape[0]
|
| 1702 |
+
n_samples_output = y.shape[0]
|
| 1703 |
+
if n_samples_input < n_samples_output:
|
| 1704 |
+
y = y[-n_samples_input:, :]
|
| 1705 |
+
|
| 1706 |
+
return y
|
| 1707 |
+
|
| 1708 |
+
|
| 1709 |
+
def __main__():
|
| 1710 |
+
"""
|
| 1711 |
+
Main function for testing the audio effects.
|
| 1712 |
+
|
| 1713 |
+
examples:
|
| 1714 |
+
|
| 1715 |
+
config['AUGMENTER_CHAIN'] = AugmentationChain([(ConvolutionalReverb(impulse_responses=impulse_responses,
|
| 1716 |
+
sample_rates=config['ACCEPTED_SAMPLING_RATES']), 0.5),
|
| 1717 |
+
(Gain(), 0.5),
|
| 1718 |
+
(Haas(sample_rates=config['ACCEPTED_SAMPLING_RATES']), 0.5),
|
| 1719 |
+
(Panner(sample_rates=config['ACCEPTED_SAMPLING_RATES']), 0.5),
|
| 1720 |
+
(SwapChannels(n_channels=config['N_CHANNELS']), 1.),
|
| 1721 |
+
(Monauralize(n_channels=config['N_CHANNELS']), 0.5)],
|
| 1722 |
+
shuffle=True)
|
| 1723 |
+
|
| 1724 |
+
config['AUGMENTER_CHAIN'] = AugmentationChain([(Gain(), 1.),
|
| 1725 |
+
(SwapChannels(n_channels=config['N_CHANNELS']), 1.),
|
| 1726 |
+
(Monauralize(n_channels=config['N_CHANNELS']), 0.5)], shuffle=True)
|
| 1727 |
+
"""
|
| 1728 |
+
pass
|
| 1729 |
+
|
utils/data_utils.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import soundfile as sf
|
| 2 |
+
import torchaudio
|
| 3 |
+
|
| 4 |
+
def read_wav_segment(file_path, start=None, end=None, dtype="float32"):
|
| 5 |
+
"""
|
| 6 |
+
Reads a specific segment from a .wav file efficiently.
|
| 7 |
+
|
| 8 |
+
Args:
|
| 9 |
+
file_path (str): Path to the .wav file.
|
| 10 |
+
start (int): Start frame index.
|
| 11 |
+
end (int): End frame index.
|
| 12 |
+
|
| 13 |
+
Returns:
|
| 14 |
+
numpy.ndarray: Audio data for the specified segment.
|
| 15 |
+
int: Sample rate of the audio file.
|
| 16 |
+
"""
|
| 17 |
+
# Open the .wav file
|
| 18 |
+
if start is None or end is None:
|
| 19 |
+
data, samplerate = sf.read(file_path, dtype=dtype)
|
| 20 |
+
else:
|
| 21 |
+
with sf.SoundFile(file_path) as audio_file:
|
| 22 |
+
# Read only the required frames
|
| 23 |
+
audio_file.seek(start)
|
| 24 |
+
data = audio_file.read(frames=end-start, dtype=dtype)
|
| 25 |
+
samplerate = audio_file.samplerate
|
| 26 |
+
|
| 27 |
+
return data, samplerate
|
| 28 |
+
|
| 29 |
+
def get_audio_length(file_path):
|
| 30 |
+
"""
|
| 31 |
+
Retrieves the length of an audio file in seconds and frames.
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
file_path (str): Path to the audio file.
|
| 35 |
+
|
| 36 |
+
Returns:
|
| 37 |
+
float: Length of the audio file in seconds.
|
| 38 |
+
int: Total number of frames in the audio file.
|
| 39 |
+
int: Sample rate of the audio file.
|
| 40 |
+
"""
|
| 41 |
+
with sf.SoundFile(file_path) as audio_file:
|
| 42 |
+
total_frames = len(audio_file) # Total number of frames
|
| 43 |
+
samplerate = audio_file.samplerate # Sample rate
|
| 44 |
+
duration = total_frames / samplerate # Duration in seconds
|
| 45 |
+
|
| 46 |
+
return duration, total_frames, samplerate
|
| 47 |
+
|
| 48 |
+
def taxonomy2track(input_class, num_instr=8):
|
| 49 |
+
|
| 50 |
+
assert num_instr==8, "num_instr should be 8 for this function, the rest is not implemented yet"
|
| 51 |
+
|
| 52 |
+
if input_class is None:
|
| 53 |
+
return 'unknown'
|
| 54 |
+
if num_instr == 8:
|
| 55 |
+
mapping = {0000: 'other', 1100: 'drums', 1200: 'drums', 1300: 'other', 2000: 'bass', 3000: 'guitar', 4100: 'piano', 4200: 'piano', 4300: 'piano', 4400: 'other', 4500: 'other', 4600: 'other', 4700: 'other', 4900: 'other', 5000: 'brass', 6100: 'strings', 6210: 'brass', 6220: 'brass', 8100: 'guitar', 8200: 'brass', 9000: 'vocals'}
|
| 56 |
+
else:
|
| 57 |
+
raise NotImplementedError()
|
| 58 |
+
|
| 59 |
+
code_length = len(str(input_class))
|
| 60 |
+
if code_length < 4:
|
| 61 |
+
#pad zeros to the right to make it 4 digits
|
| 62 |
+
input_class = int(str(input_class) + "0" * (4 - code_length))
|
| 63 |
+
|
| 64 |
+
class_str = str(input_class)
|
| 65 |
+
for i in range(len(class_str), 0, -1):
|
| 66 |
+
general_class = int(class_str[:i] + "0" * (len(class_str) - i))
|
| 67 |
+
if general_class in mapping:
|
| 68 |
+
return mapping[general_class]
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
raise ValueError(f"No mapping found for input class {input_class} with num_instr {num_instr}")
|
| 72 |
+
except ValueError as e:
|
| 73 |
+
print(f"Error: {e}")
|
| 74 |
+
return "other" # Return a default value if no mapping is found
|
| 75 |
+
|
| 76 |
+
import torch
|
| 77 |
+
def efficient_roll(x, shift, dims=-1):
|
| 78 |
+
"""
|
| 79 |
+
Efficiently roll tensor elements along a dimension without creating a full copy.
|
| 80 |
+
|
| 81 |
+
Args:
|
| 82 |
+
x: Input tensor
|
| 83 |
+
shift: Number of places to roll (negative for left roll)
|
| 84 |
+
dim: Dimension along which to roll
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
Rolled tensor view where possible, minimal copy where necessary
|
| 88 |
+
"""
|
| 89 |
+
if shift == 0:
|
| 90 |
+
return x
|
| 91 |
+
|
| 92 |
+
# Get the size of the dimension
|
| 93 |
+
dim_size = x.size(dims)
|
| 94 |
+
|
| 95 |
+
# Handle shift larger than dimension size
|
| 96 |
+
shift = shift % dim_size
|
| 97 |
+
if shift < 0:
|
| 98 |
+
shift += dim_size
|
| 99 |
+
|
| 100 |
+
# Create indices for the roll
|
| 101 |
+
indices = torch.cat([torch.arange(dim_size-shift, dim_size),
|
| 102 |
+
torch.arange(0, dim_size-shift)])
|
| 103 |
+
|
| 104 |
+
# Use index_select for the roll
|
| 105 |
+
return torch.index_select(x, dims, indices)
|
| 106 |
+
|
| 107 |
+
#import loudness
|
| 108 |
+
import numpy as np
|
| 109 |
+
|
| 110 |
+
def apply_loud_normalization(x, lufs=-23, sample_rate=44100,device=None):
|
| 111 |
+
"""
|
| 112 |
+
x shaPe: (batch_size, channels, time)
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
in_shape= x.shape
|
| 116 |
+
if x.ndim != 3:
|
| 117 |
+
x=x.view(-1, in_shape[-2], in_shape[-1]) # Ensure x is 3D
|
| 118 |
+
|
| 119 |
+
B, C, T = x.shape
|
| 120 |
+
|
| 121 |
+
if device is None:
|
| 122 |
+
device = x.device
|
| 123 |
+
|
| 124 |
+
x_out = torch.zeros_like(x)
|
| 125 |
+
#for b in range(B):
|
| 126 |
+
# x_i=x[b].cpu().numpy().T
|
| 127 |
+
# lufs_in=loudness.integrated_loudness(x_i, sample_rate)
|
| 128 |
+
|
| 129 |
+
# delta_loudness= lufs - lufs_in
|
| 130 |
+
# gain=np.power(10, delta_loudness / 20) # Convert dB to linear gain
|
| 131 |
+
|
| 132 |
+
# x_out[b] = torch.tensor(x_i.T * gain, device=device)
|
| 133 |
+
|
| 134 |
+
x=x.view(B* C,1, T) # Ensure x is 3D
|
| 135 |
+
|
| 136 |
+
loudness=torchaudio.functional.loudness(x+1e-5, sample_rate=sample_rate)
|
| 137 |
+
delta_loudness = lufs - loudness
|
| 138 |
+
gain= torch.pow(10, delta_loudness / 20) # Convert dB to linear gain
|
| 139 |
+
if gain.isnan().any():
|
| 140 |
+
print("NaN detected in gain, setting to -30 dB")
|
| 141 |
+
gain = torch.nan_to_num(gain, nan=-30.0)
|
| 142 |
+
|
| 143 |
+
x_out = x * gain.view(B * C, 1, 1) # Apply gain to each channel
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
x_out = x_out.view(in_shape)
|
| 147 |
+
|
| 148 |
+
return x_out
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
from utils.feature_extractors.dsp_features import compute_log_rms_gated_max, compute_crest_factor, compute_stereo_width, compute_stereo_imbalance, compute_log_spread
|
| 153 |
+
|
| 154 |
+
def apply_RMS_normalization(x, RMS_norm=-25, device=None, use_gate=False):
|
| 155 |
+
if device is None:
|
| 156 |
+
device = x.device
|
| 157 |
+
|
| 158 |
+
RMS= torch.tensor(RMS_norm, device=device).view(1, 1, 1).repeat(x.shape[0],1,1) # Use fixed RMS for evaluation
|
| 159 |
+
|
| 160 |
+
x_RMS_ref=20*torch.log10(torch.sqrt(torch.mean(x**2, dim=(-1), keepdim=True).mean(dim=-2, keepdim=True)))
|
| 161 |
+
if use_gate:
|
| 162 |
+
x_RMS = compute_log_rms_gated_max(x).unsqueeze(-1)
|
| 163 |
+
else:
|
| 164 |
+
x_RMS=20*torch.log10(torch.sqrt(torch.mean(x**2, dim=(-1), keepdim=True).mean(dim=-2, keepdim=True)))
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
gain= RMS - x_RMS
|
| 168 |
+
gain_linear = 10 ** (gain / 20 + 1e-6) # Convert dB gain to linear scale, adding a small value to avoid division by zero
|
| 169 |
+
x=x* gain_linear
|
| 170 |
+
|
| 171 |
+
return x
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
import pyloudnorm as pyln
|
| 175 |
+
|
| 176 |
+
def loudness_normalize(audio, target_loudness=-23.0, sample_rate=44100):
|
| 177 |
+
"""
|
| 178 |
+
Normalize the loudness of the audio to a target level.
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
pylnmeter = pyln.Meter(sample_rate) # Create a meter for 44100 Hz sampling rate
|
| 182 |
+
|
| 183 |
+
audio= np.array(audio, dtype=np.float32).T
|
| 184 |
+
loudness = pylnmeter.integrated_loudness(audio)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
# loudness normalize audio to -12 dB LUFS
|
| 188 |
+
loudness_normalized_audio = pyln.normalize.loudness(audio, loudness, -14.0)
|
| 189 |
+
|
| 190 |
+
return torch.tensor(loudness_normalized_audio.T, dtype=torch.float32)
|
utils/dnnlib/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is licensed under a Creative Commons
|
| 4 |
+
# Attribution-NonCommercial-ShareAlike 4.0 International License.
|
| 5 |
+
# You should have received a copy of the license along with this
|
| 6 |
+
# work. If not, see http://creativecommons.org/licenses/by-nc-sa/4.0/
|
| 7 |
+
|
| 8 |
+
from .util import EasyDict, make_cache_dir_path, call_func_by_name
|
utils/dnnlib/util.py
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# This work is licensed under a Creative Commons
|
| 4 |
+
# Attribution-NonCommercial-ShareAlike 4.0 International License.
|
| 5 |
+
# You should have received a copy of the license along with this
|
| 6 |
+
# work. If not, see http://creativecommons.org/licenses/by-nc-sa/4.0/
|
| 7 |
+
|
| 8 |
+
"""Miscellaneous utility classes and functions."""
|
| 9 |
+
|
| 10 |
+
import ctypes
|
| 11 |
+
import fnmatch
|
| 12 |
+
import importlib
|
| 13 |
+
import inspect
|
| 14 |
+
import numpy as np
|
| 15 |
+
import os
|
| 16 |
+
import shutil
|
| 17 |
+
import sys
|
| 18 |
+
import types
|
| 19 |
+
import io
|
| 20 |
+
import pickle
|
| 21 |
+
import re
|
| 22 |
+
import requests
|
| 23 |
+
import html
|
| 24 |
+
import hashlib
|
| 25 |
+
import glob
|
| 26 |
+
import tempfile
|
| 27 |
+
import urllib
|
| 28 |
+
import urllib.request
|
| 29 |
+
import uuid
|
| 30 |
+
|
| 31 |
+
from distutils.util import strtobool
|
| 32 |
+
from typing import Any, List, Tuple, Union, Optional
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# Util classes
|
| 36 |
+
# ------------------------------------------------------------------------------------------
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class EasyDict(dict):
|
| 40 |
+
"""Convenience class that behaves like a dict but allows access with the attribute syntax."""
|
| 41 |
+
|
| 42 |
+
def __getattr__(self, name: str) -> Any:
|
| 43 |
+
try:
|
| 44 |
+
return self[name]
|
| 45 |
+
except KeyError:
|
| 46 |
+
raise AttributeError(name)
|
| 47 |
+
|
| 48 |
+
def __setattr__(self, name: str, value: Any) -> None:
|
| 49 |
+
self[name] = value
|
| 50 |
+
|
| 51 |
+
def __delattr__(self, name: str) -> None:
|
| 52 |
+
del self[name]
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class Logger(object):
|
| 56 |
+
"""Redirect stderr to stdout, optionally print stdout to a file, and optionally force flushing on both stdout and the file."""
|
| 57 |
+
|
| 58 |
+
def __init__(self, file_name: Optional[str] = None, file_mode: str = "w", should_flush: bool = True):
|
| 59 |
+
self.file = None
|
| 60 |
+
|
| 61 |
+
if file_name is not None:
|
| 62 |
+
self.file = open(file_name, file_mode)
|
| 63 |
+
|
| 64 |
+
self.should_flush = should_flush
|
| 65 |
+
self.stdout = sys.stdout
|
| 66 |
+
self.stderr = sys.stderr
|
| 67 |
+
|
| 68 |
+
sys.stdout = self
|
| 69 |
+
sys.stderr = self
|
| 70 |
+
|
| 71 |
+
def __enter__(self) -> "Logger":
|
| 72 |
+
return self
|
| 73 |
+
|
| 74 |
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
| 75 |
+
self.close()
|
| 76 |
+
|
| 77 |
+
def write(self, text: Union[str, bytes]) -> None:
|
| 78 |
+
"""Write text to stdout (and a file) and optionally flush."""
|
| 79 |
+
if isinstance(text, bytes):
|
| 80 |
+
text = text.decode()
|
| 81 |
+
if len(text) == 0: # workaround for a bug in VSCode debugger: sys.stdout.write(''); sys.stdout.flush() => crash
|
| 82 |
+
return
|
| 83 |
+
|
| 84 |
+
if self.file is not None:
|
| 85 |
+
self.file.write(text)
|
| 86 |
+
|
| 87 |
+
self.stdout.write(text)
|
| 88 |
+
|
| 89 |
+
if self.should_flush:
|
| 90 |
+
self.flush()
|
| 91 |
+
|
| 92 |
+
def flush(self) -> None:
|
| 93 |
+
"""Flush written text to both stdout and a file, if open."""
|
| 94 |
+
if self.file is not None:
|
| 95 |
+
self.file.flush()
|
| 96 |
+
|
| 97 |
+
self.stdout.flush()
|
| 98 |
+
|
| 99 |
+
def close(self) -> None:
|
| 100 |
+
"""Flush, close possible files, and remove stdout/stderr mirroring."""
|
| 101 |
+
self.flush()
|
| 102 |
+
|
| 103 |
+
# if using multiple loggers, prevent closing in wrong order
|
| 104 |
+
if sys.stdout is self:
|
| 105 |
+
sys.stdout = self.stdout
|
| 106 |
+
if sys.stderr is self:
|
| 107 |
+
sys.stderr = self.stderr
|
| 108 |
+
|
| 109 |
+
if self.file is not None:
|
| 110 |
+
self.file.close()
|
| 111 |
+
self.file = None
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# Cache directories
|
| 115 |
+
# ------------------------------------------------------------------------------------------
|
| 116 |
+
|
| 117 |
+
_dnnlib_cache_dir = None
|
| 118 |
+
|
| 119 |
+
def set_cache_dir(path: str) -> None:
|
| 120 |
+
global _dnnlib_cache_dir
|
| 121 |
+
_dnnlib_cache_dir = path
|
| 122 |
+
|
| 123 |
+
def make_cache_dir_path(*paths: str) -> str:
|
| 124 |
+
if _dnnlib_cache_dir is not None:
|
| 125 |
+
return os.path.join(_dnnlib_cache_dir, *paths)
|
| 126 |
+
if 'DNNLIB_CACHE_DIR' in os.environ:
|
| 127 |
+
return os.path.join(os.environ['DNNLIB_CACHE_DIR'], *paths)
|
| 128 |
+
if 'HOME' in os.environ:
|
| 129 |
+
return os.path.join(os.environ['HOME'], '.cache', 'dnnlib', *paths)
|
| 130 |
+
if 'USERPROFILE' in os.environ:
|
| 131 |
+
return os.path.join(os.environ['USERPROFILE'], '.cache', 'dnnlib', *paths)
|
| 132 |
+
return os.path.join(tempfile.gettempdir(), '.cache', 'dnnlib', *paths)
|
| 133 |
+
|
| 134 |
+
# Small util functions
|
| 135 |
+
# ------------------------------------------------------------------------------------------
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def format_time(seconds: Union[int, float]) -> str:
|
| 139 |
+
"""Convert the seconds to human readable string with days, hours, minutes and seconds."""
|
| 140 |
+
s = int(np.rint(seconds))
|
| 141 |
+
|
| 142 |
+
if s < 60:
|
| 143 |
+
return "{0}s".format(s)
|
| 144 |
+
elif s < 60 * 60:
|
| 145 |
+
return "{0}m {1:02}s".format(s // 60, s % 60)
|
| 146 |
+
elif s < 24 * 60 * 60:
|
| 147 |
+
return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60)
|
| 148 |
+
else:
|
| 149 |
+
return "{0}d {1:02}h {2:02}m".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def format_time_brief(seconds: Union[int, float]) -> str:
|
| 153 |
+
"""Convert the seconds to human readable string with days, hours, minutes and seconds."""
|
| 154 |
+
s = int(np.rint(seconds))
|
| 155 |
+
|
| 156 |
+
if s < 60:
|
| 157 |
+
return "{0}s".format(s)
|
| 158 |
+
elif s < 60 * 60:
|
| 159 |
+
return "{0}m {1:02}s".format(s // 60, s % 60)
|
| 160 |
+
elif s < 24 * 60 * 60:
|
| 161 |
+
return "{0}h {1:02}m".format(s // (60 * 60), (s // 60) % 60)
|
| 162 |
+
else:
|
| 163 |
+
return "{0}d {1:02}h".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def ask_yes_no(question: str) -> bool:
|
| 167 |
+
"""Ask the user the question until the user inputs a valid answer."""
|
| 168 |
+
while True:
|
| 169 |
+
try:
|
| 170 |
+
print("{0} [y/n]".format(question))
|
| 171 |
+
return strtobool(input().lower())
|
| 172 |
+
except ValueError:
|
| 173 |
+
pass
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def tuple_product(t: Tuple) -> Any:
|
| 177 |
+
"""Calculate the product of the tuple elements."""
|
| 178 |
+
result = 1
|
| 179 |
+
|
| 180 |
+
for v in t:
|
| 181 |
+
result *= v
|
| 182 |
+
|
| 183 |
+
return result
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
_str_to_ctype = {
|
| 187 |
+
"uint8": ctypes.c_ubyte,
|
| 188 |
+
"uint16": ctypes.c_uint16,
|
| 189 |
+
"uint32": ctypes.c_uint32,
|
| 190 |
+
"uint64": ctypes.c_uint64,
|
| 191 |
+
"int8": ctypes.c_byte,
|
| 192 |
+
"int16": ctypes.c_int16,
|
| 193 |
+
"int32": ctypes.c_int32,
|
| 194 |
+
"int64": ctypes.c_int64,
|
| 195 |
+
"float32": ctypes.c_float,
|
| 196 |
+
"float64": ctypes.c_double
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]:
|
| 201 |
+
"""Given a type name string (or an object having a __name__ attribute), return matching Numpy and ctypes types that have the same size in bytes."""
|
| 202 |
+
type_str = None
|
| 203 |
+
|
| 204 |
+
if isinstance(type_obj, str):
|
| 205 |
+
type_str = type_obj
|
| 206 |
+
elif hasattr(type_obj, "__name__"):
|
| 207 |
+
type_str = type_obj.__name__
|
| 208 |
+
elif hasattr(type_obj, "name"):
|
| 209 |
+
type_str = type_obj.name
|
| 210 |
+
else:
|
| 211 |
+
raise RuntimeError("Cannot infer type name from input")
|
| 212 |
+
|
| 213 |
+
assert type_str in _str_to_ctype.keys()
|
| 214 |
+
|
| 215 |
+
my_dtype = np.dtype(type_str)
|
| 216 |
+
my_ctype = _str_to_ctype[type_str]
|
| 217 |
+
|
| 218 |
+
assert my_dtype.itemsize == ctypes.sizeof(my_ctype)
|
| 219 |
+
|
| 220 |
+
return my_dtype, my_ctype
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def is_pickleable(obj: Any) -> bool:
|
| 224 |
+
try:
|
| 225 |
+
with io.BytesIO() as stream:
|
| 226 |
+
pickle.dump(obj, stream)
|
| 227 |
+
return True
|
| 228 |
+
except:
|
| 229 |
+
return False
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
# Functionality to import modules/objects by name, and call functions by name
|
| 233 |
+
# ------------------------------------------------------------------------------------------
|
| 234 |
+
|
| 235 |
+
def get_module_from_obj_name(obj_name: str) -> Tuple[types.ModuleType, str]:
|
| 236 |
+
"""Searches for the underlying module behind the name to some python object.
|
| 237 |
+
Returns the module and the object name (original name with module part removed)."""
|
| 238 |
+
|
| 239 |
+
# allow convenience shorthands, substitute them by full names
|
| 240 |
+
obj_name = re.sub("^np.", "numpy.", obj_name)
|
| 241 |
+
obj_name = re.sub("^tf.", "tensorflow.", obj_name)
|
| 242 |
+
|
| 243 |
+
# list alternatives for (module_name, local_obj_name)
|
| 244 |
+
parts = obj_name.split(".")
|
| 245 |
+
name_pairs = [(".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1)]
|
| 246 |
+
|
| 247 |
+
# try each alternative in turn
|
| 248 |
+
for module_name, local_obj_name in name_pairs:
|
| 249 |
+
try:
|
| 250 |
+
module = importlib.import_module(module_name) # may raise ImportError
|
| 251 |
+
get_obj_from_module(module, local_obj_name) # may raise AttributeError
|
| 252 |
+
return module, local_obj_name
|
| 253 |
+
except:
|
| 254 |
+
pass
|
| 255 |
+
|
| 256 |
+
# maybe some of the modules themselves contain errors?
|
| 257 |
+
for module_name, _local_obj_name in name_pairs:
|
| 258 |
+
try:
|
| 259 |
+
importlib.import_module(module_name) # may raise ImportError
|
| 260 |
+
except ImportError:
|
| 261 |
+
if not str(sys.exc_info()[1]).startswith("No module named '" + module_name + "'"):
|
| 262 |
+
raise
|
| 263 |
+
|
| 264 |
+
# maybe the requested attribute is missing?
|
| 265 |
+
for module_name, local_obj_name in name_pairs:
|
| 266 |
+
try:
|
| 267 |
+
module = importlib.import_module(module_name) # may raise ImportError
|
| 268 |
+
get_obj_from_module(module, local_obj_name) # may raise AttributeError
|
| 269 |
+
except ImportError:
|
| 270 |
+
pass
|
| 271 |
+
|
| 272 |
+
# we are out of luck, but we have no idea why
|
| 273 |
+
raise ImportError(obj_name)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def get_obj_from_module(module: types.ModuleType, obj_name: str) -> Any:
|
| 277 |
+
"""Traverses the object name and returns the last (rightmost) python object."""
|
| 278 |
+
if obj_name == '':
|
| 279 |
+
return module
|
| 280 |
+
obj = module
|
| 281 |
+
for part in obj_name.split("."):
|
| 282 |
+
obj = getattr(obj, part)
|
| 283 |
+
return obj
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def get_obj_by_name(name: str) -> Any:
|
| 287 |
+
"""Finds the python object with the given name."""
|
| 288 |
+
module, obj_name = get_module_from_obj_name(name)
|
| 289 |
+
return get_obj_from_module(module, obj_name)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def call_func_by_name(*args, func_name: str = None, **kwargs) -> Any:
|
| 293 |
+
"""Finds the python object with the given name and calls it as a function."""
|
| 294 |
+
assert func_name is not None
|
| 295 |
+
func_obj = get_obj_by_name(func_name)
|
| 296 |
+
assert callable(func_obj)
|
| 297 |
+
return func_obj(*args, **kwargs)
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def construct_class_by_name(*args, class_name: str = None, **kwargs) -> Any:
|
| 301 |
+
"""Finds the python class with the given name and constructs it with the given arguments."""
|
| 302 |
+
return call_func_by_name(*args, func_name=class_name, **kwargs)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def get_module_dir_by_obj_name(obj_name: str) -> str:
|
| 306 |
+
"""Get the directory path of the module containing the given object name."""
|
| 307 |
+
module, _ = get_module_from_obj_name(obj_name)
|
| 308 |
+
return os.path.dirname(inspect.getfile(module))
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def is_top_level_function(obj: Any) -> bool:
|
| 312 |
+
"""Determine whether the given object is a top-level function, i.e., defined at module scope using 'def'."""
|
| 313 |
+
return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def get_top_level_function_name(obj: Any) -> str:
|
| 317 |
+
"""Return the fully-qualified name of a top-level function."""
|
| 318 |
+
assert is_top_level_function(obj)
|
| 319 |
+
module = obj.__module__
|
| 320 |
+
if module == '__main__':
|
| 321 |
+
module = os.path.splitext(os.path.basename(sys.modules[module].__file__))[0]
|
| 322 |
+
return module + "." + obj.__name__
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
# File system helpers
|
| 326 |
+
# ------------------------------------------------------------------------------------------
|
| 327 |
+
|
| 328 |
+
def list_dir_recursively_with_ignore(dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False) -> List[Tuple[str, str]]:
|
| 329 |
+
"""List all files recursively in a given directory while ignoring given file and directory names.
|
| 330 |
+
Returns list of tuples containing both absolute and relative paths."""
|
| 331 |
+
assert os.path.isdir(dir_path)
|
| 332 |
+
base_name = os.path.basename(os.path.normpath(dir_path))
|
| 333 |
+
|
| 334 |
+
if ignores is None:
|
| 335 |
+
ignores = []
|
| 336 |
+
|
| 337 |
+
result = []
|
| 338 |
+
|
| 339 |
+
for root, dirs, files in os.walk(dir_path, topdown=True):
|
| 340 |
+
for ignore_ in ignores:
|
| 341 |
+
dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)]
|
| 342 |
+
|
| 343 |
+
# dirs need to be edited in-place
|
| 344 |
+
for d in dirs_to_remove:
|
| 345 |
+
dirs.remove(d)
|
| 346 |
+
|
| 347 |
+
files = [f for f in files if not fnmatch.fnmatch(f, ignore_)]
|
| 348 |
+
|
| 349 |
+
absolute_paths = [os.path.join(root, f) for f in files]
|
| 350 |
+
relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths]
|
| 351 |
+
|
| 352 |
+
if add_base_to_relative:
|
| 353 |
+
relative_paths = [os.path.join(base_name, p) for p in relative_paths]
|
| 354 |
+
|
| 355 |
+
assert len(absolute_paths) == len(relative_paths)
|
| 356 |
+
result += zip(absolute_paths, relative_paths)
|
| 357 |
+
|
| 358 |
+
return result
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def copy_files_and_create_dirs(files: List[Tuple[str, str]]) -> None:
|
| 362 |
+
"""Takes in a list of tuples of (src, dst) paths and copies files.
|
| 363 |
+
Will create all necessary directories."""
|
| 364 |
+
for file in files:
|
| 365 |
+
target_dir_name = os.path.dirname(file[1])
|
| 366 |
+
|
| 367 |
+
# will create all intermediate-level directories
|
| 368 |
+
if not os.path.exists(target_dir_name):
|
| 369 |
+
os.makedirs(target_dir_name)
|
| 370 |
+
|
| 371 |
+
shutil.copyfile(file[0], file[1])
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
# URL helpers
|
| 375 |
+
# ------------------------------------------------------------------------------------------
|
| 376 |
+
|
| 377 |
+
def is_url(obj: Any, allow_file_urls: bool = False) -> bool:
|
| 378 |
+
"""Determine whether the given object is a valid URL string."""
|
| 379 |
+
if not isinstance(obj, str) or not "://" in obj:
|
| 380 |
+
return False
|
| 381 |
+
if allow_file_urls and obj.startswith('file://'):
|
| 382 |
+
return True
|
| 383 |
+
try:
|
| 384 |
+
res = requests.compat.urlparse(obj)
|
| 385 |
+
if not res.scheme or not res.netloc or not "." in res.netloc:
|
| 386 |
+
return False
|
| 387 |
+
res = requests.compat.urlparse(requests.compat.urljoin(obj, "/"))
|
| 388 |
+
if not res.scheme or not res.netloc or not "." in res.netloc:
|
| 389 |
+
return False
|
| 390 |
+
except:
|
| 391 |
+
return False
|
| 392 |
+
return True
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def open_url(url: str, cache_dir: str = None, num_attempts: int = 10, verbose: bool = True, return_filename: bool = False, cache: bool = True) -> Any:
|
| 396 |
+
"""Download the given URL and return a binary-mode file object to access the data."""
|
| 397 |
+
assert num_attempts >= 1
|
| 398 |
+
assert not (return_filename and (not cache))
|
| 399 |
+
|
| 400 |
+
# Doesn't look like an URL scheme so interpret it as a local filename.
|
| 401 |
+
if not re.match('^[a-z]+://', url):
|
| 402 |
+
return url if return_filename else open(url, "rb")
|
| 403 |
+
|
| 404 |
+
# Handle file URLs. This code handles unusual file:// patterns that
|
| 405 |
+
# arise on Windows:
|
| 406 |
+
#
|
| 407 |
+
# file:///c:/foo.txt
|
| 408 |
+
#
|
| 409 |
+
# which would translate to a local '/c:/foo.txt' filename that's
|
| 410 |
+
# invalid. Drop the forward slash for such pathnames.
|
| 411 |
+
#
|
| 412 |
+
# If you touch this code path, you should test it on both Linux and
|
| 413 |
+
# Windows.
|
| 414 |
+
#
|
| 415 |
+
# Some internet resources suggest using urllib.request.url2pathname() but
|
| 416 |
+
# but that converts forward slashes to backslashes and this causes
|
| 417 |
+
# its own set of problems.
|
| 418 |
+
if url.startswith('file://'):
|
| 419 |
+
filename = urllib.parse.urlparse(url).path
|
| 420 |
+
if re.match(r'^/[a-zA-Z]:', filename):
|
| 421 |
+
filename = filename[1:]
|
| 422 |
+
return filename if return_filename else open(filename, "rb")
|
| 423 |
+
|
| 424 |
+
assert is_url(url)
|
| 425 |
+
|
| 426 |
+
# Lookup from cache.
|
| 427 |
+
if cache_dir is None:
|
| 428 |
+
cache_dir = make_cache_dir_path('downloads')
|
| 429 |
+
|
| 430 |
+
url_md5 = hashlib.md5(url.encode("utf-8")).hexdigest()
|
| 431 |
+
if cache:
|
| 432 |
+
cache_files = glob.glob(os.path.join(cache_dir, url_md5 + "_*"))
|
| 433 |
+
if len(cache_files) == 1:
|
| 434 |
+
filename = cache_files[0]
|
| 435 |
+
return filename if return_filename else open(filename, "rb")
|
| 436 |
+
|
| 437 |
+
# Download.
|
| 438 |
+
url_name = None
|
| 439 |
+
url_data = None
|
| 440 |
+
with requests.Session() as session:
|
| 441 |
+
if verbose:
|
| 442 |
+
print("Downloading %s ..." % url, end="", flush=True)
|
| 443 |
+
for attempts_left in reversed(range(num_attempts)):
|
| 444 |
+
try:
|
| 445 |
+
with session.get(url) as res:
|
| 446 |
+
res.raise_for_status()
|
| 447 |
+
if len(res.content) == 0:
|
| 448 |
+
raise IOError("No data received")
|
| 449 |
+
|
| 450 |
+
if len(res.content) < 8192:
|
| 451 |
+
content_str = res.content.decode("utf-8")
|
| 452 |
+
if "download_warning" in res.headers.get("Set-Cookie", ""):
|
| 453 |
+
links = [html.unescape(link) for link in content_str.split('"') if "export=download" in link]
|
| 454 |
+
if len(links) == 1:
|
| 455 |
+
url = requests.compat.urljoin(url, links[0])
|
| 456 |
+
raise IOError("Google Drive virus checker nag")
|
| 457 |
+
if "Google Drive - Quota exceeded" in content_str:
|
| 458 |
+
raise IOError("Google Drive download quota exceeded -- please try again later")
|
| 459 |
+
|
| 460 |
+
match = re.search(r'filename="([^"]*)"', res.headers.get("Content-Disposition", ""))
|
| 461 |
+
url_name = match[1] if match else url
|
| 462 |
+
url_data = res.content
|
| 463 |
+
if verbose:
|
| 464 |
+
print(" done")
|
| 465 |
+
break
|
| 466 |
+
except KeyboardInterrupt:
|
| 467 |
+
raise
|
| 468 |
+
except:
|
| 469 |
+
if not attempts_left:
|
| 470 |
+
if verbose:
|
| 471 |
+
print(" failed")
|
| 472 |
+
raise
|
| 473 |
+
if verbose:
|
| 474 |
+
print(".", end="", flush=True)
|
| 475 |
+
|
| 476 |
+
# Save to cache.
|
| 477 |
+
if cache:
|
| 478 |
+
safe_name = re.sub(r"[^0-9a-zA-Z-._]", "_", url_name)
|
| 479 |
+
safe_name = safe_name[:min(len(safe_name), 128)]
|
| 480 |
+
cache_file = os.path.join(cache_dir, url_md5 + "_" + safe_name)
|
| 481 |
+
temp_file = os.path.join(cache_dir, "tmp_" + uuid.uuid4().hex + "_" + url_md5 + "_" + safe_name)
|
| 482 |
+
os.makedirs(cache_dir, exist_ok=True)
|
| 483 |
+
with open(temp_file, "wb") as f:
|
| 484 |
+
f.write(url_data)
|
| 485 |
+
os.replace(temp_file, cache_file) # atomic
|
| 486 |
+
if return_filename:
|
| 487 |
+
return cache_file
|
| 488 |
+
|
| 489 |
+
# Return data as file object.
|
| 490 |
+
assert not return_filename
|
| 491 |
+
return io.BytesIO(url_data)
|
utils/evaluation/KAD_evaluate.py
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import os
|
| 6 |
+
import omegaconf
|
| 7 |
+
import numpy as np
|
| 8 |
+
import glob
|
| 9 |
+
import pyloudnorm as pyln
|
| 10 |
+
import sys
|
| 11 |
+
#move to the parent directory to import datasets
|
| 12 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
| 13 |
+
|
| 14 |
+
from datasets.eval_benchmark import load_audio
|
| 15 |
+
from utils.evaluation.dist_metrics import KADFeatures
|
| 16 |
+
|
| 17 |
+
# see device 1
|
| 18 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
| 19 |
+
|
| 20 |
+
path_test_set="/scratch/elec/t412-asp/automix/MDX_TM_benchmark"
|
| 21 |
+
|
| 22 |
+
set="MDX_TM"
|
| 23 |
+
|
| 24 |
+
features=[
|
| 25 |
+
"AFxRep",
|
| 26 |
+
"FxEncoder",
|
| 27 |
+
"FxEncoder++",
|
| 28 |
+
"CLAP",
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
KAD_metrics={}
|
| 33 |
+
|
| 34 |
+
KAD_args = {
|
| 35 |
+
"do_PCA_figure": False,
|
| 36 |
+
"do_TSNE_figure": False,
|
| 37 |
+
"kernel": "gaussian", #kernel to use for the KAD metric
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
KAD_args = omegaconf.OmegaConf.create(KAD_args)
|
| 41 |
+
|
| 42 |
+
for feature in features:
|
| 43 |
+
if feature=="AFxRep":
|
| 44 |
+
AFxRep_args = {
|
| 45 |
+
"distance_type": "cosine", # not used
|
| 46 |
+
"ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src/tmp/afx-rep.ckpt"
|
| 47 |
+
}
|
| 48 |
+
AFxRep_args = omegaconf.OmegaConf.create(AFxRep_args)
|
| 49 |
+
KAD_metrics[feature] = KADFeatures(type="AFxRep", sample_rate=44100, AFxRep_args=AFxRep_args, KAD_args=KAD_args)
|
| 50 |
+
elif feature=="FxEncoder":
|
| 51 |
+
fx_encoder_args = {
|
| 52 |
+
"distance_type": "cosine", # not used
|
| 53 |
+
"ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src/utils/feature_extractors/ckpt/fxenc_default.pt"
|
| 54 |
+
}
|
| 55 |
+
fx_encoder_args = omegaconf.OmegaConf.create(fx_encoder_args)
|
| 56 |
+
KAD_metrics[feature] = KADFeatures(type="fx_encoder", sample_rate=44100, fx_encoder_args=fx_encoder_args, KAD_args=KAD_args)
|
| 57 |
+
elif feature=="FxEncoder++":
|
| 58 |
+
fx_encoder_plusplus_args = {
|
| 59 |
+
"distance_type": "cosine", # not used
|
| 60 |
+
"ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src_clean/checkpoints/fxenc_plusplus_default.pt"
|
| 61 |
+
}
|
| 62 |
+
fx_encoder_plusplus_args = omegaconf.OmegaConf.create(fx_encoder_plusplus_args)
|
| 63 |
+
KAD_metrics[feature] = KADFeatures(type="fx_encoder_++", sample_rate=44100, fx_encoder_plusplus_args=fx_encoder_plusplus_args, KAD_args=KAD_args)
|
| 64 |
+
|
| 65 |
+
elif feature=="CLAP":
|
| 66 |
+
clap_args = {
|
| 67 |
+
"ckpt_path": "/scratch/work/molinee2/projects/project_mfm_eloi/src_clean/checkpoints/music_audioset_epoch_15_esc_90.14.patched.pt",
|
| 68 |
+
"distance_type": "cosine",
|
| 69 |
+
"normalize": True, # if True, the features will be normalized
|
| 70 |
+
"use_adaptor": False, # if True, the features will be adapted to the CLAP space
|
| 71 |
+
"adaptor_checkpoint":None,
|
| 72 |
+
"adaptor_type":None,
|
| 73 |
+
"add_noise": False, # if True, the features will be augmented with orthogonal noise
|
| 74 |
+
"noise_sigma": 0 # sigma of the orthogonal noise to
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
clap_args = omegaconf.OmegaConf.create(clap_args)
|
| 78 |
+
KAD_metrics[feature] = KADFeatures(type="CLAP", sample_rate=44100, CLAP_args=clap_args, KAD_args=KAD_args)
|
| 79 |
+
|
| 80 |
+
elif feature=="bark":
|
| 81 |
+
bark_args = {
|
| 82 |
+
"distance_type": "cosine", # not used
|
| 83 |
+
"normalize": True, # if True, the features will be normalized
|
| 84 |
+
}
|
| 85 |
+
bark_args = omegaconf.OmegaConf.create(bark_args)
|
| 86 |
+
KAD_metrics[feature] = KADFeatures(type="bark", sample_rate=44100, bark_args=bark_args, KAD_args=KAD_args, normalize=True)
|
| 87 |
+
|
| 88 |
+
else:
|
| 89 |
+
raise ValueError(f"Unknown feature: {feature}")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_ref_file_path(dir):
|
| 93 |
+
ref_mixture = os.path.join(dir,"mix", "mix.wav")
|
| 94 |
+
return ref_mixture
|
| 95 |
+
|
| 96 |
+
pylnmeter = pyln.Meter(44100) # Create a meter for 44100 Hz sampling rate
|
| 97 |
+
|
| 98 |
+
def loudness_normalize(audio, target_loudness=-23.0):
|
| 99 |
+
"""
|
| 100 |
+
Normalize the loudness of the audio to a target level.
|
| 101 |
+
"""
|
| 102 |
+
audio= np.array(audio, dtype=np.float32).T
|
| 103 |
+
loudness = pylnmeter.integrated_loudness(audio)
|
| 104 |
+
|
| 105 |
+
# loudness normalize audio to -12 dB LUFS
|
| 106 |
+
loudness_normalized_audio = pyln.normalize.loudness(audio, loudness, -14.0)
|
| 107 |
+
|
| 108 |
+
return torch.tensor(loudness_normalized_audio.T, dtype=torch.float32)
|
| 109 |
+
|
| 110 |
+
song_dirs= sorted(glob.glob(os.path.join(path_test_set, "*")))
|
| 111 |
+
|
| 112 |
+
reference_dict = {}
|
| 113 |
+
|
| 114 |
+
for song_dir in song_dirs:
|
| 115 |
+
|
| 116 |
+
song_name = os.path.basename(song_dir)
|
| 117 |
+
segment_subdirs = sorted(glob.glob(os.path.join(song_dir, "*")))
|
| 118 |
+
|
| 119 |
+
song_id=os.path.basename(song_dir)
|
| 120 |
+
|
| 121 |
+
for segment_dir in segment_subdirs:
|
| 122 |
+
|
| 123 |
+
segment= os.path.basename(segment_dir)
|
| 124 |
+
id= f"{song_id}_{segment}"
|
| 125 |
+
|
| 126 |
+
ref_file_path= get_ref_file_path(segment_dir)
|
| 127 |
+
mix_ref, fs=load_audio(str(ref_file_path), stereo=True)
|
| 128 |
+
assert fs==44100, "Expected sampling rate of 44100 Hz"
|
| 129 |
+
mix_ref = loudness_normalize(mix_ref)
|
| 130 |
+
reference_dict[id] = mix_ref
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
# create dataframe colums are features, rows are methods
|
| 134 |
+
dataframe= pd.DataFrame(columns=["method"] +list(KAD_metrics.keys()))
|
| 135 |
+
|
| 136 |
+
filename_results="results_eval/results_KAD"+f"_{set}_test.csv"
|
| 137 |
+
|
| 138 |
+
#methods=[ "fxnorm_automix_S_Lb", "fxnorm_automix_L_Lb",]
|
| 139 |
+
#methods=[ "proposed_random_churn","S3_random_churn", "S4_random_churn", "only_rms_S4_random_churn", "only_rms_random_churn" ]
|
| 140 |
+
#methods=[ "proposed_public", "only_rms_public" ]j
|
| 141 |
+
#methods=[ "S1internal_S2public"]
|
| 142 |
+
#methods=[ "S1internal_S2publicv3", "S1publicv3_S2publicv3", "S1publicv3_S2internal"]
|
| 143 |
+
#methods=[ "S1public_S2publicv3"]
|
| 144 |
+
#methods=[ "S1public_S2publicv3", "S1public_S2publicv3_oracle", "S1public_S2publicv3_rms"]
|
| 145 |
+
#methods=["mst_oracle", "equal_loudness", "diff_baseline", "proposed_oracle", "proposed_random", "only_rms_random" , "proposed_centroid_close", "proposed_centroid_far", "only_rms_centroid_close", "only_rms_centroid_far"]
|
| 146 |
+
#methods=["S4_random"]
|
| 147 |
+
#methods=["onlyrms_3108"]
|
| 148 |
+
#methods=["publicv3_2"]
|
| 149 |
+
#methods=["mst_oracle", "equal_loudness", "diff_baseline", "proposed_oracle", "proposed_random_3108_v2", "prop_random_indep_3108", "S4_random", "S3_random", "WUN_4instr", "publicv3", "DMC_14instr", "fxnorm_automix_S_Lb", "fxnorm_automix_L_Lb"]
|
| 150 |
+
methods=[ "tencydb_test", "publicv3_test"]
|
| 151 |
+
#methods=["prop_random_indep", "only_rms_indep"]
|
| 152 |
+
#methods=["WUN_4instr"]
|
| 153 |
+
#methods=["diff_baseline"]
|
| 154 |
+
#methods=["internal_2708", "equal_loudness", "internal_2708_oracle"]
|
| 155 |
+
#methods=["WUN_4instr", "WUN_14instr", "DMC_14instr"]
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
for method in methods:
|
| 159 |
+
|
| 160 |
+
if method=="mst_oracle":
|
| 161 |
+
def get_pred_file_path(dir):
|
| 162 |
+
pred_mixture= os.path.join(dir,"mst_oracle", "mixture_output.wav")
|
| 163 |
+
return pred_mixture
|
| 164 |
+
elif method=="mst_oracle_multi":
|
| 165 |
+
def get_pred_file_path(dir):
|
| 166 |
+
pred_mixture= os.path.join(dir,"mst_oracle_multi", "mixture_output.wav")
|
| 167 |
+
return pred_mixture
|
| 168 |
+
elif method=="equal_loudness":
|
| 169 |
+
def get_pred_file_path(dir):
|
| 170 |
+
pred_mixture= os.path.join(dir,"anchor_equal_loudness", "mix.wav")
|
| 171 |
+
return pred_mixture
|
| 172 |
+
elif method=="diff_baseline":
|
| 173 |
+
def get_pred_file_path(dir):
|
| 174 |
+
pred_mixture= os.path.join(dir,"diff_baseline", "pred_mixture.wav")
|
| 175 |
+
return pred_mixture
|
| 176 |
+
elif method=="fxnorm_automix_S_Lb":
|
| 177 |
+
def get_pred_file_path(dir):
|
| 178 |
+
pred_mixture= os.path.join(dir,"fxnorm_automix_S_Lb_v2", "mixture_output.wav")
|
| 179 |
+
return pred_mixture
|
| 180 |
+
elif method=="fxnorm_automix_L_Lb":
|
| 181 |
+
def get_pred_file_path(dir):
|
| 182 |
+
pred_mixture= os.path.join(dir,"fxnorm_automix_L_Lb_v2", "mixture_output.wav")
|
| 183 |
+
return pred_mixture
|
| 184 |
+
elif method=="proposed_random":
|
| 185 |
+
def get_pred_file_path(dir):
|
| 186 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30", "random.wav")
|
| 187 |
+
return pred_mixture
|
| 188 |
+
elif method=="proposed_random_3108_v2":
|
| 189 |
+
def get_pred_file_path(dir):
|
| 190 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2", "random.wav")
|
| 191 |
+
return pred_mixture
|
| 192 |
+
elif method=="onlyrms_3108":
|
| 193 |
+
def get_pred_file_path(dir):
|
| 194 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2", "only_rms_random.wav")
|
| 195 |
+
return pred_mixture
|
| 196 |
+
elif method=="proposed_random_3108_v2_oracle":
|
| 197 |
+
def get_pred_file_path(dir):
|
| 198 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2_oracle", "random.wav")
|
| 199 |
+
return pred_mixture
|
| 200 |
+
elif method=="only_rms_random":
|
| 201 |
+
def get_pred_file_path(dir):
|
| 202 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30", "only_rms_random.wav")
|
| 203 |
+
return pred_mixture
|
| 204 |
+
elif method=="S3_random":
|
| 205 |
+
def get_pred_file_path(dir):
|
| 206 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S3v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30", "random.wav")
|
| 207 |
+
return pred_mixture
|
| 208 |
+
elif method=="S4_3108":
|
| 209 |
+
def get_pred_file_path(dir):
|
| 210 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S4v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2", "random.wav")
|
| 211 |
+
return pred_mixture
|
| 212 |
+
elif method=="publicv3":
|
| 213 |
+
def get_pred_file_path(dir):
|
| 214 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_publicv3_publicv3_MDX_TM_benchmark_cfg_1_T30_publicv3", "random.wav")
|
| 215 |
+
return pred_mixture
|
| 216 |
+
elif method=="publicv3_test":
|
| 217 |
+
def get_pred_file_path(dir):
|
| 218 |
+
pred_mixture= os.path.join(dir,"public_public_MDX_TM_benchmark_public_test_Sep29", "random.wav")
|
| 219 |
+
return pred_mixture
|
| 220 |
+
elif method=="tencydb_test":
|
| 221 |
+
def get_pred_file_path(dir):
|
| 222 |
+
pred_mixture= os.path.join(dir,"internal_TencyDB_internal_TencyMastering_MDX_TM_benchmark_internal_test_Sep29", "random.wav")
|
| 223 |
+
return pred_mixture
|
| 224 |
+
elif method=="prop_random_indep_3108":
|
| 225 |
+
def get_pred_file_path(dir):
|
| 226 |
+
pred_mixture= os.path.join(dir,"stylediffpipeline_S9v6_MF3wetv6_MDX_TM_benchmark_cfg_1_T30_3108_v2_independent", "random.wav")
|
| 227 |
+
return pred_mixture
|
| 228 |
+
elif method=="WUN_4instr":
|
| 229 |
+
def get_pred_file_path(dir):
|
| 230 |
+
pred_mixture= os.path.join(dir,"WUN_4instr", "pred_mixture.wav")
|
| 231 |
+
return pred_mixture
|
| 232 |
+
elif method=="DMC_14instr":
|
| 233 |
+
def get_pred_file_path(dir):
|
| 234 |
+
pred_mixture= os.path.join(dir,"DMC_14instr", "pred_mixture.wav")
|
| 235 |
+
return pred_mixture
|
| 236 |
+
elif method=="only_rms_public":
|
| 237 |
+
def get_pred_file_path(dir):
|
| 238 |
+
pred_mixture= os.path.join(dir,"ours_public", "mixture_processed_onlyrms.wav")
|
| 239 |
+
return pred_mixture
|
| 240 |
+
elif method=="proposed_oracle":
|
| 241 |
+
def get_pred_file_path(dir):
|
| 242 |
+
pred_mixture= os.path.join(dir,"oracle_proposed", "mix.wav")
|
| 243 |
+
return pred_mixture
|
| 244 |
+
elif method=="publicv3_oracle":
|
| 245 |
+
def get_pred_file_path(dir):
|
| 246 |
+
pred_mixture= os.path.join(dir,"oracle_proposed_public", "mix.wav")
|
| 247 |
+
return pred_mixture
|
| 248 |
+
else:
|
| 249 |
+
raise ValueError(f"Unknown method: {method}")
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
method_dict = {}
|
| 253 |
+
|
| 254 |
+
for song_dir in song_dirs:
|
| 255 |
+
|
| 256 |
+
song_name = os.path.basename(song_dir)
|
| 257 |
+
segment_subdirs = sorted(glob.glob(os.path.join(song_dir, "*")))
|
| 258 |
+
|
| 259 |
+
song_id=os.path.basename(song_dir)
|
| 260 |
+
|
| 261 |
+
for segment_dir in segment_subdirs:
|
| 262 |
+
|
| 263 |
+
segment= os.path.basename(segment_dir)
|
| 264 |
+
id= f"{song_id}_{segment}"
|
| 265 |
+
|
| 266 |
+
pred_file_path= get_pred_file_path(segment_dir)
|
| 267 |
+
pred_mixture, fs=load_audio(str(pred_file_path), stereo=True)
|
| 268 |
+
assert fs==44100, "Expected sampling rate of 44100 Hz"
|
| 269 |
+
pred_mixture = loudness_normalize(pred_mixture)
|
| 270 |
+
assert not np.isnan(pred_mixture).any(), f"NaN values found in predicted mixture for {id}"
|
| 271 |
+
method_dict[id] = pred_mixture
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
#create new empty row with key as "method"
|
| 275 |
+
dataframe.loc[method] = [None] * (len(KAD_metrics)+1)
|
| 276 |
+
|
| 277 |
+
for feature, metric in KAD_metrics.items():
|
| 278 |
+
|
| 279 |
+
KAD_distance, dict_output=metric.compute(reference_dict, method_dict, None)
|
| 280 |
+
|
| 281 |
+
print(f"KAD distance for method {method} and feature {feature}: {KAD_distance}")
|
| 282 |
+
|
| 283 |
+
#write the KAD distance to the dataframe
|
| 284 |
+
dataframe.at[method, "method"] = method
|
| 285 |
+
dataframe.at[method, feature] = KAD_distance
|
| 286 |
+
|
| 287 |
+
for output_key, output_value in dict_output.items():
|
| 288 |
+
if "figure" in output_key:
|
| 289 |
+
output_value.savefig(f"results_eval/{output_key}_{set}_{method}_{feature}.png")
|
| 290 |
+
print(f"Saved figure: {output_key}_{set}_{method}_{feature}.png")
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
dataframe.to_csv(filename_results, index=False)
|
| 295 |
+
print(f"Results saved to {filename_results}")
|
| 296 |
+
|
| 297 |
+
#except Exception as e:
|
| 298 |
+
|
| 299 |
+
# print(f"Error processing method {method}: {e}")
|
| 300 |
+
|
| 301 |
+
# dataframe.to_csv(filename_results, index=False)
|
| 302 |
+
# print(f"Probably incompleted Results saved to {filename_results}")
|
| 303 |
+
# continue
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
|
utils/evaluation/dist_metrics.py
ADDED
|
@@ -0,0 +1,971 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from numpy.lib.scimath import sqrt as scisqrt
|
| 3 |
+
from scipy import linalg
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torchaudio
|
| 6 |
+
import os
|
| 7 |
+
from importlib import import_module
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
from utils.feature_extractors.load_features import load_AFxRep, load_fx_encoder, load_fx_encoder_plusplus, load_CLAP
|
| 12 |
+
|
| 13 |
+
from utils.log import make_PCA_figure
|
| 14 |
+
|
| 15 |
+
SCALE_FACTOR = 100
|
| 16 |
+
|
| 17 |
+
def calc_kernel_audio_distance(
|
| 18 |
+
x: torch.Tensor,
|
| 19 |
+
y: torch.Tensor,
|
| 20 |
+
device: str,
|
| 21 |
+
bandwidth=None,
|
| 22 |
+
kernel='gaussian',
|
| 23 |
+
precision=torch.float32,
|
| 24 |
+
eps=1e-8
|
| 25 |
+
) -> torch.Tensor:
|
| 26 |
+
"""
|
| 27 |
+
Compute the Kernel Audio Distance (KAD) between two samples using PyTorch.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
x: The first set of embeddings of shape (m, embedding_dim).
|
| 31 |
+
y: The second set of embeddings of shape (n, embedding_dim).
|
| 32 |
+
cache_dirs: Directories to cache kernel statistics.
|
| 33 |
+
bandwidth: The bandwidth value for the Gaussian RBF kernel.
|
| 34 |
+
kernel: Kernel function to use ('gaussian', 'iq', 'imq').
|
| 35 |
+
precision: Type setting for matrix calculation precision.
|
| 36 |
+
eps: Small value to prevent division by zero.
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
The KAD between x and y embedding sets.
|
| 40 |
+
"""
|
| 41 |
+
# Ensure x and y are of the correct precision
|
| 42 |
+
x = x.to(dtype=precision, device=device)
|
| 43 |
+
y = y.to(dtype=precision, device=device)
|
| 44 |
+
|
| 45 |
+
assert bandwidth is not None, "Bandwidth must be provided for KAD calculation"
|
| 46 |
+
|
| 47 |
+
m, n = x.shape[0], y.shape[0]
|
| 48 |
+
|
| 49 |
+
# Define kernel functions
|
| 50 |
+
gamma = 1 / (2 * bandwidth**2 + eps)
|
| 51 |
+
if kernel == 'gaussian': # Gaussian Kernel
|
| 52 |
+
kernel = lambda a: torch.exp(-gamma * a)
|
| 53 |
+
elif kernel == 'iq': # Inverse Quadratic Kernel
|
| 54 |
+
kernel = lambda a: 1 / (1 + gamma * a)
|
| 55 |
+
elif kernel == 'imq': # Inverse Multiquadric Kernel
|
| 56 |
+
kernel = lambda a: 1 / torch.sqrt(1 + gamma * a)
|
| 57 |
+
else:
|
| 58 |
+
raise ValueError("Invalid kernel type. Valid kernels: 'gaussian', 'iq', 'imq'")
|
| 59 |
+
|
| 60 |
+
# Load x kernel statistics
|
| 61 |
+
xx = x @ x.T
|
| 62 |
+
x_sqnorms = torch.diagonal(xx)
|
| 63 |
+
d2_xx = x_sqnorms.unsqueeze(1) + x_sqnorms.unsqueeze(0) - 2 * xx # shape (m, m)
|
| 64 |
+
|
| 65 |
+
k_xx = kernel(d2_xx)
|
| 66 |
+
k_xx = k_xx - torch.diag(torch.diagonal(k_xx))
|
| 67 |
+
k_xx_mean = k_xx.sum() / (m * (m - 1))
|
| 68 |
+
|
| 69 |
+
# Load y kernel statistics
|
| 70 |
+
yy = y @ y.T
|
| 71 |
+
y_sqnorms = torch.diagonal(yy)
|
| 72 |
+
d2_yy = y_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * yy # shape (n, n)
|
| 73 |
+
|
| 74 |
+
k_yy = kernel(d2_yy)
|
| 75 |
+
k_yy = k_yy - torch.diag(torch.diagonal(k_yy))
|
| 76 |
+
k_yy_mean = k_yy.sum() / (n * (n - 1))
|
| 77 |
+
|
| 78 |
+
# Compute kernel statistics for xy
|
| 79 |
+
xy = x @ y.T
|
| 80 |
+
d2_xy = x_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * xy # shape (m, n)
|
| 81 |
+
k_xy = kernel(d2_xy)
|
| 82 |
+
k_xy_mean = k_xy.mean()
|
| 83 |
+
|
| 84 |
+
# Compute MMD
|
| 85 |
+
result = k_xx_mean + k_yy_mean - 2 * k_xy_mean
|
| 86 |
+
|
| 87 |
+
return result * SCALE_FACTOR
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def median_pairwise_distance(x, subsample=None):
|
| 91 |
+
"""
|
| 92 |
+
Compute the median pairwise distance of an embedding set.
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
x: torch.Tensor of shape (n_samples, embedding_dim)
|
| 96 |
+
subsample: int, number of random pairs to consider (optional)
|
| 97 |
+
|
| 98 |
+
Returns:
|
| 99 |
+
The median pairwise distance between points in x.
|
| 100 |
+
"""
|
| 101 |
+
x = torch.tensor(x, dtype=torch.float32)
|
| 102 |
+
n_samples = x.shape[0]
|
| 103 |
+
|
| 104 |
+
if subsample is not None and subsample < n_samples * (n_samples - 1) / 2:
|
| 105 |
+
# Randomly select pairs of indices
|
| 106 |
+
idx1 = torch.randint(0, n_samples, (subsample,))
|
| 107 |
+
idx2 = torch.randint(0, n_samples, (subsample,))
|
| 108 |
+
|
| 109 |
+
# Ensure idx1 != idx2
|
| 110 |
+
mask = idx1 == idx2
|
| 111 |
+
idx2[mask] = (idx2[mask] + 1) % n_samples
|
| 112 |
+
|
| 113 |
+
# Compute distances for selected pairs
|
| 114 |
+
distances = torch.sqrt(torch.sum((x[idx1] - x[idx2])**2, dim=1))
|
| 115 |
+
else:
|
| 116 |
+
# Compute all pairwise distances
|
| 117 |
+
distances = torch.pdist(x)
|
| 118 |
+
|
| 119 |
+
return torch.median(distances).item()
|
| 120 |
+
|
| 121 |
+
class DistMetric:
|
| 122 |
+
"""
|
| 123 |
+
Base class for pairwise metrics.
|
| 124 |
+
|
| 125 |
+
This class should be subclassed to implement specific pairwise metrics.
|
| 126 |
+
"""
|
| 127 |
+
def __init__(self,type, sample_rate,*args, **kwargs):
|
| 128 |
+
"""
|
| 129 |
+
Initialize the PairwiseMetric instance.
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
*args: Variable length argument list.
|
| 133 |
+
**kwargs: Arbitrary keyword arguments.
|
| 134 |
+
"""
|
| 135 |
+
self.type=type
|
| 136 |
+
self.sample_rate=sample_rate
|
| 137 |
+
|
| 138 |
+
if self.type == "fx_encoder":
|
| 139 |
+
self.model_args= kwargs.get("fx_encoder_args", None)
|
| 140 |
+
|
| 141 |
+
assert self.model_args is not None, "model_args must be provided for fx_encoder type"
|
| 142 |
+
|
| 143 |
+
self.distance_type=self.model_args.distance_type
|
| 144 |
+
|
| 145 |
+
self.feat_extractor = load_fx_encoder(self.model_args, self.device)
|
| 146 |
+
|
| 147 |
+
#self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
|
| 148 |
+
|
| 149 |
+
elif self.type == "fx_encoder_++":
|
| 150 |
+
self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
|
| 151 |
+
|
| 152 |
+
assert self.model_args is not None, "model_args must be provided for fx_encoder_plusplus type"
|
| 153 |
+
|
| 154 |
+
print(self.model_args)
|
| 155 |
+
|
| 156 |
+
self.distance_type=self.model_args.distance_type
|
| 157 |
+
|
| 158 |
+
self.feat_extractor = load_fx_encoder_plusplus(self.model_args, self.device)
|
| 159 |
+
|
| 160 |
+
#self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
|
| 161 |
+
|
| 162 |
+
elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
|
| 163 |
+
|
| 164 |
+
self.model_args= kwargs.get("AFxRep_args", None)
|
| 165 |
+
|
| 166 |
+
assert self.model_args is not None, "model_args must be provided for AFxRep type"
|
| 167 |
+
|
| 168 |
+
self.distance_type=self.model_args.distance_type
|
| 169 |
+
|
| 170 |
+
feat_extractor = load_AFxRep(self.model_args, self.device, sample_rate=self.sample_rate)
|
| 171 |
+
|
| 172 |
+
if self.type == "AFxRep-mid":
|
| 173 |
+
def feat_extractor_mid(x):
|
| 174 |
+
|
| 175 |
+
features= feat_extractor(x)
|
| 176 |
+
|
| 177 |
+
#print("features shape:", features.shape)
|
| 178 |
+
#divide by 2 to get mid and side features
|
| 179 |
+
|
| 180 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 181 |
+
|
| 182 |
+
return feat_mid
|
| 183 |
+
|
| 184 |
+
self.feat_extractor = feat_extractor_mid
|
| 185 |
+
|
| 186 |
+
elif self.type == "AFxRep-side":
|
| 187 |
+
def feat_extractor_side(x):
|
| 188 |
+
|
| 189 |
+
features= feat_extractor(x)
|
| 190 |
+
|
| 191 |
+
#divide by 2 to get mid and side features
|
| 192 |
+
|
| 193 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 194 |
+
|
| 195 |
+
return feat_side
|
| 196 |
+
|
| 197 |
+
self.feat_extractor = feat_extractor_side
|
| 198 |
+
else:
|
| 199 |
+
self.feat_extractor = feat_extractor
|
| 200 |
+
|
| 201 |
+
elif self.type == "CLAP":
|
| 202 |
+
self.model_args= kwargs.get("CLAP_args", None)
|
| 203 |
+
assert self.model_args is not None, "model_args must be provided for CLAP type"
|
| 204 |
+
|
| 205 |
+
CLAP_fn = load_CLAP(self.model_args, self.device)
|
| 206 |
+
self.feat_extractor=lambda x: CLAP_fn(x, None)
|
| 207 |
+
|
| 208 |
+
else:
|
| 209 |
+
raise ValueError(f"Unknown type: {self.type}. Supported types: fx_encoder, fx_encoder_plusplus, AFxRep-mid, AFxRep-side, AFxRep")
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def compute(self, *args, **kwargs):
|
| 213 |
+
raise NotImplementedError("Subclasses should implement this method.")
|
| 214 |
+
|
| 215 |
+
def extract_features(self, y, y_hat, x=None):
|
| 216 |
+
|
| 217 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 218 |
+
y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 219 |
+
|
| 220 |
+
with torch.no_grad():
|
| 221 |
+
feat_y= self.feat_extractor(y)
|
| 222 |
+
feat_y_hat= self.feat_extractor(y_hat)
|
| 223 |
+
|
| 224 |
+
if x is not None:
|
| 225 |
+
x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
|
| 226 |
+
with torch.no_grad():
|
| 227 |
+
feat_x= self.feat_extractor(x)
|
| 228 |
+
else:
|
| 229 |
+
feat_x=None
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
return feat_y, feat_y_hat, feat_x
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def do_TSNE_figure(self, dict_features_y, dict_features_y_hat, dict_features_x=None, fit_mode="target", dict_cluster=None):
|
| 236 |
+
"""
|
| 237 |
+
Perform PCA on the features and create a figure.
|
| 238 |
+
|
| 239 |
+
Args:
|
| 240 |
+
dict_features_y (dict): Dictionary containing features for the first set.
|
| 241 |
+
dict_features_y_hat (dict): Dictionary containing features for the second set.
|
| 242 |
+
|
| 243 |
+
Returns:
|
| 244 |
+
fig: The created figure.
|
| 245 |
+
"""
|
| 246 |
+
import matplotlib.pyplot as plt
|
| 247 |
+
from sklearn.decomposition import PCA
|
| 248 |
+
from sklearn.manifold import TSNE
|
| 249 |
+
|
| 250 |
+
y_values = list(dict_features_y.values())
|
| 251 |
+
y_values = torch.cat(y_values, dim=0)
|
| 252 |
+
|
| 253 |
+
y_hat_values = list(dict_features_y_hat.values())
|
| 254 |
+
y_hat_values = torch.cat(y_hat_values, dim=0)
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
if dict_cluster is not None:
|
| 258 |
+
clusters= list(dict_cluster.values())
|
| 259 |
+
clusters = [c.unsqueeze(0) if c.dim() == 0 else c for c in clusters]
|
| 260 |
+
clusters = torch.cat(clusters, dim=0)
|
| 261 |
+
|
| 262 |
+
#check different clusters (0,1,2,3...)
|
| 263 |
+
assert len(torch.unique(clusters)) == 2, "Only two clusters are supported for PCA visualization"
|
| 264 |
+
C0= clusters == 0
|
| 265 |
+
C1= clusters == 1
|
| 266 |
+
|
| 267 |
+
#manage Nans in y_values
|
| 268 |
+
y_values=torch.nan_to_num(y_values, nan=0)
|
| 269 |
+
y_hat_values=torch.nan_to_num(y_hat_values, nan=0)
|
| 270 |
+
|
| 271 |
+
#if self.pca is None:
|
| 272 |
+
self.tsne =TSNE(n_components=2, perplexity=30)
|
| 273 |
+
|
| 274 |
+
combined_data = torch.cat([y_values, y_hat_values], dim=0).cpu().numpy()
|
| 275 |
+
combined_result = self.tsne.fit_transform(combined_data)
|
| 276 |
+
|
| 277 |
+
# Split the results back
|
| 278 |
+
n_y = y_values.shape[0]
|
| 279 |
+
tsne_result = combined_result[:n_y]
|
| 280 |
+
tsne_result_hat = combined_result[n_y:]
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
if dict_cluster is not None:
|
| 284 |
+
data_dict = {
|
| 285 |
+
"y_C0": tsne_result[C0],
|
| 286 |
+
"y_hat_C0": tsne_result_hat[C0],
|
| 287 |
+
"y_C1": tsne_result[C1],
|
| 288 |
+
"y_hat_C1": tsne_result_hat[C1]
|
| 289 |
+
}
|
| 290 |
+
else:
|
| 291 |
+
data_dict = {
|
| 292 |
+
"y": tsne_result,
|
| 293 |
+
"y_hat": tsne_result_hat
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
fig= make_PCA_figure(data_dict, title=self.type + " TSNE")
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
return fig
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def do_PCA_figure(self, dict_features_y, dict_features_y_hat, dict_features_x=None, fit_mode="target", dict_cluster=None):
|
| 305 |
+
"""
|
| 306 |
+
Perform PCA on the features and create a figure.
|
| 307 |
+
|
| 308 |
+
Args:
|
| 309 |
+
dict_features_y (dict): Dictionary containing features for the first set.
|
| 310 |
+
dict_features_y_hat (dict): Dictionary containing features for the second set.
|
| 311 |
+
|
| 312 |
+
Returns:
|
| 313 |
+
fig: The created figure.
|
| 314 |
+
"""
|
| 315 |
+
import matplotlib.pyplot as plt
|
| 316 |
+
from sklearn.decomposition import PCA
|
| 317 |
+
|
| 318 |
+
y_values = list(dict_features_y.values())
|
| 319 |
+
y_values = torch.cat(y_values, dim=0)
|
| 320 |
+
|
| 321 |
+
y_hat_values = list(dict_features_y_hat.values())
|
| 322 |
+
y_hat_values = torch.cat(y_hat_values, dim=0)
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
if dict_cluster is not None:
|
| 326 |
+
clusters= list(dict_cluster.values())
|
| 327 |
+
clusters = [c.unsqueeze(0) if c.dim() == 0 else c for c in clusters]
|
| 328 |
+
clusters = torch.cat(clusters, dim=0)
|
| 329 |
+
|
| 330 |
+
#check different clusters (0,1,2,3...)
|
| 331 |
+
assert len(torch.unique(clusters)) == 2, "Only two clusters are supported for PCA visualization"
|
| 332 |
+
C0= clusters == 0
|
| 333 |
+
C1= clusters == 1
|
| 334 |
+
|
| 335 |
+
#manage Nans in y_values
|
| 336 |
+
y_values=torch.nan_to_num(y_values, nan=0)
|
| 337 |
+
y_hat_values=torch.nan_to_num(y_hat_values, nan=0)
|
| 338 |
+
|
| 339 |
+
if self.pca is None:
|
| 340 |
+
self.pca = PCA(n_components=2)
|
| 341 |
+
if fit_mode == "target":
|
| 342 |
+
pca_result = self.pca.fit_transform(y_values.cpu().numpy())
|
| 343 |
+
elif fit_mode == "all":
|
| 344 |
+
self.pca = self.pca.fit(torch.cat([y_values,y_hat_values], dim=0).cpu().numpy())
|
| 345 |
+
pca_result= self.pca.transform(y_values.cpu().numpy())
|
| 346 |
+
else:
|
| 347 |
+
pca_result = self.pca.transform(y_values.cpu().numpy())
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
#project y_hat values into the same PCA space
|
| 351 |
+
pca_result_hat = self.pca.transform(y_hat_values.cpu().numpy())
|
| 352 |
+
|
| 353 |
+
if dict_cluster is not None:
|
| 354 |
+
data_dict = {
|
| 355 |
+
"y_C0": pca_result[C0],
|
| 356 |
+
"y_hat_C0": pca_result_hat[C0],
|
| 357 |
+
"y_C1": pca_result[C1],
|
| 358 |
+
"y_hat_C1": pca_result_hat[C1]
|
| 359 |
+
}
|
| 360 |
+
else:
|
| 361 |
+
data_dict = {
|
| 362 |
+
"y": pca_result,
|
| 363 |
+
"y_hat": pca_result_hat
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
if dict_features_x is not None:
|
| 367 |
+
x_values = list(dict_features_x.values())
|
| 368 |
+
x_values = torch.cat(x_values, dim=0)
|
| 369 |
+
|
| 370 |
+
pca_result_x = self.pca.transform(x_values.cpu().numpy())
|
| 371 |
+
|
| 372 |
+
data_dict["x"] = pca_result_x
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
fig= make_PCA_figure(data_dict, title=self.type + " PCA")
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
return fig
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class KADFeatures(DistMetric):
|
| 385 |
+
"""
|
| 386 |
+
Class for computing the pairwise spectral metric.
|
| 387 |
+
|
| 388 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 389 |
+
to calculate the pairwise spectral metric.
|
| 390 |
+
"""
|
| 391 |
+
def __init__(self,
|
| 392 |
+
type=None,
|
| 393 |
+
sample_rate=44100,
|
| 394 |
+
KAD_args=None,
|
| 395 |
+
classwise=False,
|
| 396 |
+
*args, **kwargs):
|
| 397 |
+
"""
|
| 398 |
+
Initialize the PairwiseSpectral instance.
|
| 399 |
+
|
| 400 |
+
Args:
|
| 401 |
+
*args: Variable length argument list.
|
| 402 |
+
**kwargs: Arbitrary keyword arguments.
|
| 403 |
+
"""
|
| 404 |
+
self.type = type
|
| 405 |
+
|
| 406 |
+
self.classwise = classwise
|
| 407 |
+
|
| 408 |
+
self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
self.KAD_args = KAD_args
|
| 412 |
+
|
| 413 |
+
assert self.KAD_args is not None, "FAD_args must be provided"
|
| 414 |
+
|
| 415 |
+
if self.KAD_args.do_PCA_figure:
|
| 416 |
+
self.pca = None
|
| 417 |
+
|
| 418 |
+
self.bandwidth = None
|
| 419 |
+
|
| 420 |
+
super().__init__(self.type, sample_rate, *args, **kwargs)
|
| 421 |
+
|
| 422 |
+
def calculate_KAD_distance_classwise(self, dict_features_y, dict_features_y_hat, dict_cluster):
|
| 423 |
+
|
| 424 |
+
if dict_cluster is not None:
|
| 425 |
+
clusters= list(dict_cluster.values())
|
| 426 |
+
clusters = [c.unsqueeze(0) if c.dim() == 0 else c for c in clusters]
|
| 427 |
+
clusters = torch.cat(clusters, dim=0)
|
| 428 |
+
|
| 429 |
+
#check different clusters (0,1,2,3...)
|
| 430 |
+
assert len(torch.unique(clusters)) == 2, "Only two clusters are supported for PCA visualization"
|
| 431 |
+
C0= clusters == 0
|
| 432 |
+
C1= clusters == 1
|
| 433 |
+
|
| 434 |
+
y= torch.cat(list(dict_features_y.values()), dim=0)
|
| 435 |
+
y_hat= torch.cat(list(dict_features_y_hat.values()), dim=0)
|
| 436 |
+
|
| 437 |
+
y_C0= y[C0]
|
| 438 |
+
y_hat_C0= y_hat[C0]
|
| 439 |
+
|
| 440 |
+
with torch.no_grad():
|
| 441 |
+
KAD_C0=calc_kernel_audio_distance(y_C0, y_hat_C0, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
|
| 442 |
+
|
| 443 |
+
y_C1= y[C1]
|
| 444 |
+
y_hat_C1= y_hat[C1]
|
| 445 |
+
with torch.no_grad():
|
| 446 |
+
KAD_C1=calc_kernel_audio_distance(y_C1, y_hat_C1, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
|
| 447 |
+
|
| 448 |
+
KAD= (KAD_C0 + KAD_C1) / 2
|
| 449 |
+
|
| 450 |
+
return KAD.cpu().item()
|
| 451 |
+
def calculate_KAD_distance(self, dict_features_y, dict_features_y_hat):
|
| 452 |
+
y= torch.cat(list(dict_features_y.values()), dim=0)
|
| 453 |
+
y_hat= torch.cat(list(dict_features_y_hat.values()), dim=0)
|
| 454 |
+
with torch.no_grad():
|
| 455 |
+
KAD=calc_kernel_audio_distance(y, y_hat, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
|
| 456 |
+
return KAD.cpu().item()
|
| 457 |
+
|
| 458 |
+
def calculate_bandwidth(self, dict_features_y):
|
| 459 |
+
print("Calculating bandwidth...")
|
| 460 |
+
y= torch.cat(list(dict_features_y.values()), dim=0)
|
| 461 |
+
print("y shape:", y.shape)
|
| 462 |
+
self.bandwidth = median_pairwise_distance(y)
|
| 463 |
+
print("Bandwidth:", self.bandwidth)
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, dict_cluster=None, normalize=False, *args, **kwargs):
|
| 468 |
+
"""
|
| 469 |
+
Compute the pairwise spectral metric.
|
| 470 |
+
|
| 471 |
+
Args:
|
| 472 |
+
*args: Variable length argument list.
|
| 473 |
+
**kwargs: Arbitrary keyword arguments.
|
| 474 |
+
|
| 475 |
+
Returns:
|
| 476 |
+
The computed pairwise spectral metric.
|
| 477 |
+
"""
|
| 478 |
+
|
| 479 |
+
print("Computing KAD distance...")
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
if self.classwise:
|
| 483 |
+
assert dict_cluster is not None, "dict_cluster must be provided if classwise is True"
|
| 484 |
+
|
| 485 |
+
dict_features_y={}
|
| 486 |
+
dict_features_y_hat={}
|
| 487 |
+
#dict_features_x={}
|
| 488 |
+
|
| 489 |
+
if dict_y_hat is None:
|
| 490 |
+
print("computing KAD with style embeddings")
|
| 491 |
+
assert dict_p_hat is not None, "dict_p_hat must be provided if dict_y_hat is None"
|
| 492 |
+
|
| 493 |
+
for key in dict_y.keys():
|
| 494 |
+
y= dict_y[key]
|
| 495 |
+
#x= dict_x[key]
|
| 496 |
+
|
| 497 |
+
embed= dict_p_hat[key]
|
| 498 |
+
embed=torch.tensor(embed).to(self.device).unsqueeze(0)
|
| 499 |
+
|
| 500 |
+
embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
|
| 501 |
+
|
| 502 |
+
if self.type== "AFxRep-mid":
|
| 503 |
+
p_hat= embed_mid
|
| 504 |
+
elif self.type== "AFxRep-side":
|
| 505 |
+
p_hat= embed_side
|
| 506 |
+
elif self.type== "AFxRep":
|
| 507 |
+
p_hat= embed
|
| 508 |
+
|
| 509 |
+
#if x.shape[-2] == 1:
|
| 510 |
+
# x = x.repeat( 2, 1)
|
| 511 |
+
|
| 512 |
+
#assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
|
| 513 |
+
|
| 514 |
+
c, d=y.shape
|
| 515 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 516 |
+
|
| 517 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 518 |
+
|
| 519 |
+
y=y.T
|
| 520 |
+
#x=x.T
|
| 521 |
+
|
| 522 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 523 |
+
#x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
|
| 524 |
+
|
| 525 |
+
with torch.no_grad():
|
| 526 |
+
feat_y= self.feat_extractor(y)
|
| 527 |
+
#feat_x= self.feat_extractor(x)
|
| 528 |
+
|
| 529 |
+
assert p_hat.shape == feat_y.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_y.shape}"
|
| 530 |
+
#assert p_hat.shape == feat_x.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_x.shape}"
|
| 531 |
+
|
| 532 |
+
dict_features_y[key] = feat_y
|
| 533 |
+
#dict_features_x[key] = feat_x
|
| 534 |
+
dict_features_y_hat[key] = p_hat
|
| 535 |
+
|
| 536 |
+
else:
|
| 537 |
+
for key in dict_y.keys():
|
| 538 |
+
y= dict_y[key]
|
| 539 |
+
#x= dict_x[key]
|
| 540 |
+
y_hat= dict_y_hat[key]
|
| 541 |
+
|
| 542 |
+
#if x.shape[-2] == 1:
|
| 543 |
+
# x = x.repeat( 2, 1)
|
| 544 |
+
|
| 545 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 546 |
+
#assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
|
| 547 |
+
|
| 548 |
+
c, d=y.shape
|
| 549 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 550 |
+
|
| 551 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 552 |
+
|
| 553 |
+
y=y.T
|
| 554 |
+
y_hat=y_hat.T
|
| 555 |
+
#x=x.T
|
| 556 |
+
|
| 557 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 558 |
+
y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 559 |
+
#x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
|
| 560 |
+
|
| 561 |
+
with torch.no_grad():
|
| 562 |
+
#try:
|
| 563 |
+
feat_y= self.feat_extractor(y)
|
| 564 |
+
feat_y_hat= self.feat_extractor(y_hat)
|
| 565 |
+
#except Exception as e:
|
| 566 |
+
#print(f"Error extracting features for key {key}: {e}")
|
| 567 |
+
# continue
|
| 568 |
+
#feat_x= self.feat_extractor(x)
|
| 569 |
+
|
| 570 |
+
dict_features_y[key] = feat_y
|
| 571 |
+
dict_features_y_hat[key] = feat_y_hat
|
| 572 |
+
#dict_features_x[key] = feat_x
|
| 573 |
+
|
| 574 |
+
if normalize:
|
| 575 |
+
#compute normalization statistics wrt to reference features
|
| 576 |
+
y_values = list(dict_features_y.values())
|
| 577 |
+
y_values = torch.cat(y_values, dim=0)
|
| 578 |
+
mean= y_values.mean(dim=0, keepdim=True)
|
| 579 |
+
std= y_values.std(dim=0, keepdim=True)
|
| 580 |
+
|
| 581 |
+
#normalize features
|
| 582 |
+
for key in dict_features_y.keys():
|
| 583 |
+
dict_features_y[key] = (dict_features_y[key] - mean) / (std + 1e-8)
|
| 584 |
+
dict_features_y_hat[key] = (dict_features_y_hat[key] - mean) / (std + 1e-8)
|
| 585 |
+
|
| 586 |
+
|
| 587 |
+
dict_output = {}
|
| 588 |
+
if self.KAD_args.do_PCA_figure:
|
| 589 |
+
fig=self.do_PCA_figure(dict_features_y, dict_features_y_hat, fit_mode=self.KAD_args.PCA_fit_mode, dict_cluster=dict_cluster)
|
| 590 |
+
key= self.type+ "_PCA_figure"
|
| 591 |
+
dict_output = {key: fig}
|
| 592 |
+
|
| 593 |
+
if self.KAD_args.do_TSNE_figure:
|
| 594 |
+
fig=self.do_TSNE_figure(dict_features_y, dict_features_y_hat, fit_mode="all", dict_cluster=dict_cluster)
|
| 595 |
+
key= self.type+ "_TSNE_figure"
|
| 596 |
+
dict_output[key] = fig
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
# Compute mean features
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
# Compute the distance between the mean features
|
| 604 |
+
#FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
|
| 605 |
+
if self.bandwidth is None:
|
| 606 |
+
self.calculate_bandwidth(dict_features_y)
|
| 607 |
+
|
| 608 |
+
if self.classwise:
|
| 609 |
+
#calculate KAD distance for each class
|
| 610 |
+
KAD_distance = self.calculate_KAD_distance_classwise(dict_features_y, dict_features_y_hat, dict_cluster)
|
| 611 |
+
else:
|
| 612 |
+
|
| 613 |
+
KAD_distance = self.calculate_KAD_distance(dict_features_y, dict_features_y_hat)
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
return KAD_distance, dict_output
|
| 617 |
+
|
| 618 |
+
class FADFeatures(DistMetric):
|
| 619 |
+
"""
|
| 620 |
+
Class for computing the pairwise spectral metric.
|
| 621 |
+
|
| 622 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 623 |
+
to calculate the pairwise spectral metric.
|
| 624 |
+
"""
|
| 625 |
+
def __init__(self,
|
| 626 |
+
type=None,
|
| 627 |
+
sample_rate=44100,
|
| 628 |
+
FAD_args=None,
|
| 629 |
+
*args, **kwargs):
|
| 630 |
+
"""
|
| 631 |
+
Initialize the PairwiseSpectral instance.
|
| 632 |
+
|
| 633 |
+
Args:
|
| 634 |
+
*args: Variable length argument list.
|
| 635 |
+
**kwargs: Arbitrary keyword arguments.
|
| 636 |
+
"""
|
| 637 |
+
self.type = type
|
| 638 |
+
|
| 639 |
+
self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
self.FAD_args = FAD_args
|
| 643 |
+
|
| 644 |
+
assert self.FAD_args is not None, "FAD_args must be provided"
|
| 645 |
+
|
| 646 |
+
if self.FAD_args.do_PCA_figure:
|
| 647 |
+
self.pca = None
|
| 648 |
+
|
| 649 |
+
super().__init__(self.type, sample_rate, *args, **kwargs)
|
| 650 |
+
|
| 651 |
+
def calculate_FAD_distance(self, mu1, sigma1, mu2, sigma2, eps=1e-6):
|
| 652 |
+
|
| 653 |
+
"""Numpy implementation of the Frechet Distance.
|
| 654 |
+
The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
|
| 655 |
+
and X_2 ~ N(mu_2, C_2) is
|
| 656 |
+
d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).
|
| 657 |
+
|
| 658 |
+
Stable version by Dougal J. Sutherland.
|
| 659 |
+
|
| 660 |
+
Params:
|
| 661 |
+
-- mu1 : Numpy array containing the activations of a layer of the
|
| 662 |
+
inception net (like returned by the function 'get_predictions')
|
| 663 |
+
for generated samples.
|
| 664 |
+
-- mu2 : The sample mean over activations, precalculated on an
|
| 665 |
+
representative data set.
|
| 666 |
+
-- sigma1: The covariance matrix over activations for generated samples.
|
| 667 |
+
-- sigma2: The covariance matrix over activations, precalculated on an
|
| 668 |
+
representative data set.
|
| 669 |
+
|
| 670 |
+
Returns:
|
| 671 |
+
-- : The Frechet Distance.
|
| 672 |
+
"""
|
| 673 |
+
mu1=mu1.detach().cpu().numpy()
|
| 674 |
+
mu2=mu2.detach().cpu().numpy()
|
| 675 |
+
|
| 676 |
+
sigma1=sigma1.detach().cpu().numpy()
|
| 677 |
+
sigma2=sigma2.detach().cpu().numpy()
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
mu1 = np.atleast_1d(mu1)
|
| 681 |
+
mu2 = np.atleast_1d(mu2)
|
| 682 |
+
|
| 683 |
+
sigma1 = np.atleast_2d(sigma1)
|
| 684 |
+
sigma2 = np.atleast_2d(sigma2)
|
| 685 |
+
|
| 686 |
+
assert (
|
| 687 |
+
mu1.shape == mu2.shape
|
| 688 |
+
), "Training and test mean vectors have different lengths"
|
| 689 |
+
assert (
|
| 690 |
+
sigma1.shape == sigma2.shape
|
| 691 |
+
), "Training and test covariances have different dimensions"
|
| 692 |
+
|
| 693 |
+
diff = mu1 - mu2
|
| 694 |
+
|
| 695 |
+
# Product might be almost singular
|
| 696 |
+
covmean, _ = linalg.sqrtm(sigma1.dot(sigma2), disp=False)
|
| 697 |
+
if not np.isfinite(covmean).all():
|
| 698 |
+
msg = (
|
| 699 |
+
"fid calculation produces singular product; "
|
| 700 |
+
"adding %s to diagonal of cov estimates"
|
| 701 |
+
) % eps
|
| 702 |
+
offset = np.eye(sigma1.shape[0]) * eps
|
| 703 |
+
covmean = linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))
|
| 704 |
+
|
| 705 |
+
# Numerical error might give slight imaginary component
|
| 706 |
+
if np.iscomplexobj(covmean):
|
| 707 |
+
if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
|
| 708 |
+
m = np.max(np.abs(covmean.imag))
|
| 709 |
+
raise ValueError("Imaginary component {}".format(m))
|
| 710 |
+
covmean = covmean.real
|
| 711 |
+
|
| 712 |
+
tr_covmean = np.trace(covmean)
|
| 713 |
+
|
| 714 |
+
return diff.dot(diff) + np.trace(sigma1) + np.trace(sigma2) - 2 * tr_covmean
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
def calculate_emb_statistics(self, features_dicto):
|
| 718 |
+
"""
|
| 719 |
+
Calculate the mean and standard deviation of the features.
|
| 720 |
+
|
| 721 |
+
Args:
|
| 722 |
+
features_dicto (dict): Dictionary containing features for each key.
|
| 723 |
+
|
| 724 |
+
Returns:
|
| 725 |
+
mean_features (torch.Tensor): Mean of the features.
|
| 726 |
+
std_features (torch.Tensor): Standard deviation of the features.
|
| 727 |
+
"""
|
| 728 |
+
|
| 729 |
+
all_features = torch.cat(list(features_dicto.values()), dim=0)
|
| 730 |
+
|
| 731 |
+
|
| 732 |
+
#mean
|
| 733 |
+
#mean_features = all_features.mean(dim=0)
|
| 734 |
+
|
| 735 |
+
#cov
|
| 736 |
+
#cov_features = torch.cov(all_features.T)
|
| 737 |
+
mean_features = np.mean(all_features.cpu().numpy(), axis=0)
|
| 738 |
+
|
| 739 |
+
cov_features= np.cov(all_features.cpu().numpy(), rowvar=False)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
return torch.tensor(mean_features), torch.tensor(cov_features)
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
def do_PCA_figure(self, dict_features_y, dict_features_y_hat, dict_features_x=None):
|
| 747 |
+
"""
|
| 748 |
+
Perform PCA on the features and create a figure.
|
| 749 |
+
|
| 750 |
+
Args:
|
| 751 |
+
dict_features_y (dict): Dictionary containing features for the first set.
|
| 752 |
+
dict_features_y_hat (dict): Dictionary containing features for the second set.
|
| 753 |
+
|
| 754 |
+
Returns:
|
| 755 |
+
fig: The created figure.
|
| 756 |
+
"""
|
| 757 |
+
import matplotlib.pyplot as plt
|
| 758 |
+
from sklearn.decomposition import PCA
|
| 759 |
+
|
| 760 |
+
y_values = list(dict_features_y.values())
|
| 761 |
+
y_values = torch.cat(y_values, dim=0)
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
|
| 765 |
+
if self.pca is None:
|
| 766 |
+
self.pca = PCA(n_components=2)
|
| 767 |
+
pca_result = self.pca.fit_transform(y_values.cpu().numpy())
|
| 768 |
+
else:
|
| 769 |
+
pca_result = self.pca.transform(y_values.cpu().numpy())
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
y_hat_values = list(dict_features_y_hat.values())
|
| 773 |
+
y_hat_values = torch.cat(y_hat_values, dim=0)
|
| 774 |
+
|
| 775 |
+
#project y_hat values into the same PCA space
|
| 776 |
+
pca_result_hat = self.pca.transform(y_hat_values.cpu().numpy())
|
| 777 |
+
|
| 778 |
+
data_dict = {
|
| 779 |
+
"y": pca_result,
|
| 780 |
+
"y_hat": pca_result_hat
|
| 781 |
+
}
|
| 782 |
+
|
| 783 |
+
if dict_features_x is not None:
|
| 784 |
+
x_values = list(dict_features_x.values())
|
| 785 |
+
x_values = torch.cat(x_values, dim=0)
|
| 786 |
+
|
| 787 |
+
pca_result_x = self.pca.transform(x_values.cpu().numpy())
|
| 788 |
+
|
| 789 |
+
data_dict["x"] = pca_result_x
|
| 790 |
+
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
fig= make_PCA_figure(data_dict, title=self.type + " PCA")
|
| 794 |
+
|
| 795 |
+
|
| 796 |
+
return fig
|
| 797 |
+
|
| 798 |
+
|
| 799 |
+
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, dict_cluster=None, *args, **kwargs):
|
| 803 |
+
"""
|
| 804 |
+
Compute the pairwise spectral metric.
|
| 805 |
+
|
| 806 |
+
Args:
|
| 807 |
+
*args: Variable length argument list.
|
| 808 |
+
**kwargs: Arbitrary keyword arguments.
|
| 809 |
+
|
| 810 |
+
Returns:
|
| 811 |
+
The computed pairwise spectral metric.
|
| 812 |
+
"""
|
| 813 |
+
|
| 814 |
+
print("Computing FAD distance...")
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
|
| 818 |
+
dict_features_y={}
|
| 819 |
+
dict_features_y_hat={}
|
| 820 |
+
#dict_features_x={}
|
| 821 |
+
|
| 822 |
+
if dict_y_hat is None:
|
| 823 |
+
print("computing FAD with style embeddings")
|
| 824 |
+
assert dict_p_hat is not None, "dict_p_hat must be provided if dict_y_hat is None"
|
| 825 |
+
|
| 826 |
+
for key in dict_y.keys():
|
| 827 |
+
y= dict_y[key]
|
| 828 |
+
#x= dict_x[key]
|
| 829 |
+
|
| 830 |
+
embed= dict_p_hat[key]
|
| 831 |
+
embed=torch.tensor(embed).to(self.device).unsqueeze(0)
|
| 832 |
+
|
| 833 |
+
print("embed shape:", embed.shape)
|
| 834 |
+
embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
|
| 835 |
+
|
| 836 |
+
if self.type== "AFxRep-mid":
|
| 837 |
+
p_hat= embed_mid
|
| 838 |
+
elif self.type== "AFxRep-side":
|
| 839 |
+
p_hat= embed_side
|
| 840 |
+
elif self.type== "AFxRep":
|
| 841 |
+
p_hat= embed
|
| 842 |
+
|
| 843 |
+
#if x.shape[-2] == 1:
|
| 844 |
+
# x = x.repeat( 2, 1)
|
| 845 |
+
|
| 846 |
+
#assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
|
| 847 |
+
|
| 848 |
+
c, d=y.shape
|
| 849 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 850 |
+
|
| 851 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 852 |
+
|
| 853 |
+
y=y.T
|
| 854 |
+
#x=x.T
|
| 855 |
+
|
| 856 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 857 |
+
#x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
|
| 858 |
+
|
| 859 |
+
with torch.no_grad():
|
| 860 |
+
feat_y= self.feat_extractor(y)
|
| 861 |
+
#feat_x= self.feat_extractor(x)
|
| 862 |
+
|
| 863 |
+
assert p_hat.shape == feat_y.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_y.shape}"
|
| 864 |
+
#assert p_hat.shape == feat_x.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_x.shape}"
|
| 865 |
+
|
| 866 |
+
dict_features_y[key] = feat_y
|
| 867 |
+
#dict_features_x[key] = feat_x
|
| 868 |
+
dict_features_y_hat[key] = p_hat
|
| 869 |
+
|
| 870 |
+
else:
|
| 871 |
+
for key in dict_y.keys():
|
| 872 |
+
y= dict_y[key]
|
| 873 |
+
#x= dict_x[key]
|
| 874 |
+
y_hat= dict_y_hat[key]
|
| 875 |
+
|
| 876 |
+
#if x.shape[-2] == 1:
|
| 877 |
+
# x = x.repeat( 2, 1)
|
| 878 |
+
|
| 879 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 880 |
+
#assert x.shape == y.shape, f"Shape mismatch for key {key}: {x.shape} vs {y.shape}"
|
| 881 |
+
|
| 882 |
+
c, d=y.shape
|
| 883 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 884 |
+
|
| 885 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 886 |
+
|
| 887 |
+
y=y.T
|
| 888 |
+
y_hat=y_hat.T
|
| 889 |
+
#x=x.T
|
| 890 |
+
|
| 891 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 892 |
+
y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 893 |
+
#x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
|
| 894 |
+
|
| 895 |
+
with torch.no_grad():
|
| 896 |
+
feat_y= self.feat_extractor(y)
|
| 897 |
+
feat_y_hat= self.feat_extractor(y_hat)
|
| 898 |
+
#feat_x= self.feat_extractor(x)
|
| 899 |
+
|
| 900 |
+
dict_features_y[key] = feat_y
|
| 901 |
+
dict_features_y_hat[key] = feat_y_hat
|
| 902 |
+
#dict_features_x[key] = feat_x
|
| 903 |
+
|
| 904 |
+
dict_output = {}
|
| 905 |
+
if self.FAD_args.do_PCA_figure:
|
| 906 |
+
fig=self.do_PCA_figure(dict_features_y, dict_features_y_hat, fit_mode="target")
|
| 907 |
+
key= self.type+ "_PCA_figure"
|
| 908 |
+
dict_output[key] = fig
|
| 909 |
+
|
| 910 |
+
if self.FAD_args.do_TSNE_figure:
|
| 911 |
+
fig=self.do_TSNE_figure(dict_features_y, dict_features_y_hat, fit_mode="target")
|
| 912 |
+
key= self.type+ "_TSNE_figure"
|
| 913 |
+
dict_output[key] = fig
|
| 914 |
+
|
| 915 |
+
# Compute mean features
|
| 916 |
+
|
| 917 |
+
y_mean, y_cov = self.calculate_emb_statistics(dict_features_y)
|
| 918 |
+
|
| 919 |
+
y_hat_mean, y_hat_cov = self.calculate_emb_statistics(dict_features_y_hat)
|
| 920 |
+
|
| 921 |
+
|
| 922 |
+
# Compute the distance between the mean features
|
| 923 |
+
#FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
|
| 924 |
+
FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
|
| 925 |
+
|
| 926 |
+
|
| 927 |
+
return FAD_distance, dict_output
|
| 928 |
+
|
| 929 |
+
|
| 930 |
+
def metric_factory(metric_name, sample_rate, *args, **kwargs):
|
| 931 |
+
"""
|
| 932 |
+
Factory function to create a metric function based on the metric name.
|
| 933 |
+
|
| 934 |
+
Args:
|
| 935 |
+
metric_name (str): The name of the metric to create.
|
| 936 |
+
*args: Variable length argument list.
|
| 937 |
+
**kwargs: Arbitrary keyword arguments.
|
| 938 |
+
|
| 939 |
+
Returns:
|
| 940 |
+
An instance of a class that implements the metric function.
|
| 941 |
+
"""
|
| 942 |
+
if metric_name == "fad-fx_encoder":
|
| 943 |
+
return FADFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate )
|
| 944 |
+
elif metric_name == "fad-AFxRep":
|
| 945 |
+
return FADFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate)
|
| 946 |
+
elif metric_name == "fad-AFxRep-mid":
|
| 947 |
+
return FADFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate)
|
| 948 |
+
elif metric_name == "fad-AFxRep-side":
|
| 949 |
+
return FADFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate)
|
| 950 |
+
elif metric_name == "kad-fx_encoder":
|
| 951 |
+
return KADFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate)
|
| 952 |
+
elif metric_name == "kad-AFxRep":
|
| 953 |
+
return KADFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate)
|
| 954 |
+
elif metric_name == "kad-AFxRep-mid":
|
| 955 |
+
return KADFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate)
|
| 956 |
+
elif metric_name == "kad-AFxRep-side":
|
| 957 |
+
return KADFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate)
|
| 958 |
+
elif metric_name == "kad-class-fx_encoder":
|
| 959 |
+
return KADFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate, classwise=True)
|
| 960 |
+
elif metric_name == "kad-class-AFxRep":
|
| 961 |
+
return KADFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate, classwise=True)
|
| 962 |
+
elif metric_name == "kad-class-AFxRep-mid":
|
| 963 |
+
return KADFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate, classwise=True)
|
| 964 |
+
elif metric_name == "kad-class-AFxRep-side":
|
| 965 |
+
return KADFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate, classwise=True)
|
| 966 |
+
else:
|
| 967 |
+
raise ValueError(f"Unknown metric: {metric_name}")
|
| 968 |
+
|
| 969 |
+
# Example usage:
|
| 970 |
+
#metric_instance = metric_factory("pairwise-spectral")
|
| 971 |
+
#```
|
utils/evaluation/dist_metrics_multitrack.py
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
from numpy.lib.scimath import sqrt as scisqrt
|
| 4 |
+
from scipy import linalg
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torchaudio
|
| 7 |
+
import os
|
| 8 |
+
from importlib import import_module
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
from utils.feature_extractors.load_features import load_AFxRep, load_fx_encoder, load_fx_encoder_plusplus, load_MERT, load_CLAP, load_fx_encoder_plusplus_2048
|
| 13 |
+
|
| 14 |
+
from utils.log import make_PCA_figure
|
| 15 |
+
|
| 16 |
+
SCALE_FACTOR = 100
|
| 17 |
+
|
| 18 |
+
def calc_kernel_audio_distance(
|
| 19 |
+
x: torch.Tensor,
|
| 20 |
+
y: torch.Tensor,
|
| 21 |
+
device: str,
|
| 22 |
+
bandwidth=None,
|
| 23 |
+
kernel='gaussian',
|
| 24 |
+
precision=torch.float32,
|
| 25 |
+
eps=1e-8
|
| 26 |
+
) -> torch.Tensor:
|
| 27 |
+
"""
|
| 28 |
+
Compute the Kernel Audio Distance (KAD) between two samples using PyTorch.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
x: The first set of embeddings of shape (m, embedding_dim).
|
| 32 |
+
y: The second set of embeddings of shape (n, embedding_dim).
|
| 33 |
+
cache_dirs: Directories to cache kernel statistics.
|
| 34 |
+
bandwidth: The bandwidth value for the Gaussian RBF kernel.
|
| 35 |
+
kernel: Kernel function to use ('gaussian', 'iq', 'imq').
|
| 36 |
+
precision: Type setting for matrix calculation precision.
|
| 37 |
+
eps: Small value to prevent division by zero.
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
The KAD between x and y embedding sets.
|
| 41 |
+
"""
|
| 42 |
+
# Ensure x and y are of the correct precision
|
| 43 |
+
x = x.to(dtype=precision, device=device)
|
| 44 |
+
y = y.to(dtype=precision, device=device)
|
| 45 |
+
|
| 46 |
+
assert bandwidth is not None, "Bandwidth must be provided for KAD calculation"
|
| 47 |
+
|
| 48 |
+
m, n = x.shape[0], y.shape[0]
|
| 49 |
+
|
| 50 |
+
# Define kernel functions
|
| 51 |
+
gamma = 1 / (2 * bandwidth**2 + eps)
|
| 52 |
+
if kernel == 'gaussian': # Gaussian Kernel
|
| 53 |
+
kernel = lambda a: torch.exp(-gamma * a)
|
| 54 |
+
elif kernel == 'iq': # Inverse Quadratic Kernel
|
| 55 |
+
kernel = lambda a: 1 / (1 + gamma * a)
|
| 56 |
+
elif kernel == 'imq': # Inverse Multiquadric Kernel
|
| 57 |
+
kernel = lambda a: 1 / torch.sqrt(1 + gamma * a)
|
| 58 |
+
else:
|
| 59 |
+
raise ValueError("Invalid kernel type. Valid kernels: 'gaussian', 'iq', 'imq'")
|
| 60 |
+
|
| 61 |
+
# Load x kernel statistics
|
| 62 |
+
xx = x @ x.T
|
| 63 |
+
x_sqnorms = torch.diagonal(xx)
|
| 64 |
+
d2_xx = x_sqnorms.unsqueeze(1) + x_sqnorms.unsqueeze(0) - 2 * xx # shape (m, m)
|
| 65 |
+
|
| 66 |
+
k_xx = kernel(d2_xx)
|
| 67 |
+
k_xx = k_xx - torch.diag(torch.diagonal(k_xx))
|
| 68 |
+
k_xx_mean = k_xx.sum() / (m * (m - 1))
|
| 69 |
+
|
| 70 |
+
# Load y kernel statistics
|
| 71 |
+
yy = y @ y.T
|
| 72 |
+
y_sqnorms = torch.diagonal(yy)
|
| 73 |
+
d2_yy = y_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * yy # shape (n, n)
|
| 74 |
+
|
| 75 |
+
k_yy = kernel(d2_yy)
|
| 76 |
+
k_yy = k_yy - torch.diag(torch.diagonal(k_yy))
|
| 77 |
+
k_yy_mean = k_yy.sum() / (n * (n - 1))
|
| 78 |
+
|
| 79 |
+
# Compute kernel statistics for xy
|
| 80 |
+
xy = x @ y.T
|
| 81 |
+
d2_xy = x_sqnorms.unsqueeze(1) + y_sqnorms.unsqueeze(0) - 2 * xy # shape (m, n)
|
| 82 |
+
k_xy = kernel(d2_xy)
|
| 83 |
+
k_xy_mean = k_xy.mean()
|
| 84 |
+
|
| 85 |
+
# Compute MMD
|
| 86 |
+
result = k_xx_mean + k_yy_mean - 2 * k_xy_mean
|
| 87 |
+
|
| 88 |
+
return result * SCALE_FACTOR
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def median_pairwise_distance(x, subsample=None):
|
| 92 |
+
"""
|
| 93 |
+
Compute the median pairwise distance of an embedding set.
|
| 94 |
+
|
| 95 |
+
Args:
|
| 96 |
+
x: torch.Tensor of shape (n_samples, embedding_dim)
|
| 97 |
+
subsample: int, number of random pairs to consider (optional)
|
| 98 |
+
|
| 99 |
+
Returns:
|
| 100 |
+
The median pairwise distance between points in x.
|
| 101 |
+
"""
|
| 102 |
+
x = torch.tensor(x, dtype=torch.float32)
|
| 103 |
+
n_samples = x.shape[0]
|
| 104 |
+
|
| 105 |
+
if subsample is not None and subsample < n_samples * (n_samples - 1) / 2:
|
| 106 |
+
# Randomly select pairs of indices
|
| 107 |
+
idx1 = torch.randint(0, n_samples, (subsample,))
|
| 108 |
+
idx2 = torch.randint(0, n_samples, (subsample,))
|
| 109 |
+
|
| 110 |
+
# Ensure idx1 != idx2
|
| 111 |
+
mask = idx1 == idx2
|
| 112 |
+
idx2[mask] = (idx2[mask] + 1) % n_samples
|
| 113 |
+
|
| 114 |
+
# Compute distances for selected pairs
|
| 115 |
+
distances = torch.sqrt(torch.sum((x[idx1] - x[idx2])**2, dim=1))
|
| 116 |
+
else:
|
| 117 |
+
# Compute all pairwise distances
|
| 118 |
+
distances = torch.pdist(x)
|
| 119 |
+
|
| 120 |
+
return torch.median(distances).item()
|
| 121 |
+
|
| 122 |
+
class DistMetric:
|
| 123 |
+
"""
|
| 124 |
+
Base class for pairwise metrics.
|
| 125 |
+
|
| 126 |
+
This class should be subclassed to implement specific pairwise metrics.
|
| 127 |
+
"""
|
| 128 |
+
def __init__(self,type, sample_rate,*args, **kwargs):
|
| 129 |
+
"""
|
| 130 |
+
Initialize the PairwiseMetric instance.
|
| 131 |
+
|
| 132 |
+
Args:
|
| 133 |
+
*args: Variable length argument list.
|
| 134 |
+
**kwargs: Arbitrary keyword arguments.
|
| 135 |
+
"""
|
| 136 |
+
self.type=type
|
| 137 |
+
self.sample_rate=sample_rate
|
| 138 |
+
|
| 139 |
+
self.taxonomy_ref= kwargs.get("taxonomy_ref", None)
|
| 140 |
+
|
| 141 |
+
if self.type == "DynamicFeatures":
|
| 142 |
+
from utils.feature_extractors.AF_features_embedding import AF_fourier_embedding
|
| 143 |
+
AFembedding= AF_fourier_embedding(device=self.device)
|
| 144 |
+
|
| 145 |
+
def feat_extracfor_fn(x):
|
| 146 |
+
z, _ = AFembedding.encode(x)
|
| 147 |
+
return z
|
| 148 |
+
|
| 149 |
+
self.feat_extractor = feat_extracfor_fn
|
| 150 |
+
elif self.type == "FxEncoder++":
|
| 151 |
+
self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
|
| 152 |
+
assert self.model_args is not None, "model_args must be provided for fxencAFv2-fxenc++ type"
|
| 153 |
+
|
| 154 |
+
self.distance_type=self.model_args.distance_type
|
| 155 |
+
fxencoder = load_fx_encoder_plusplus_2048(self.model_args, self.device)
|
| 156 |
+
|
| 157 |
+
def feat_extractor_fn(x):
|
| 158 |
+
z= fxencoder(x)
|
| 159 |
+
z=torch.nn.functional.normalize(z, dim=-1, p=2) # normalize to unit variance
|
| 160 |
+
return z
|
| 161 |
+
|
| 162 |
+
self.feat_extractor = feat_extractor_fn
|
| 163 |
+
|
| 164 |
+
else:
|
| 165 |
+
raise ValueError(f"Unknown type: {self.type}. Supported types: fx_encoder, fx_encoder_plusplus, AFxRep-mid, AFxRep-side, AFxRep")
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def compute(self, *args, **kwargs):
|
| 169 |
+
raise NotImplementedError("Subclasses should implement this method.")
|
| 170 |
+
|
| 171 |
+
def extract_features(self, y, y_hat, x=None):
|
| 172 |
+
|
| 173 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 174 |
+
y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 175 |
+
|
| 176 |
+
with torch.no_grad():
|
| 177 |
+
feat_y= self.feat_extractor(y)
|
| 178 |
+
feat_y_hat= self.feat_extractor(y_hat)
|
| 179 |
+
|
| 180 |
+
if x is not None:
|
| 181 |
+
x=torch.tensor(x).permute(1,0).unsqueeze(0).to(self.device)
|
| 182 |
+
with torch.no_grad():
|
| 183 |
+
feat_x= self.feat_extractor(x)
|
| 184 |
+
else:
|
| 185 |
+
feat_x=None
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
return feat_y, feat_y_hat, feat_x
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def do_TSNE_figure(self, dict_features_y, dict_features_y_hat, dict_taxonomy=None):
|
| 192 |
+
"""
|
| 193 |
+
Perform PCA on the features and create a figure.
|
| 194 |
+
|
| 195 |
+
Args:
|
| 196 |
+
dict_features_y (dict): Dictionary containing features for the first set.
|
| 197 |
+
dict_features_y_hat (dict): Dictionary containing features for the second set.
|
| 198 |
+
|
| 199 |
+
Returns:
|
| 200 |
+
fig: The created figure.
|
| 201 |
+
"""
|
| 202 |
+
import matplotlib.pyplot as plt
|
| 203 |
+
from sklearn.manifold import TSNE
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
y_values = {}
|
| 207 |
+
y_hat_values = {}
|
| 208 |
+
for key, track_name in self.taxonomy_ref.items():
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
y_values[track_name] = []
|
| 212 |
+
for k in range(len(dict_features_y.keys())):
|
| 213 |
+
index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
|
| 214 |
+
print("taxonomy",k, dict_taxonomy[k])
|
| 215 |
+
if index: # If any matches were found
|
| 216 |
+
# Select the corresponding rows from dict_features_y[k]
|
| 217 |
+
selected_features = dict_features_y[k][index]
|
| 218 |
+
y_values[track_name].append(selected_features)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
if not y_values[track_name]:
|
| 222 |
+
print(f"Warning: No features found for track {track_name} with key {key}. Skipping this track.")
|
| 223 |
+
#remove the track from the dictionaries
|
| 224 |
+
y_values.pop(track_name, None)
|
| 225 |
+
continue
|
| 226 |
+
|
| 227 |
+
y_values[track_name]=torch.cat(y_values[track_name], dim=0)
|
| 228 |
+
y_values[track_name]=torch.nan_to_num(y_values[track_name], nan=0)
|
| 229 |
+
|
| 230 |
+
#print(clusters[track_name])
|
| 231 |
+
|
| 232 |
+
y_hat_values[track_name] = []
|
| 233 |
+
|
| 234 |
+
for k in range(len(dict_features_y_hat.keys())):
|
| 235 |
+
index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
|
| 236 |
+
if index: # If any matches were found
|
| 237 |
+
# Select the corresponding rows from dict_features_y[k]
|
| 238 |
+
selected_features = dict_features_y_hat[k][index]
|
| 239 |
+
y_hat_values[track_name].append(selected_features)
|
| 240 |
+
#else:
|
| 241 |
+
# y_hat_values[track_name].append(None) # Append zeros if no match found
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
y_hat_values[track_name]=torch.cat(y_hat_values[track_name], dim=0)
|
| 245 |
+
y_hat_values[track_name]=torch.nan_to_num(y_hat_values[track_name], nan=0)
|
| 246 |
+
|
| 247 |
+
#print(clusters_hat[track_name])
|
| 248 |
+
|
| 249 |
+
if self.pca is None:
|
| 250 |
+
self.pca= {}
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
tsne_result = {}
|
| 254 |
+
tsne_result_hat = {}
|
| 255 |
+
data_dict = {}
|
| 256 |
+
figs= {}
|
| 257 |
+
|
| 258 |
+
for k in y_values.keys():
|
| 259 |
+
print("Processing track:", k)
|
| 260 |
+
self.tsne =TSNE(n_components=2, perplexity=30)
|
| 261 |
+
combined_data = torch.cat([y_values[k], y_hat_values[k]], dim=0).cpu().numpy()
|
| 262 |
+
combined_result = self.tsne.fit_transform(combined_data)
|
| 263 |
+
|
| 264 |
+
n_y = y_values[k].shape[0]
|
| 265 |
+
tsne_result[k] = combined_result[:n_y]
|
| 266 |
+
tsne_result_hat[k] = combined_result[n_y:]
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
data_dict[k] = {
|
| 270 |
+
"y": tsne_result[k],
|
| 271 |
+
"y_hat": tsne_result_hat[k] }
|
| 272 |
+
|
| 273 |
+
figs[k]= make_PCA_figure(data_dict[k], title=self.type + " TSNE; track: "+k)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
return figs
|
| 277 |
+
|
| 278 |
+
def do_PCA_figure(self, dict_features_y, dict_features_y_hat, fit_mode="target", dict_cluster=None, dict_taxonomy=None):
|
| 279 |
+
"""
|
| 280 |
+
Perform PCA on the features and create a figure.
|
| 281 |
+
|
| 282 |
+
Args:
|
| 283 |
+
dict_features_y (dict): Dictionary containing features for the first set.
|
| 284 |
+
dict_features_y_hat (dict): Dictionary containing features for the second set.
|
| 285 |
+
|
| 286 |
+
Returns:
|
| 287 |
+
fig: The created figure.
|
| 288 |
+
"""
|
| 289 |
+
import matplotlib.pyplot as plt
|
| 290 |
+
from sklearn.decomposition import PCA
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
y_values = {}
|
| 294 |
+
y_hat_values = {}
|
| 295 |
+
if dict_cluster is not None:
|
| 296 |
+
clusters={}
|
| 297 |
+
clusters_hat= {}
|
| 298 |
+
|
| 299 |
+
for key, track_name in self.taxonomy_ref.items():
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
y_values[track_name] = []
|
| 303 |
+
if dict_cluster is not None:
|
| 304 |
+
clusters[track_name] = []
|
| 305 |
+
for k in range(len(dict_features_y.keys())):
|
| 306 |
+
print("taxonomy",k, dict_taxonomy[k])
|
| 307 |
+
index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
|
| 308 |
+
if index: # If any matches were found
|
| 309 |
+
# Select the corresponding rows from dict_features_y[k]
|
| 310 |
+
selected_features = dict_features_y[k][index]
|
| 311 |
+
y_values[track_name].append(selected_features)
|
| 312 |
+
|
| 313 |
+
if dict_cluster is not None:
|
| 314 |
+
clusters[track_name].append(dict_cluster[k].unsqueeze(0))
|
| 315 |
+
|
| 316 |
+
#check if y_values[track_name] is empty
|
| 317 |
+
if not y_values[track_name]:
|
| 318 |
+
print(f"Warning: No features found for track {track_name} with key {key}. Skipping this track.")
|
| 319 |
+
#remove the track from the dictionaries
|
| 320 |
+
y_values.pop(track_name, None)
|
| 321 |
+
continue
|
| 322 |
+
|
| 323 |
+
y_values[track_name]=torch.cat(y_values[track_name], dim=0)
|
| 324 |
+
y_values[track_name]=torch.nan_to_num(y_values[track_name], nan=0)
|
| 325 |
+
|
| 326 |
+
if dict_cluster is not None:
|
| 327 |
+
clusters[track_name]=torch.cat(clusters[track_name], dim=0)
|
| 328 |
+
|
| 329 |
+
y_hat_values[track_name] = []
|
| 330 |
+
if dict_cluster is not None:
|
| 331 |
+
clusters_hat[track_name] = []
|
| 332 |
+
|
| 333 |
+
for k in range(len(dict_features_y_hat.keys())):
|
| 334 |
+
index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
|
| 335 |
+
if index: # If any matches were found
|
| 336 |
+
# Select the corresponding rows from dict_features_y[k]
|
| 337 |
+
selected_features = dict_features_y_hat[k][index]
|
| 338 |
+
y_hat_values[track_name].append(selected_features)
|
| 339 |
+
if dict_cluster is not None:
|
| 340 |
+
clusters_hat[track_name].append(dict_cluster[k].unsqueeze(0))
|
| 341 |
+
|
| 342 |
+
y_hat_values[track_name]=torch.cat(y_hat_values[track_name], dim=0)
|
| 343 |
+
y_hat_values[track_name]=torch.nan_to_num(y_hat_values[track_name], nan=0)
|
| 344 |
+
|
| 345 |
+
if dict_cluster is not None:
|
| 346 |
+
clusters_hat[track_name]=torch.cat(clusters_hat[track_name], dim=0)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
if self.pca is None:
|
| 350 |
+
self.pca= {}
|
| 351 |
+
|
| 352 |
+
pca_result = {}
|
| 353 |
+
pca_result_hat = {}
|
| 354 |
+
data_dict = {}
|
| 355 |
+
figs= {}
|
| 356 |
+
|
| 357 |
+
for k in y_values.keys():
|
| 358 |
+
print("Processing track:", k)
|
| 359 |
+
#check if pca already exists for this key
|
| 360 |
+
if k not in self.pca.keys():
|
| 361 |
+
if fit_mode == "target":
|
| 362 |
+
print("Fitting PCA for target values only track:", k)
|
| 363 |
+
self.pca[k] = PCA(n_components=2).fit(y_values[k].cpu().numpy())
|
| 364 |
+
elif fit_mode == "all":
|
| 365 |
+
print("Fitting PCA for all values track:", k)
|
| 366 |
+
self.pca[k] = PCA(n_components=2).fit(torch.cat([y_values[k], y_hat_values[k]], dim=0).cpu().numpy())
|
| 367 |
+
else:
|
| 368 |
+
raise ValueError(f"Unknown fit_mode: {fit_mode}. Supported modes: target, all")
|
| 369 |
+
|
| 370 |
+
pca_result[k]=self.pca[k].transform(y_values[k].cpu().numpy())
|
| 371 |
+
pca_result_hat[k] = self.pca[k].transform(y_hat_values[k].cpu().numpy())
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
if dict_cluster is not None:
|
| 375 |
+
data_dict[k] = {
|
| 376 |
+
"y_C0": pca_result[k][clusters[k] == 0],
|
| 377 |
+
"y_hat_C0": pca_result_hat[k][clusters_hat[k] == 0],
|
| 378 |
+
"y_C1": pca_result[k][clusters[k] == 1],
|
| 379 |
+
"y_hat_C1": pca_result_hat[k][clusters_hat[k] == 1]
|
| 380 |
+
}
|
| 381 |
+
else:
|
| 382 |
+
data_dict[k] = {
|
| 383 |
+
"y": pca_result[k],
|
| 384 |
+
"y_hat": pca_result_hat[k]
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
figs[k]= make_PCA_figure(data_dict[k], title=self.type + " PCA; track: "+k)
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
return figs
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
class KADFeatures(DistMetric):
|
| 395 |
+
"""
|
| 396 |
+
Class for computing the pairwise spectral metric.
|
| 397 |
+
|
| 398 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 399 |
+
to calculate the pairwise spectral metric.
|
| 400 |
+
"""
|
| 401 |
+
def __init__(self,
|
| 402 |
+
type=None,
|
| 403 |
+
sample_rate=44100,
|
| 404 |
+
KAD_args=None,
|
| 405 |
+
*args, **kwargs):
|
| 406 |
+
"""
|
| 407 |
+
Initialize the PairwiseSpectral instance.
|
| 408 |
+
|
| 409 |
+
Args:
|
| 410 |
+
*args: Variable length argument list.
|
| 411 |
+
**kwargs: Arbitrary keyword arguments.
|
| 412 |
+
"""
|
| 413 |
+
self.type = type
|
| 414 |
+
|
| 415 |
+
self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 416 |
+
|
| 417 |
+
self.KAD_args = KAD_args
|
| 418 |
+
|
| 419 |
+
assert self.KAD_args is not None, "FAD_args must be provided"
|
| 420 |
+
|
| 421 |
+
if self.KAD_args.do_PCA_figure:
|
| 422 |
+
self.pca = None
|
| 423 |
+
|
| 424 |
+
self.bandwidth = None
|
| 425 |
+
|
| 426 |
+
super().__init__(self.type, sample_rate, *args, **kwargs)
|
| 427 |
+
|
| 428 |
+
def calculate_KAD_distance(self, dict_features_y, dict_features_y_hat, dict_taxonomy=None):
|
| 429 |
+
|
| 430 |
+
y_values = {}
|
| 431 |
+
y_hat_values = {}
|
| 432 |
+
for key, track_name in self.taxonomy_ref.items():
|
| 433 |
+
y_values[track_name] = []
|
| 434 |
+
for k in range(len(dict_features_y.keys())):
|
| 435 |
+
index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
|
| 436 |
+
if index: # If any matches were found
|
| 437 |
+
# Select the corresponding rows from dict_features_y[k]
|
| 438 |
+
selected_features = dict_features_y[k][index]
|
| 439 |
+
y_values[track_name].append(selected_features)
|
| 440 |
+
#else:
|
| 441 |
+
# y_values[track_name].append(None) # Append zeros if no match found
|
| 442 |
+
if not y_values[track_name]:
|
| 443 |
+
print(f"Warning: No features found for track {track_name} with key {key}. Skipping this track.")
|
| 444 |
+
#remove the track from the dictionaries
|
| 445 |
+
y_values.pop(track_name, None)
|
| 446 |
+
continue
|
| 447 |
+
|
| 448 |
+
y_values[track_name]=torch.cat(y_values[track_name], dim=0)
|
| 449 |
+
y_values[track_name]=torch.nan_to_num(y_values[track_name], nan=0)
|
| 450 |
+
|
| 451 |
+
y_hat_values[track_name] = []
|
| 452 |
+
for k in range(len(dict_features_y_hat.keys())):
|
| 453 |
+
index = [i for i, tax_id in enumerate(dict_taxonomy[k]) if tax_id == key]
|
| 454 |
+
if index: # If any matches were found
|
| 455 |
+
# Select the corresponding rows from dict_features_y[k]
|
| 456 |
+
selected_features = dict_features_y_hat[k][index]
|
| 457 |
+
y_hat_values[track_name].append(selected_features)
|
| 458 |
+
#else:
|
| 459 |
+
# y_hat_values[track_name].append(None) # Append zeros if no match found
|
| 460 |
+
|
| 461 |
+
y_hat_values[track_name]=torch.cat(y_hat_values[track_name], dim=0)
|
| 462 |
+
y_hat_values[track_name]=torch.nan_to_num(y_hat_values[track_name], nan=0)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
KAD={}
|
| 466 |
+
weight={}
|
| 467 |
+
total_examples= 0
|
| 468 |
+
for key in y_values.keys():
|
| 469 |
+
y= y_values[key]
|
| 470 |
+
y_hat= y_hat_values[key]
|
| 471 |
+
with torch.no_grad():
|
| 472 |
+
KAD[key]=calc_kernel_audio_distance(y, y_hat, device=self.device, bandwidth=self.bandwidth, kernel=self.KAD_args.kernel, precision=torch.float32)
|
| 473 |
+
|
| 474 |
+
weight[key]= y.shape[0]
|
| 475 |
+
total_examples += weight[key]
|
| 476 |
+
|
| 477 |
+
KAD= sum([KAD[k] * weight[k] for k in KAD.keys()]) / total_examples
|
| 478 |
+
|
| 479 |
+
return KAD.cpu().item()
|
| 480 |
+
|
| 481 |
+
def calculate_bandwidth(self, dict_features_y):
|
| 482 |
+
|
| 483 |
+
print("Calculating bandwidth...")
|
| 484 |
+
y= torch.cat(list(dict_features_y.values()), dim=0)
|
| 485 |
+
print("y shape:", y.shape)
|
| 486 |
+
self.bandwidth = median_pairwise_distance(y)
|
| 487 |
+
print("Bandwidth:", self.bandwidth)
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def compute(self, dict_y, dict_y_hat, dict_taxonomy=None, dict_p_hat=None, *args, **kwargs):
|
| 492 |
+
"""
|
| 493 |
+
Compute the pairwise spectral metric.
|
| 494 |
+
|
| 495 |
+
Args:
|
| 496 |
+
*args: Variable length argument list.
|
| 497 |
+
**kwargs: Arbitrary keyword arguments.
|
| 498 |
+
|
| 499 |
+
Returns:
|
| 500 |
+
The computed pairwise spectral metric.
|
| 501 |
+
"""
|
| 502 |
+
|
| 503 |
+
print("Computing KAD distance...")
|
| 504 |
+
|
| 505 |
+
dict_features_y={}
|
| 506 |
+
dict_features_y_hat={}
|
| 507 |
+
|
| 508 |
+
if dict_y_hat is None and dict_p_hat is not None:
|
| 509 |
+
|
| 510 |
+
for key in dict_y.keys():
|
| 511 |
+
y= dict_y[key]
|
| 512 |
+
|
| 513 |
+
embed= dict_p_hat[key]
|
| 514 |
+
embed=torch.tensor(embed).to(self.device)
|
| 515 |
+
|
| 516 |
+
embed=embed*math.sqrt(embed.shape[-1]) # Scale the embedding
|
| 517 |
+
|
| 518 |
+
embed_fxenc=embed[...,:2048]/ math.sqrt(2048) # Scale the first 128 dimensions
|
| 519 |
+
embed_AF=embed[...,2048:]/ math.sqrt(64) # Scale the last 128 dimensions
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
if self.type == "FxEncoder++":
|
| 523 |
+
p_hat= embed_fxenc
|
| 524 |
+
|
| 525 |
+
elif self.type == "DynamicFeatures":
|
| 526 |
+
p_hat= embed_AF
|
| 527 |
+
|
| 528 |
+
n, c, d=y.shape
|
| 529 |
+
|
| 530 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 531 |
+
|
| 532 |
+
y=torch.tensor(y).to(self.device)
|
| 533 |
+
|
| 534 |
+
with torch.no_grad():
|
| 535 |
+
feat_y= self.feat_extractor(y)
|
| 536 |
+
|
| 537 |
+
assert p_hat.shape == feat_y.shape, f"Shape mismatch for key {key}: {p_hat.shape} vs {feat_y.shape}"
|
| 538 |
+
|
| 539 |
+
dict_features_y[key] = feat_y
|
| 540 |
+
dict_features_y_hat[key] = p_hat
|
| 541 |
+
|
| 542 |
+
else:
|
| 543 |
+
for key in dict_y.keys():
|
| 544 |
+
y= dict_y[key]
|
| 545 |
+
y_hat= dict_y_hat[key]
|
| 546 |
+
|
| 547 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 548 |
+
|
| 549 |
+
n, c, d=y.shape
|
| 550 |
+
if dict_taxonomy is not None:
|
| 551 |
+
taxonomy= dict_taxonomy[key]
|
| 552 |
+
assert len(taxonomy) == n, f"Taxonomy length mismatch for key {key}: {len(taxonomy)} vs {n}"
|
| 553 |
+
|
| 554 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 555 |
+
|
| 556 |
+
y=torch.tensor(y).to(self.device)
|
| 557 |
+
y_hat=torch.tensor(y_hat).to(self.device)
|
| 558 |
+
|
| 559 |
+
with torch.no_grad():
|
| 560 |
+
feat_y= self.feat_extractor(y)
|
| 561 |
+
feat_y_hat= self.feat_extractor(y_hat)
|
| 562 |
+
|
| 563 |
+
dict_features_y[key] = feat_y
|
| 564 |
+
dict_features_y_hat[key] = feat_y_hat
|
| 565 |
+
|
| 566 |
+
dict_output = {}
|
| 567 |
+
if self.KAD_args.do_PCA_figure:
|
| 568 |
+
dict_figs=self.do_PCA_figure(dict_features_y, dict_features_y_hat, dict_taxonomy=dict_taxonomy, fit_mode=self.KAD_args.PCA_fit_mode)
|
| 569 |
+
#returns a dictionary with figures for each taxonomy key
|
| 570 |
+
#modify the key to include the type
|
| 571 |
+
|
| 572 |
+
for k, v in dict_figs.items():
|
| 573 |
+
#modify the key to include the type
|
| 574 |
+
key= self.type+ "_PCA_figure_" + k
|
| 575 |
+
dict_output[key] = v
|
| 576 |
+
|
| 577 |
+
if self.KAD_args.do_TSNE_figure:
|
| 578 |
+
dict_figs=self.do_TSNE_figure(dict_features_y, dict_features_y_hat, dict_taxonomy=dict_taxonomy)
|
| 579 |
+
for k, fig in dict_figs.items():
|
| 580 |
+
key= self.type+ "_TSNE_figure"+k
|
| 581 |
+
dict_output[key] = fig
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
|
| 585 |
+
# Compute the distance between the mean features
|
| 586 |
+
#FAD_distance = self.calculate_FAD_distance(y_mean, y_cov, y_hat_mean, y_hat_cov)
|
| 587 |
+
if self.bandwidth is None:
|
| 588 |
+
self.calculate_bandwidth(dict_features_y)
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
KAD_distance = self.calculate_KAD_distance(dict_features_y, dict_features_y_hat, dict_taxonomy)
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
return KAD_distance, dict_output
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
|
| 598 |
+
def metric_factory(metric_name, sample_rate, *args, **kwargs):
|
| 599 |
+
"""
|
| 600 |
+
Factory function to create a metric function based on the metric name.
|
| 601 |
+
|
| 602 |
+
Args:
|
| 603 |
+
metric_name (str): The name of the metric to create.
|
| 604 |
+
*args: Variable length argument list.
|
| 605 |
+
**kwargs: Arbitrary keyword arguments.
|
| 606 |
+
|
| 607 |
+
Returns:
|
| 608 |
+
An instance of a class that implements the metric function.
|
| 609 |
+
"""
|
| 610 |
+
if metric_name == "kad-FxEncoder++":
|
| 611 |
+
return KADFeatures(*args, **kwargs, type="FxEncoder++", sample_rate=sample_rate)
|
| 612 |
+
elif metric_name == "kad-DynamicFeatures":
|
| 613 |
+
return KADFeatures(*args, **kwargs, type="DynamicFeatures", sample_rate=sample_rate)
|
| 614 |
+
else:
|
| 615 |
+
raise ValueError(f"Unknown metric: {metric_name}")
|
| 616 |
+
|
| 617 |
+
# Example usage:
|
| 618 |
+
#metric_instance = metric_factory("pairwise-spectral")
|
| 619 |
+
#```
|
utils/evaluation/pairwise_metrics.py
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
import torchaudio
|
| 4 |
+
import os
|
| 5 |
+
from importlib import import_module
|
| 6 |
+
import yaml
|
| 7 |
+
import torch
|
| 8 |
+
from utils.feature_extractors.load_features import load_AFxRep, load_fx_encoder, load_fx_encoder_plusplus, load_fx_encoder_plusplus_2048
|
| 9 |
+
from utils.MSS_loss import MultiScale_Spectral_Loss_MidSide_DDSP
|
| 10 |
+
|
| 11 |
+
class PairwiseMetric:
|
| 12 |
+
"""
|
| 13 |
+
Base class for pairwise metrics.
|
| 14 |
+
|
| 15 |
+
This class should be subclassed to implement specific pairwise metrics.
|
| 16 |
+
"""
|
| 17 |
+
def __init__(self, *args, **kwargs):
|
| 18 |
+
"""
|
| 19 |
+
Initialize the PairwiseMetric instance.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
*args: Variable length argument list.
|
| 23 |
+
**kwargs: Arbitrary keyword arguments.
|
| 24 |
+
"""
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
def compute(self, *args, **kwargs):
|
| 28 |
+
raise NotImplementedError("Subclasses should implement this method.")
|
| 29 |
+
|
| 30 |
+
class PairwiseFeatures(PairwiseMetric):
|
| 31 |
+
"""
|
| 32 |
+
Class for computing the pairwise spectral metric.
|
| 33 |
+
|
| 34 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 35 |
+
to calculate the pairwise spectral metric.
|
| 36 |
+
"""
|
| 37 |
+
def __init__(self,
|
| 38 |
+
type=None,
|
| 39 |
+
sample_rate=44100,
|
| 40 |
+
*args, **kwargs):
|
| 41 |
+
"""
|
| 42 |
+
Initialize the PairwiseSpectral instance.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
*args: Variable length argument list.
|
| 46 |
+
**kwargs: Arbitrary keyword arguments.
|
| 47 |
+
"""
|
| 48 |
+
self.type = type
|
| 49 |
+
self.sample_rate = sample_rate
|
| 50 |
+
|
| 51 |
+
self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 52 |
+
|
| 53 |
+
if self.type == "fx_encoder":
|
| 54 |
+
self.model_args= kwargs.get("fx_encoder_args", None)
|
| 55 |
+
|
| 56 |
+
assert self.model_args is not None, "model_args must be provided for fx_encoder type"
|
| 57 |
+
|
| 58 |
+
self.distance_type=self.model_args.distance_type
|
| 59 |
+
|
| 60 |
+
self.feat_extractor = load_fx_encoder(self.model_args, self.device)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
#self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
|
| 64 |
+
elif self.type == "fxenc++":
|
| 65 |
+
self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
|
| 66 |
+
|
| 67 |
+
assert self.model_args is not None, "model_args must be provided for fx_encoder type"
|
| 68 |
+
|
| 69 |
+
self.distance_type=self.model_args.distance_type
|
| 70 |
+
|
| 71 |
+
self.feat_extractor = load_fx_encoder_plusplus(self.model_args, self.device)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
#self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
|
| 75 |
+
|
| 76 |
+
elif self.type == "logrms":
|
| 77 |
+
self.model_args= kwargs.get("logrms_args", None)
|
| 78 |
+
|
| 79 |
+
assert self.model_args is not None, "model_args must be provided for logrms type"
|
| 80 |
+
self.distance_type=self.model_args.distance_type
|
| 81 |
+
|
| 82 |
+
from utils.feature_extractors.dsp_features import compute_log_rms
|
| 83 |
+
|
| 84 |
+
self.feat_extractor = lambda x: compute_log_rms(x)
|
| 85 |
+
|
| 86 |
+
elif self.type == "crestfactor":
|
| 87 |
+
self.model_args= kwargs.get("crestfactor_args", None)
|
| 88 |
+
assert self.model_args is not None, "model_args must be provided for crestfactor type"
|
| 89 |
+
|
| 90 |
+
self.distance_type=self.model_args.distance_type
|
| 91 |
+
|
| 92 |
+
from utils.feature_extractors.dsp_features import compute_crest_factor
|
| 93 |
+
|
| 94 |
+
self.feat_extractor = lambda x: compute_crest_factor(x)
|
| 95 |
+
elif self.type == "logspread":
|
| 96 |
+
self.model_args= kwargs.get("logspread_args", None)
|
| 97 |
+
assert self.model_args is not None, "model_args must be provided for logspread type"
|
| 98 |
+
|
| 99 |
+
self.distance_type=self.model_args.distance_type
|
| 100 |
+
|
| 101 |
+
from utils.feature_extractors.dsp_features import compute_log_spread
|
| 102 |
+
|
| 103 |
+
self.feat_extractor = lambda x: compute_log_spread(x).view(-1, 1)
|
| 104 |
+
elif self.type == "stereowidth":
|
| 105 |
+
self.model_args= kwargs.get("stereowidth_args", None)
|
| 106 |
+
assert self.model_args is not None, "model_args must be provided for stereowidth type"
|
| 107 |
+
|
| 108 |
+
self.distance_type=self.model_args.distance_type
|
| 109 |
+
|
| 110 |
+
from utils.feature_extractors.dsp_features import compute_stereo_width
|
| 111 |
+
self.feat_extractor = lambda x: compute_stereo_width(x).view(-1, 1)
|
| 112 |
+
|
| 113 |
+
elif self.type == "stereoimbalance":
|
| 114 |
+
self.model_args= kwargs.get("stereoimbalance_args", None)
|
| 115 |
+
assert self.model_args is not None, "model_args must be provided for stereoimbalance type"
|
| 116 |
+
self.distance_type=self.model_args.distance_type
|
| 117 |
+
from utils.feature_extractors.dsp_features import compute_stereo_imbalance
|
| 118 |
+
self.feat_extractor = lambda x: compute_stereo_imbalance(x).view(-1, 1)
|
| 119 |
+
|
| 120 |
+
elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
|
| 121 |
+
|
| 122 |
+
self.model_args= kwargs.get("AFxRep_args", None)
|
| 123 |
+
|
| 124 |
+
assert self.model_args is not None, "model_args must be provided for AFxRep type"
|
| 125 |
+
|
| 126 |
+
self.distance_type=self.model_args.distance_type
|
| 127 |
+
|
| 128 |
+
feat_extractor = load_AFxRep(self.model_args, self.device)
|
| 129 |
+
|
| 130 |
+
if self.type == "AFxRep-mid":
|
| 131 |
+
def feat_extractor_mid(x):
|
| 132 |
+
|
| 133 |
+
features= feat_extractor(x)
|
| 134 |
+
|
| 135 |
+
#divide by 2 to get mid and side features
|
| 136 |
+
|
| 137 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 138 |
+
|
| 139 |
+
return feat_mid
|
| 140 |
+
|
| 141 |
+
self.feat_extractor = feat_extractor_mid
|
| 142 |
+
|
| 143 |
+
elif self.type == "AFxRep-side":
|
| 144 |
+
def feat_extractor_side(x):
|
| 145 |
+
|
| 146 |
+
features= feat_extractor(x)
|
| 147 |
+
|
| 148 |
+
#divide by 2 to get mid and side features
|
| 149 |
+
|
| 150 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 151 |
+
|
| 152 |
+
return feat_side
|
| 153 |
+
|
| 154 |
+
self.feat_extractor = feat_extractor_side
|
| 155 |
+
else:
|
| 156 |
+
self.feat_extractor = feat_extractor
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
super().__init__(*args, **kwargs)
|
| 160 |
+
|
| 161 |
+
def compute_feature_distance(self, y, y_hat, sample_rate, type):
|
| 162 |
+
|
| 163 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 164 |
+
y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 165 |
+
|
| 166 |
+
with torch.no_grad():
|
| 167 |
+
feat_y= self.feat_extractor(y)
|
| 168 |
+
feat_y_hat= self.feat_extractor(y_hat)
|
| 169 |
+
|
| 170 |
+
if self.distance_type == "cosine":
|
| 171 |
+
cos_dist= 1- torch.cosine_similarity(feat_y_hat, feat_y, dim=1)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
return {"distance": cos_dist.mean().item()}
|
| 175 |
+
elif self.distance_type == "l1":
|
| 176 |
+
l1_dist = torch.abs(feat_y_hat - feat_y).mean(dim=1)
|
| 177 |
+
|
| 178 |
+
return {"distance": l1_dist.mean().item()}
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
|
| 182 |
+
"""
|
| 183 |
+
Compute the pairwise spectral metric.
|
| 184 |
+
|
| 185 |
+
Args:
|
| 186 |
+
*args: Variable length argument list.
|
| 187 |
+
**kwargs: Arbitrary keyword arguments.
|
| 188 |
+
|
| 189 |
+
Returns:
|
| 190 |
+
The computed pairwise spectral metric.
|
| 191 |
+
"""
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
dict_features={}
|
| 196 |
+
|
| 197 |
+
for key in dict_y.keys():
|
| 198 |
+
y= dict_y[key]
|
| 199 |
+
y_hat= dict_y_hat[key]
|
| 200 |
+
|
| 201 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 202 |
+
|
| 203 |
+
c, d=y.shape
|
| 204 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 205 |
+
|
| 206 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 207 |
+
|
| 208 |
+
y=y.T
|
| 209 |
+
y_hat=y_hat.T
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
if self.type == "spectral":
|
| 213 |
+
from evaluation.automix_evaluation import compute_spectral_features
|
| 214 |
+
dict_features_out= compute_spectral_features(y_hat, y ,self.sample_rate)
|
| 215 |
+
dict_features[key] = dict_features_out['mean_mape_spectral']
|
| 216 |
+
elif self.type=="panning":
|
| 217 |
+
from evaluation.automix_evaluation import compute_panning_features
|
| 218 |
+
dict_features_out = compute_panning_features(y_hat, y, self.sample_rate)
|
| 219 |
+
dict_features[key] = dict_features_out['mean_mape_panning']
|
| 220 |
+
elif self.type=="loudness":
|
| 221 |
+
from evaluation.automix_evaluation import compute_loudness_features
|
| 222 |
+
dict_features_out = compute_loudness_features(y_hat, y, self.sample_rate)
|
| 223 |
+
dict_features[key] = dict_features_out['mean_mape_loudness']
|
| 224 |
+
elif self.type=="dynamic":
|
| 225 |
+
from evaluation.automix_evaluation import compute_dynamic_features
|
| 226 |
+
dict_features_out = compute_dynamic_features(y_hat, y, self.sample_rate)
|
| 227 |
+
dict_features[key] = dict_features_out['mean_mape_dynamic']
|
| 228 |
+
elif self.type=="fx_encoder":
|
| 229 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 230 |
+
dict_features[key] = dict_features_out["distance"]
|
| 231 |
+
elif self.type=="AFxRep":
|
| 232 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 233 |
+
dict_features[key] = dict_features_out["distance"]
|
| 234 |
+
elif self.type=="AFxRep-mid":
|
| 235 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 236 |
+
dict_features[key] = dict_features_out["distance"]
|
| 237 |
+
elif self.type=="AFxRep-side":
|
| 238 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 239 |
+
dict_features[key] = dict_features_out["distance"]
|
| 240 |
+
elif self.type=="fxenc++":
|
| 241 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 242 |
+
dict_features[key] = dict_features_out["distance"]
|
| 243 |
+
elif self.type=="logrms":
|
| 244 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 245 |
+
dict_features[key] = dict_features_out["distance"]
|
| 246 |
+
elif self.type=="crestfactor":
|
| 247 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 248 |
+
dict_features[key] = dict_features_out["distance"]
|
| 249 |
+
elif self.type=="logspread":
|
| 250 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 251 |
+
dict_features[key] = dict_features_out["distance"]
|
| 252 |
+
elif self.type=="stereowidth":
|
| 253 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 254 |
+
dict_features[key] = dict_features_out["distance"]
|
| 255 |
+
elif self.type=="stereoimbalance":
|
| 256 |
+
dict_features_out = self.compute_feature_distance(y, y_hat, self.sample_rate, type=self.type)
|
| 257 |
+
dict_features[key] = dict_features_out["distance"]
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
# Compute the mean of the features across all keys
|
| 261 |
+
|
| 262 |
+
mean_features = sum(dict_features.values()) / len(dict_features)
|
| 263 |
+
|
| 264 |
+
return mean_features, {}
|
| 265 |
+
|
| 266 |
+
class PairwiseStyleMultitrackFeatures(PairwiseMetric):
|
| 267 |
+
"""
|
| 268 |
+
Class for computing the pairwise spectral metric.
|
| 269 |
+
|
| 270 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 271 |
+
to calculate the pairwise spectral metric.
|
| 272 |
+
"""
|
| 273 |
+
def __init__(self,
|
| 274 |
+
type=None,
|
| 275 |
+
sample_rate=44100,
|
| 276 |
+
*args, **kwargs):
|
| 277 |
+
"""
|
| 278 |
+
Initialize the PairwiseSpectral instance.
|
| 279 |
+
|
| 280 |
+
Args:
|
| 281 |
+
*args: Variable length argument list.
|
| 282 |
+
**kwargs: Arbitrary keyword arguments.
|
| 283 |
+
"""
|
| 284 |
+
self.type = type
|
| 285 |
+
self.sample_rate = sample_rate
|
| 286 |
+
|
| 287 |
+
self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 288 |
+
|
| 289 |
+
if self.type == "fx_encoder":
|
| 290 |
+
raise NotImplementedError("Style features for fx_encoder not implemented yet")
|
| 291 |
+
self.model_args= kwargs.get("fx_encoder_args", None)
|
| 292 |
+
|
| 293 |
+
assert self.model_args is not None, "model_args must be provided for fx_encoder type"
|
| 294 |
+
|
| 295 |
+
self.distance_type=self.model_args.distance_type
|
| 296 |
+
|
| 297 |
+
self.feat_extractor = load_fx_encoder(self.model_args, self.device)
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
#self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
|
| 301 |
+
|
| 302 |
+
elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
|
| 303 |
+
|
| 304 |
+
self.model_args= kwargs.get("AFxRep_args", None)
|
| 305 |
+
|
| 306 |
+
assert self.model_args is not None, "model_args must be provided for AFxRep type"
|
| 307 |
+
|
| 308 |
+
self.distance_type=self.model_args.distance_type
|
| 309 |
+
|
| 310 |
+
feat_extractor = load_AFxRep(self.model_args, self.device)
|
| 311 |
+
|
| 312 |
+
if self.type == "AFxRep-mid":
|
| 313 |
+
def feat_extractor_mid(x):
|
| 314 |
+
|
| 315 |
+
features= feat_extractor(x)
|
| 316 |
+
|
| 317 |
+
#divide by 2 to get mid and side features
|
| 318 |
+
|
| 319 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 320 |
+
|
| 321 |
+
return feat_mid
|
| 322 |
+
|
| 323 |
+
self.feat_extractor = feat_extractor_mid
|
| 324 |
+
|
| 325 |
+
elif self.type == "AFxRep-side":
|
| 326 |
+
def feat_extractor_side(x):
|
| 327 |
+
|
| 328 |
+
features= feat_extractor(x)
|
| 329 |
+
|
| 330 |
+
#divide by 2 to get mid and side features
|
| 331 |
+
|
| 332 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 333 |
+
|
| 334 |
+
return feat_side
|
| 335 |
+
|
| 336 |
+
self.feat_extractor = feat_extractor_side
|
| 337 |
+
else:
|
| 338 |
+
self.feat_extractor = feat_extractor
|
| 339 |
+
|
| 340 |
+
elif self.type == "fxenc2048AFv3-fxenc++":
|
| 341 |
+
|
| 342 |
+
self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
|
| 343 |
+
assert self.model_args is not None, "model_args must be provided for fxencAFv2-fxenc++ type"
|
| 344 |
+
|
| 345 |
+
self.distance_type= self.model_args.distance_type
|
| 346 |
+
|
| 347 |
+
fxencoder = load_fx_encoder_plusplus_2048(self.model_args, self.device)
|
| 348 |
+
|
| 349 |
+
def feat_extractor_fn(x):
|
| 350 |
+
z= fxencoder(x)
|
| 351 |
+
z=torch.nn.functional.normalize(z, dim=-1, p=2) # normalize to unit variance
|
| 352 |
+
return z
|
| 353 |
+
|
| 354 |
+
self.feat_extractor = feat_extractor_fn
|
| 355 |
+
|
| 356 |
+
elif self.type == "fxenc2048AFv3-AF":
|
| 357 |
+
|
| 358 |
+
from utils.AF_features_embedding_v2 import AF_fourier_embedding
|
| 359 |
+
AFembedding= AF_fourier_embedding(device=self.device)
|
| 360 |
+
|
| 361 |
+
self.distance_type="cosine"
|
| 362 |
+
|
| 363 |
+
def feat_extracfor_fn(x):
|
| 364 |
+
z, _ = AFembedding.encode(x)
|
| 365 |
+
return z
|
| 366 |
+
|
| 367 |
+
self.feat_extractor = feat_extracfor_fn
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
super().__init__(*args, **kwargs)
|
| 371 |
+
|
| 372 |
+
def compute_feature_distance(self, y, p_hat, sample_rate, type):
|
| 373 |
+
|
| 374 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 375 |
+
#y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 376 |
+
|
| 377 |
+
with torch.no_grad():
|
| 378 |
+
feat_y= self.feat_extractor(y)
|
| 379 |
+
#feat_y_hat= self.feat_extractor(y_hat)
|
| 380 |
+
|
| 381 |
+
assert p_hat.shape == feat_y.shape, f"Shape mismatch: p_hat {p_hat.shape} vs feat_y {feat_y.shape}"
|
| 382 |
+
|
| 383 |
+
if self.distance_type == "cosine":
|
| 384 |
+
cos_dist= 1- torch.cosine_similarity(p_hat, feat_y, dim=1)
|
| 385 |
+
|
| 386 |
+
return {"distance": cos_dist.mean().item()}
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, *args, **kwargs):
|
| 391 |
+
"""
|
| 392 |
+
Compute the pairwise spectral metric.
|
| 393 |
+
|
| 394 |
+
Args:
|
| 395 |
+
*args: Variable length argument list.
|
| 396 |
+
**kwargs: Arbitrary keyword arguments.
|
| 397 |
+
|
| 398 |
+
Returns:
|
| 399 |
+
The computed pairwise spectral metric.
|
| 400 |
+
"""
|
| 401 |
+
|
| 402 |
+
dict_features={}
|
| 403 |
+
|
| 404 |
+
index=0
|
| 405 |
+
|
| 406 |
+
for key in dict_y.keys():
|
| 407 |
+
y= dict_y[key]
|
| 408 |
+
|
| 409 |
+
embed= dict_p_hat[key]
|
| 410 |
+
embed=torch.tensor(embed).to(self.device).unsqueeze(0)
|
| 411 |
+
|
| 412 |
+
print(f"Processing key: {key}, embed shape: {embed.shape}")
|
| 413 |
+
if "AFxRep" in self.type:
|
| 414 |
+
embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
|
| 415 |
+
|
| 416 |
+
if self.type== "AFxRep-mid":
|
| 417 |
+
p_hat= embed_mid
|
| 418 |
+
elif self.type== "AFxRep-side":
|
| 419 |
+
p_hat= embed_side
|
| 420 |
+
elif self.type== "AFxRep":
|
| 421 |
+
p_hat= embed
|
| 422 |
+
elif "fxenc2048AFv3" in self.type:
|
| 423 |
+
|
| 424 |
+
embed=embed*math.sqrt(embed.shape[-1]) # Scale the embedding
|
| 425 |
+
embed_fxenc=embed[...,:2048]/ math.sqrt(2048) # Scale the first 128 dimensions
|
| 426 |
+
embed_AF=embed[...,2048:]/ math.sqrt(64) # Scale the last 128 dimensions
|
| 427 |
+
|
| 428 |
+
if self.type == "fxenc2048AFv3-fxenc++":
|
| 429 |
+
p_hat= embed_fxenc
|
| 430 |
+
elif self.type == "fxenc2048AFv3-AF":
|
| 431 |
+
p_hat= embed_AF
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
n, c, d=y.shape
|
| 435 |
+
|
| 436 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 437 |
+
|
| 438 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 439 |
+
|
| 440 |
+
for i in range(n):
|
| 441 |
+
y_i=y[i].T
|
| 442 |
+
|
| 443 |
+
if self.type=="fx_encoder":
|
| 444 |
+
raise NotImplementedError("Style features for fx_encoder not implemented yet")
|
| 445 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 446 |
+
dict_features[key] = dict_features_out["distance"]
|
| 447 |
+
elif self.type=="AFxRep":
|
| 448 |
+
dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
|
| 449 |
+
dict_features[index] = dict_features_out["distance"]
|
| 450 |
+
elif self.type=="AFxRep-mid":
|
| 451 |
+
dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
|
| 452 |
+
dict_features[index] = dict_features_out["distance"]
|
| 453 |
+
elif self.type=="AFxRep-side":
|
| 454 |
+
dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
|
| 455 |
+
dict_features[index] = dict_features_out["distance"]
|
| 456 |
+
elif self.type=="fxenc2048AFv3-fxenc++":
|
| 457 |
+
dict_features_out = self.compute_feature_distance(y_i, p_hat[i], self.sample_rate, type=self.type)
|
| 458 |
+
dict_features[index] = dict_features_out["distance"]
|
| 459 |
+
elif self.type=="fxenc2048AFv3-AF":
|
| 460 |
+
dict_features_out = self.compute_feature_distance(y_i, p_hat, self.sample_rate, type=self.type)
|
| 461 |
+
dict_features[index] = dict_features_out["distance"]
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
mean_features = sum(dict_features.values()) / len(dict_features)
|
| 465 |
+
|
| 466 |
+
return mean_features, {}
|
| 467 |
+
class PairwiseStyleFeatures(PairwiseMetric):
|
| 468 |
+
"""
|
| 469 |
+
Class for computing the pairwise spectral metric.
|
| 470 |
+
|
| 471 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 472 |
+
to calculate the pairwise spectral metric.
|
| 473 |
+
"""
|
| 474 |
+
def __init__(self,
|
| 475 |
+
type=None,
|
| 476 |
+
sample_rate=44100,
|
| 477 |
+
*args, **kwargs):
|
| 478 |
+
"""
|
| 479 |
+
Initialize the PairwiseSpectral instance.
|
| 480 |
+
|
| 481 |
+
Args:
|
| 482 |
+
*args: Variable length argument list.
|
| 483 |
+
**kwargs: Arbitrary keyword arguments.
|
| 484 |
+
"""
|
| 485 |
+
self.type = type
|
| 486 |
+
self.sample_rate = sample_rate
|
| 487 |
+
|
| 488 |
+
self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 489 |
+
|
| 490 |
+
if self.type == "fx_encoder":
|
| 491 |
+
raise NotImplementedError("Style features for fx_encoder not implemented yet")
|
| 492 |
+
self.model_args= kwargs.get("fx_encoder_args", None)
|
| 493 |
+
|
| 494 |
+
assert self.model_args is not None, "model_args must be provided for fx_encoder type"
|
| 495 |
+
|
| 496 |
+
self.distance_type=self.model_args.distance_type
|
| 497 |
+
|
| 498 |
+
self.feat_extractor = load_fx_encoder(self.model_args, self.device)
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
#self.feat_extractor = load_effects_encoder(ckpt_path=ckpt_path).to(self.device)
|
| 502 |
+
|
| 503 |
+
elif self.type== "AFxRep-mid" or self.type== "AFxRep-side" or self.type== "AFxRep":
|
| 504 |
+
|
| 505 |
+
self.model_args= kwargs.get("AFxRep_args", None)
|
| 506 |
+
|
| 507 |
+
assert self.model_args is not None, "model_args must be provided for AFxRep type"
|
| 508 |
+
|
| 509 |
+
self.distance_type=self.model_args.distance_type
|
| 510 |
+
|
| 511 |
+
feat_extractor = load_AFxRep(self.model_args, self.device)
|
| 512 |
+
|
| 513 |
+
if self.type == "AFxRep-mid":
|
| 514 |
+
def feat_extractor_mid(x):
|
| 515 |
+
|
| 516 |
+
features= feat_extractor(x)
|
| 517 |
+
|
| 518 |
+
#divide by 2 to get mid and side features
|
| 519 |
+
|
| 520 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 521 |
+
|
| 522 |
+
return feat_mid
|
| 523 |
+
|
| 524 |
+
self.feat_extractor = feat_extractor_mid
|
| 525 |
+
|
| 526 |
+
elif self.type == "AFxRep-side":
|
| 527 |
+
def feat_extractor_side(x):
|
| 528 |
+
|
| 529 |
+
features= feat_extractor(x)
|
| 530 |
+
|
| 531 |
+
#divide by 2 to get mid and side features
|
| 532 |
+
|
| 533 |
+
feat_mid, feat_side = features.chunk(2, dim=-1)
|
| 534 |
+
|
| 535 |
+
return feat_side
|
| 536 |
+
|
| 537 |
+
self.feat_extractor = feat_extractor_side
|
| 538 |
+
else:
|
| 539 |
+
self.feat_extractor = feat_extractor
|
| 540 |
+
|
| 541 |
+
elif self.type == "fxenc2048AFv3-fxenc++":
|
| 542 |
+
|
| 543 |
+
self.model_args= kwargs.get("fx_encoder_plusplus_args", None)
|
| 544 |
+
assert self.model_args is not None, "model_args must be provided for fxencAFv2-fxenc++ type"
|
| 545 |
+
|
| 546 |
+
self.distance_type= self.model_args.distance_type
|
| 547 |
+
|
| 548 |
+
fxencoder = load_fx_encoder_plusplus_2048(self.model_args, self.device)
|
| 549 |
+
|
| 550 |
+
def feat_extractor_fn(x):
|
| 551 |
+
z= fxencoder(x)
|
| 552 |
+
z=torch.nn.functional.normalize(z, dim=-1, p=2) # normalize to unit variance
|
| 553 |
+
return z
|
| 554 |
+
|
| 555 |
+
self.feat_extractor = feat_extractor_fn
|
| 556 |
+
|
| 557 |
+
elif self.type == "fxenc2048AFv3-AF":
|
| 558 |
+
|
| 559 |
+
from utils.AF_features_embedding_v2 import AF_fourier_embedding
|
| 560 |
+
AFembedding= AF_fourier_embedding(device=self.device)
|
| 561 |
+
|
| 562 |
+
self.distance_type="cosine"
|
| 563 |
+
|
| 564 |
+
def feat_extracfor_fn(x):
|
| 565 |
+
z, _ = AFembedding.encode(x)
|
| 566 |
+
return z
|
| 567 |
+
|
| 568 |
+
self.feat_extractor = feat_extracfor_fn
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
super().__init__(*args, **kwargs)
|
| 572 |
+
|
| 573 |
+
def compute_feature_distance(self, y, p_hat, sample_rate, type):
|
| 574 |
+
|
| 575 |
+
y=torch.tensor(y).permute(1,0).unsqueeze(0).to(self.device)
|
| 576 |
+
#y_hat=torch.tensor(y_hat).permute(1,0).unsqueeze(0).to(self.device)
|
| 577 |
+
|
| 578 |
+
with torch.no_grad():
|
| 579 |
+
feat_y= self.feat_extractor(y)
|
| 580 |
+
#feat_y_hat= self.feat_extractor(y_hat)
|
| 581 |
+
|
| 582 |
+
assert p_hat.shape == feat_y.shape, f"Shape mismatch: p_hat {p_hat.shape} vs feat_y {feat_y.shape}"
|
| 583 |
+
|
| 584 |
+
if self.distance_type == "cosine":
|
| 585 |
+
cos_dist= 1- torch.cosine_similarity(p_hat, feat_y, dim=1)
|
| 586 |
+
|
| 587 |
+
return {"distance": cos_dist.mean().item()}
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
def compute(self, dict_y, dict_y_hat, dict_x, dict_p_hat=None, *args, **kwargs):
|
| 592 |
+
"""
|
| 593 |
+
Compute the pairwise spectral metric.
|
| 594 |
+
|
| 595 |
+
Args:
|
| 596 |
+
*args: Variable length argument list.
|
| 597 |
+
**kwargs: Arbitrary keyword arguments.
|
| 598 |
+
|
| 599 |
+
Returns:
|
| 600 |
+
The computed pairwise spectral metric.
|
| 601 |
+
"""
|
| 602 |
+
|
| 603 |
+
dict_features={}
|
| 604 |
+
|
| 605 |
+
for key in dict_y.keys():
|
| 606 |
+
y= dict_y[key]
|
| 607 |
+
|
| 608 |
+
embed= dict_p_hat[key]
|
| 609 |
+
embed=torch.tensor(embed).to(self.device).unsqueeze(0)
|
| 610 |
+
|
| 611 |
+
print(f"Processing key: {key}, embed shape: {embed.shape}")
|
| 612 |
+
if "AFxRep" in self.type:
|
| 613 |
+
embed_mid, embed_side = torch.chunk(embed, 2, dim=-1)
|
| 614 |
+
|
| 615 |
+
if self.type== "AFxRep-mid":
|
| 616 |
+
p_hat= embed_mid
|
| 617 |
+
elif self.type== "AFxRep-side":
|
| 618 |
+
p_hat= embed_side
|
| 619 |
+
elif self.type== "AFxRep":
|
| 620 |
+
p_hat= embed
|
| 621 |
+
elif "fxenc2048AFv3" in self.type:
|
| 622 |
+
|
| 623 |
+
embed=embed*math.sqrt(embed.shape[-1]) # Scale the embedding
|
| 624 |
+
embed_fxenc=embed[...,:2048]/ math.sqrt(2048) # Scale the first 128 dimensions
|
| 625 |
+
embed_AF=embed[...,2048:]/ math.sqrt(64) # Scale the last 128 dimensions
|
| 626 |
+
|
| 627 |
+
if self.type == "fxenc2048AFv3-fxenc++":
|
| 628 |
+
p_hat= embed_fxenc
|
| 629 |
+
elif self.type == "fxenc2048AFv3-AF":
|
| 630 |
+
p_hat= embed_AF
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
c, d=y.shape
|
| 634 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 635 |
+
|
| 636 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 637 |
+
|
| 638 |
+
y=y.T
|
| 639 |
+
#y_hat=y_hat.T
|
| 640 |
+
|
| 641 |
+
if self.type=="fx_encoder":
|
| 642 |
+
raise NotImplementedError("Style features for fx_encoder not implemented yet")
|
| 643 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 644 |
+
dict_features[key] = dict_features_out["distance"]
|
| 645 |
+
elif self.type=="AFxRep":
|
| 646 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 647 |
+
dict_features[key] = dict_features_out["distance"]
|
| 648 |
+
elif self.type=="AFxRep-mid":
|
| 649 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 650 |
+
dict_features[key] = dict_features_out["distance"]
|
| 651 |
+
elif self.type=="AFxRep-side":
|
| 652 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 653 |
+
dict_features[key] = dict_features_out["distance"]
|
| 654 |
+
elif self.type=="fxenc2048AFv3-fxenc++":
|
| 655 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 656 |
+
dict_features[key] = dict_features_out["distance"]
|
| 657 |
+
elif self.type=="fxenc2048AFv3-AF":
|
| 658 |
+
dict_features_out = self.compute_feature_distance(y, p_hat, self.sample_rate, type=self.type)
|
| 659 |
+
dict_features[key] = dict_features_out["distance"]
|
| 660 |
+
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
# Compute the mean of the features across all keys
|
| 664 |
+
|
| 665 |
+
mean_features = sum(dict_features.values()) / len(dict_features)
|
| 666 |
+
|
| 667 |
+
return mean_features, {}
|
| 668 |
+
|
| 669 |
+
class PairwiseIMMSS(PairwiseMetric):
|
| 670 |
+
"""
|
| 671 |
+
Class for computing the pairwise LDR metric.
|
| 672 |
+
|
| 673 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 674 |
+
to calculate the pairwise LDR metric.
|
| 675 |
+
"""
|
| 676 |
+
def __init__(self, mode=None, *args, **kwargs):
|
| 677 |
+
"""
|
| 678 |
+
Initialize the PairwiseLDR instance.
|
| 679 |
+
|
| 680 |
+
Args:
|
| 681 |
+
*args: Variable length argument list.
|
| 682 |
+
**kwargs: Arbitrary keyword arguments.
|
| 683 |
+
"""
|
| 684 |
+
super().__init__(*args, **kwargs)
|
| 685 |
+
|
| 686 |
+
assert mode is not None, "Mode must be specified for PairwiseLDR"
|
| 687 |
+
|
| 688 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 689 |
+
if mode == "mss-lr":
|
| 690 |
+
multi_scale_spectral_ori = MultiScale_Spectral_Loss_MidSide_DDSP(mode='ori', eps=1e-6, device=device)
|
| 691 |
+
self.metric= lambda y_hat, y: multi_scale_spectral_ori(y_hat, y)
|
| 692 |
+
elif mode == "mss-ms":
|
| 693 |
+
multi_scale_spectral_midside = MultiScale_Spectral_Loss_MidSide_DDSP(mode='midside', eps=1e-6, device=device)
|
| 694 |
+
self.metric= lambda y_hat, y: multi_scale_spectral_midside(y_hat, y)
|
| 695 |
+
|
| 696 |
+
def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
|
| 697 |
+
"""
|
| 698 |
+
Compute the pairwise spectral metric.
|
| 699 |
+
|
| 700 |
+
Args:
|
| 701 |
+
*args: Variable length argument list.
|
| 702 |
+
**kwargs: Arbitrary keyword arguments.
|
| 703 |
+
|
| 704 |
+
Returns:
|
| 705 |
+
The computed pairwise spectral metric.
|
| 706 |
+
"""
|
| 707 |
+
|
| 708 |
+
dict_metrics={}
|
| 709 |
+
|
| 710 |
+
for key in dict_y.keys():
|
| 711 |
+
y= dict_y[key]
|
| 712 |
+
y_hat= dict_y_hat[key]
|
| 713 |
+
|
| 714 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 715 |
+
|
| 716 |
+
c, d=y.shape
|
| 717 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 718 |
+
|
| 719 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 720 |
+
|
| 721 |
+
y=torch.from_numpy(y).cuda().unsqueeze(0)
|
| 722 |
+
y_hat=torch.from_numpy(y_hat).cuda().unsqueeze(0)
|
| 723 |
+
|
| 724 |
+
metric=self.metric(y_hat, y)
|
| 725 |
+
|
| 726 |
+
dict_metrics[key] = metric.item()
|
| 727 |
+
|
| 728 |
+
mean_features = sum(dict_metrics.values()) / len(dict_metrics)
|
| 729 |
+
|
| 730 |
+
return mean_features, {}
|
| 731 |
+
class PairwiseLDR(PairwiseMetric):
|
| 732 |
+
"""
|
| 733 |
+
Class for computing the pairwise LDR metric.
|
| 734 |
+
|
| 735 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 736 |
+
to calculate the pairwise LDR metric.
|
| 737 |
+
"""
|
| 738 |
+
def __init__(self, mode=None, *args, **kwargs):
|
| 739 |
+
"""
|
| 740 |
+
Initialize the PairwiseLDR instance.
|
| 741 |
+
|
| 742 |
+
Args:
|
| 743 |
+
*args: Variable length argument list.
|
| 744 |
+
**kwargs: Arbitrary keyword arguments.
|
| 745 |
+
"""
|
| 746 |
+
super().__init__(*args, **kwargs)
|
| 747 |
+
|
| 748 |
+
assert mode is not None, "Mode must be specified for PairwiseLDR"
|
| 749 |
+
|
| 750 |
+
if mode == "mldr-lr":
|
| 751 |
+
from evaluation.ldr import MLDRLoss
|
| 752 |
+
raw_metric= MLDRLoss(
|
| 753 |
+
sr=44100,
|
| 754 |
+
s_taus=[50, 100],
|
| 755 |
+
l_taus=[1000, 2000],
|
| 756 |
+
).cuda()
|
| 757 |
+
self.metric=lambda y_hat, y: 0.5*raw_metric(y_hat, y)
|
| 758 |
+
elif mode == "mldr-ms":
|
| 759 |
+
from evaluation.ldr import MLDRLoss
|
| 760 |
+
raw_metric= MLDRLoss(
|
| 761 |
+
sr=44100,
|
| 762 |
+
s_taus=[50, 100],
|
| 763 |
+
l_taus=[1000, 2000],
|
| 764 |
+
mid_side=True
|
| 765 |
+
).cuda()
|
| 766 |
+
self.metric=lambda y_hat, y: 0.25*raw_metric(y_hat, y)
|
| 767 |
+
|
| 768 |
+
def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
|
| 769 |
+
"""
|
| 770 |
+
Compute the pairwise spectral metric.
|
| 771 |
+
|
| 772 |
+
Args:
|
| 773 |
+
*args: Variable length argument list.
|
| 774 |
+
**kwargs: Arbitrary keyword arguments.
|
| 775 |
+
|
| 776 |
+
Returns:
|
| 777 |
+
The computed pairwise spectral metric.
|
| 778 |
+
"""
|
| 779 |
+
|
| 780 |
+
dict_metrics={}
|
| 781 |
+
|
| 782 |
+
for key in dict_y.keys():
|
| 783 |
+
y= dict_y[key]
|
| 784 |
+
y_hat= dict_y_hat[key]
|
| 785 |
+
|
| 786 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 787 |
+
|
| 788 |
+
c, d=y.shape
|
| 789 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 790 |
+
|
| 791 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 792 |
+
|
| 793 |
+
y=y.T
|
| 794 |
+
y_hat=y_hat.T
|
| 795 |
+
|
| 796 |
+
y=torch.from_numpy(y).cuda().unsqueeze(0)
|
| 797 |
+
y_hat=torch.from_numpy(y_hat).cuda().unsqueeze(0)
|
| 798 |
+
|
| 799 |
+
metric=self.metric(y_hat, y)
|
| 800 |
+
|
| 801 |
+
dict_metrics[key] = metric.item()
|
| 802 |
+
|
| 803 |
+
mean_features = sum(dict_metrics.values()) / len(dict_metrics)
|
| 804 |
+
|
| 805 |
+
return mean_features, {}
|
| 806 |
+
|
| 807 |
+
|
| 808 |
+
class PairwiseAuraloss(PairwiseMetric):
|
| 809 |
+
"""
|
| 810 |
+
Class for computing the pairwise MSS metric.
|
| 811 |
+
|
| 812 |
+
This class inherits from PairwiseMetric and implements the compute method
|
| 813 |
+
to calculate the pairwise MSS metric.
|
| 814 |
+
"""
|
| 815 |
+
def __init__(self, mode=None, *args, **kwargs):
|
| 816 |
+
"""
|
| 817 |
+
Initialize the PairwiseMSS instance.
|
| 818 |
+
|
| 819 |
+
Args:
|
| 820 |
+
*args: Variable length argument list.
|
| 821 |
+
**kwargs: Arbitrary keyword arguments.
|
| 822 |
+
"""
|
| 823 |
+
super().__init__(*args, **kwargs)
|
| 824 |
+
|
| 825 |
+
assert mode is not None, "Mode must be specified for PairwiseMSS"
|
| 826 |
+
|
| 827 |
+
if mode == "mss-lr":
|
| 828 |
+
from auraloss.freq import MultiResolutionSTFTLoss
|
| 829 |
+
raw_metric=MultiResolutionSTFTLoss(
|
| 830 |
+
[128, 512, 2048],
|
| 831 |
+
[32, 128, 512],
|
| 832 |
+
[128, 512, 2048],
|
| 833 |
+
sample_rate=44100,
|
| 834 |
+
perceptual_weighting=True,
|
| 835 |
+
).cuda()
|
| 836 |
+
self.metric=lambda y_hat, y: raw_metric(y_hat, y)
|
| 837 |
+
elif mode == "mss-ms":
|
| 838 |
+
from auraloss.freq import SumAndDifferenceSTFTLoss
|
| 839 |
+
raw_metric=SumAndDifferenceSTFTLoss(
|
| 840 |
+
[128, 512, 2048],
|
| 841 |
+
[32, 128, 512],
|
| 842 |
+
[128, 512, 2048],
|
| 843 |
+
sample_rate=44100,
|
| 844 |
+
perceptual_weighting=True,
|
| 845 |
+
).cuda()
|
| 846 |
+
self.metric=lambda y_hat, y: 0.5*raw_metric(y_hat, y)
|
| 847 |
+
|
| 848 |
+
def compute(self, dict_y, dict_y_hat, dict_x, *args, **kwargs):
|
| 849 |
+
"""
|
| 850 |
+
Compute the pairwise spectral metric.
|
| 851 |
+
|
| 852 |
+
Args:
|
| 853 |
+
*args: Variable length argument list.
|
| 854 |
+
**kwargs: Arbitrary keyword arguments.
|
| 855 |
+
|
| 856 |
+
Returns:
|
| 857 |
+
The computed pairwise spectral metric.
|
| 858 |
+
"""
|
| 859 |
+
|
| 860 |
+
dict_metrics={}
|
| 861 |
+
|
| 862 |
+
for key in dict_y.keys():
|
| 863 |
+
y= dict_y[key]
|
| 864 |
+
y_hat= dict_y_hat[key]
|
| 865 |
+
|
| 866 |
+
assert y.shape == y_hat.shape, f"Shape mismatch for key {key}: {y.shape} vs {y_hat.shape}"
|
| 867 |
+
|
| 868 |
+
c, d=y.shape
|
| 869 |
+
#assert b==1, f"Expected batch size of 1, got {b} for key {key}"
|
| 870 |
+
|
| 871 |
+
assert c==2, f"Expected 2 channels, got {c} for key {key}"
|
| 872 |
+
|
| 873 |
+
#y=y.T
|
| 874 |
+
#y_hat=y_hat.T
|
| 875 |
+
|
| 876 |
+
y=torch.from_numpy(y).cuda().unsqueeze(0)
|
| 877 |
+
y_hat=torch.from_numpy(y_hat).cuda().unsqueeze(0)
|
| 878 |
+
|
| 879 |
+
metric=self.metric(y_hat, y)
|
| 880 |
+
|
| 881 |
+
dict_metrics[key] = metric.item()
|
| 882 |
+
|
| 883 |
+
mean_features = sum(dict_metrics.values()) / len(dict_metrics)
|
| 884 |
+
|
| 885 |
+
return mean_features, {}
|
| 886 |
+
|
| 887 |
+
def metric_factory(metric_name, sample_rate, *args, **kwargs):
|
| 888 |
+
"""
|
| 889 |
+
Factory function to create a metric function based on the metric name.
|
| 890 |
+
|
| 891 |
+
Args:
|
| 892 |
+
metric_name (str): The name of the metric to create.
|
| 893 |
+
*args: Variable length argument list.
|
| 894 |
+
**kwargs: Arbitrary keyword arguments.
|
| 895 |
+
|
| 896 |
+
Returns:
|
| 897 |
+
An instance of a class that implements the metric function.
|
| 898 |
+
"""
|
| 899 |
+
if metric_name == "pairwise-spectral":
|
| 900 |
+
return PairwiseFeatures(*args, **kwargs, type="spectral", sample_rate=sample_rate)
|
| 901 |
+
elif metric_name == "pairwise-panning":
|
| 902 |
+
return PairwiseFeatures(*args, **kwargs, type="panning", sample_rate=sample_rate)
|
| 903 |
+
elif metric_name == "pairwise-loudness":
|
| 904 |
+
return PairwiseFeatures(*args, **kwargs, type="loudness", sample_rate=sample_rate)
|
| 905 |
+
elif metric_name == "pairwise-dynamic":
|
| 906 |
+
return PairwiseFeatures(*args, **kwargs, type="dynamic", sample_rate=sample_rate)
|
| 907 |
+
elif metric_name == "pairwise-mss-lr":
|
| 908 |
+
return PairwiseAuraloss(mode="mss-lr",*args, **kwargs)
|
| 909 |
+
elif metric_name == "pairwise-mss-ms":
|
| 910 |
+
return PairwiseAuraloss(mode="mss-ms",*args, **kwargs)
|
| 911 |
+
elif metric_name == "pairwise-IM-mss-lr":
|
| 912 |
+
return PairwiseIMMSS(mode="mss-lr",*args, **kwargs)
|
| 913 |
+
elif metric_name == "pairwise-IM-mss-ms":
|
| 914 |
+
return PairwiseIMMSS(mode="mss-ms",*args, **kwargs)
|
| 915 |
+
elif metric_name == "pairwise-mldr-lr":
|
| 916 |
+
return PairwiseLDR(mode="mldr-lr",*args, **kwargs)
|
| 917 |
+
elif metric_name == "pairwise-mldr-ms":
|
| 918 |
+
return PairwiseLDR(mode="mldr-ms",*args, **kwargs)
|
| 919 |
+
elif metric_name == "pairwise-fx_encoder":
|
| 920 |
+
return PairwiseFeatures(*args, **kwargs, type="fx_encoder", sample_rate=sample_rate, model_args=kwargs.get('fx_encoder_args', None))
|
| 921 |
+
elif metric_name == "pairwise-AFxRep":
|
| 922 |
+
return PairwiseFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 923 |
+
elif metric_name == "pairwise-AFxRep-mid":
|
| 924 |
+
return PairwiseFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 925 |
+
elif metric_name == "pairwise-AFxRep-side":
|
| 926 |
+
return PairwiseFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 927 |
+
elif metric_name == "pairwise-style-AFxRep":
|
| 928 |
+
return PairwiseStyleFeatures(*args, **kwargs, type="AFxRep", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 929 |
+
elif metric_name == "pairwise-style-AFxRep-mid":
|
| 930 |
+
return PairwiseStyleFeatures(*args, **kwargs, type="AFxRep-mid", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 931 |
+
elif metric_name == "pairwise-style-AFxRep-side":
|
| 932 |
+
return PairwiseStyleFeatures(*args, **kwargs, type="AFxRep-side", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 933 |
+
elif metric_name == "pairwise-style-fxenc2048AFv3-fxenc++":
|
| 934 |
+
return PairwiseStyleFeatures(*args, **kwargs, type="fxenc2048AFv3-fxenc++", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 935 |
+
elif metric_name == "pairwise-style-fxenc2048AFv3-AF":
|
| 936 |
+
return PairwiseStyleFeatures(*args, **kwargs, type="fxenc2048AFv3-AF", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 937 |
+
elif metric_name == "pairwise-style-multitrack-fxenc2048AFv3-fxenc++":
|
| 938 |
+
return PairwiseStyleMultitrackFeatures(*args, **kwargs, type="fxenc2048AFv3-fxenc++", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 939 |
+
elif metric_name == "pairwise-style-multitrack-fxenc2048AFv3-AF":
|
| 940 |
+
return PairwiseStyleMultitrackFeatures(*args, **kwargs, type="fxenc2048AFv3-AF", sample_rate=sample_rate, model_args=kwargs.get('AFxRep_args', None))
|
| 941 |
+
elif metric_name == "pairwise-fxenc++":
|
| 942 |
+
return PairwiseFeatures(*args, **kwargs, type="fxenc++", sample_rate=sample_rate, model_args=kwargs.get('fx_encoder_plusplus_args', None))
|
| 943 |
+
elif metric_name == "pairwise-logrms":
|
| 944 |
+
return PairwiseFeatures(*args, **kwargs, type="logrms", sample_rate=sample_rate, model_args=kwargs.get('logrms_args', None))
|
| 945 |
+
elif metric_name == "pairwise-crestfactor":
|
| 946 |
+
return PairwiseFeatures(*args, **kwargs, type="crestfactor", sample_rate=sample_rate, model_args=kwargs.get('crestfactor_args', None))
|
| 947 |
+
elif metric_name == "pairwise-logspread":
|
| 948 |
+
return PairwiseFeatures(*args, **kwargs, type="logspread", sample_rate=sample_rate, model_args=kwargs.get('logspread_args', None))
|
| 949 |
+
elif metric_name == "pairwise-stereowidth":
|
| 950 |
+
return PairwiseFeatures(*args, **kwargs, type="stereowidth", sample_rate=sample_rate, model_args=kwargs.get('stereowidth_args', None))
|
| 951 |
+
elif metric_name == "pairwise-stereoimbalance":
|
| 952 |
+
return PairwiseFeatures(*args, **kwargs, type="stereoimbalance", sample_rate=sample_rate, model_args=kwargs.get('stereoimbalance_args', None))
|
| 953 |
+
else:
|
| 954 |
+
raise ValueError(f"Unknown metric: {metric_name}")
|
| 955 |
+
|
| 956 |
+
# Example usage:
|
| 957 |
+
#metric_instance = metric_factory("pairwise-spectral")
|
| 958 |
+
#```
|
utils/feature_extractors/AF_features_embedding.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from utils.feature_extractors.dsp_features import compute_log_rms_gated_max, compute_crest_factor, compute_stereo_width, compute_stereo_imbalance, compute_log_spread
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
class AF_fourier_embedding:
|
| 6 |
+
def __init__(self,
|
| 7 |
+
input_dim=8,
|
| 8 |
+
output_dim=64,
|
| 9 |
+
sigma=0.2,
|
| 10 |
+
log_rms_shift=-26.5, #calculated as the mean from the dataset
|
| 11 |
+
log_rms_scale=7.0, #calculated as the std from the dataset
|
| 12 |
+
crest_shift=16.7, #calculated as the mean from the dataset
|
| 13 |
+
crest_scale=6.3,
|
| 14 |
+
log_spread_shift=-20.0, #calculated as the mean from the dataset
|
| 15 |
+
log_spread_scale=20.0, #calculated as the std from the dataset
|
| 16 |
+
stereo_width_shift=0.28,
|
| 17 |
+
stereo_width_scale=0.39,
|
| 18 |
+
stereo_imbalance_shift=0.0,
|
| 19 |
+
stereo_imbalance_scale=0.35,
|
| 20 |
+
device="cpu"
|
| 21 |
+
):
|
| 22 |
+
"""
|
| 23 |
+
Deterministic Fourier feature transformer using fixed cosine-based projection
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
self.device = device
|
| 27 |
+
# Ensure output_dim is even and >= 2 * input_dim
|
| 28 |
+
self.output_dim = max(input_dim * 2, output_dim)
|
| 29 |
+
if self.output_dim % 2 != 0:
|
| 30 |
+
self.output_dim += 1
|
| 31 |
+
|
| 32 |
+
self.input_dim = input_dim
|
| 33 |
+
self.sigma = sigma
|
| 34 |
+
|
| 35 |
+
# Create deterministic projection matrix
|
| 36 |
+
self.projection = self._create_deterministic_projection(input_dim, self.output_dim // 2, sigma)
|
| 37 |
+
self.projection = self.projection.to(self.device)
|
| 38 |
+
|
| 39 |
+
# Normalization factor
|
| 40 |
+
self.scale_factor = math.sqrt(2.0 / self.output_dim)
|
| 41 |
+
|
| 42 |
+
self.log_rms_shift = log_rms_shift
|
| 43 |
+
self.log_rms_scale = log_rms_scale
|
| 44 |
+
self.crest_shift = crest_shift
|
| 45 |
+
self.crest_scale = crest_scale
|
| 46 |
+
self.log_spread_shift = log_spread_shift
|
| 47 |
+
self.log_spread_scale = log_spread_scale
|
| 48 |
+
self.stereo_width_shift = stereo_width_shift
|
| 49 |
+
self.stereo_width_scale = stereo_width_scale
|
| 50 |
+
self.stereo_imbalance_shift = stereo_imbalance_shift
|
| 51 |
+
self.stereo_imbalance_scale = stereo_imbalance_scale
|
| 52 |
+
|
| 53 |
+
def _create_deterministic_projection(self, input_dim, proj_dim, sigma):
|
| 54 |
+
"""
|
| 55 |
+
Create a deterministic projection matrix using a cosine basis
|
| 56 |
+
"""
|
| 57 |
+
# Cosine-based matrix (like DCT type-II)
|
| 58 |
+
projection = torch.zeros(input_dim, proj_dim)
|
| 59 |
+
for i in range(input_dim):
|
| 60 |
+
for j in range(proj_dim):
|
| 61 |
+
projection[i, j] = math.cos(math.pi * (i + 0.5) * (j + 1) / proj_dim)
|
| 62 |
+
|
| 63 |
+
return projection * sigma
|
| 64 |
+
|
| 65 |
+
def encode(self, x):
|
| 66 |
+
|
| 67 |
+
log_rms=compute_log_rms_gated_max(x)
|
| 68 |
+
crest_factor= compute_crest_factor(x)
|
| 69 |
+
log_spread= compute_log_spread(x)
|
| 70 |
+
stereo_width= compute_stereo_width(x)
|
| 71 |
+
stereo_imbalance= compute_stereo_imbalance(x)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
log_rms_std, crest_factor_std, log_spread_std, stereo_width_std, stereo_imbalance_std = self.standardize_features(
|
| 75 |
+
log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
embedding= self.transform(
|
| 79 |
+
log_rms_std, crest_factor_std, log_spread_std, stereo_width_std, stereo_imbalance_std
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
return embedding, (log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance)
|
| 84 |
+
|
| 85 |
+
def decode(self, fourier_features):
|
| 86 |
+
"""
|
| 87 |
+
Invert Fourier features back to original feature space
|
| 88 |
+
(approximate due to phase-only reconstruction)
|
| 89 |
+
"""
|
| 90 |
+
reconstructed = self.inverse_transform(fourier_features)
|
| 91 |
+
|
| 92 |
+
# Reshape back to original feature dimensions
|
| 93 |
+
log_rms= reconstructed[:,0:2]
|
| 94 |
+
crest_factor = reconstructed[:,2:4]
|
| 95 |
+
log_spread= reconstructed[:,4:6]
|
| 96 |
+
stereo_width = reconstructed[:,6:7]
|
| 97 |
+
stereo_imbalance = reconstructed[:,7:8]
|
| 98 |
+
|
| 99 |
+
log_rms, crest_factor, log_spread,stereo_width, stereo_imbalance = self.destandardize_features(
|
| 100 |
+
log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
return log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance
|
| 104 |
+
|
| 105 |
+
def standardize_features(self, log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance):
|
| 106 |
+
"""
|
| 107 |
+
Standardize features using pre-computed mean and std
|
| 108 |
+
"""
|
| 109 |
+
log_rms = (log_rms - self.log_rms_shift) / self.log_rms_scale
|
| 110 |
+
crest_factor = (crest_factor - self.crest_shift) / self.crest_scale
|
| 111 |
+
log_spread = (log_spread - self.log_spread_shift) / self.log_spread_scale
|
| 112 |
+
stereo_width = (stereo_width - self.stereo_width_shift) / self.stereo_width_scale
|
| 113 |
+
stereo_imbalance = (stereo_imbalance - self.stereo_imbalance_shift) / self.stereo_imbalance_scale
|
| 114 |
+
|
| 115 |
+
return log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance
|
| 116 |
+
|
| 117 |
+
def destandardize_features(self, log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance):
|
| 118 |
+
"""
|
| 119 |
+
Reverse standardization to get back to original feature space
|
| 120 |
+
"""
|
| 121 |
+
log_rms = log_rms * self.log_rms_scale + self.log_rms_shift
|
| 122 |
+
crest_factor = crest_factor * self.crest_scale + self.crest_shift
|
| 123 |
+
log_spread = log_spread * self.log_spread_scale + self.log_spread_shift
|
| 124 |
+
stereo_width = stereo_width * self.stereo_width_scale + self.stereo_width_shift
|
| 125 |
+
stereo_imbalance = stereo_imbalance * self.stereo_imbalance_scale + self.stereo_imbalance_shift
|
| 126 |
+
|
| 127 |
+
return log_rms, crest_factor, log_spread, stereo_width, stereo_imbalance
|
| 128 |
+
|
| 129 |
+
def transform(self, log_rms, crest_factor,log_spread, stereo_width, stereo_imbalance):
|
| 130 |
+
"""
|
| 131 |
+
Transform features using the stored projection matrix
|
| 132 |
+
"""
|
| 133 |
+
|
| 134 |
+
flat_features=torch.cat([log_rms, crest_factor, log_spread, stereo_width.unsqueeze(-1), stereo_imbalance.unsqueeze(-1)], dim=-1)
|
| 135 |
+
|
| 136 |
+
# Project and transform
|
| 137 |
+
projected = flat_features @ self.projection
|
| 138 |
+
cos_features = torch.cos(projected)
|
| 139 |
+
sin_features = torch.sin(projected)
|
| 140 |
+
|
| 141 |
+
# Concatenate and normalize
|
| 142 |
+
return torch.cat([cos_features, sin_features], dim=-1) * self.scale_factor
|
| 143 |
+
|
| 144 |
+
def inverse_transform(self, fourier_features):
|
| 145 |
+
"""
|
| 146 |
+
Invert Fourier features back to original feature space
|
| 147 |
+
(approximate due to phase-only reconstruction)
|
| 148 |
+
"""
|
| 149 |
+
# Split into cosine and sine components
|
| 150 |
+
feature_dim = fourier_features.shape[-1] // 2
|
| 151 |
+
cos_features = fourier_features[:, :feature_dim]
|
| 152 |
+
sin_features = fourier_features[:, feature_dim:]
|
| 153 |
+
|
| 154 |
+
# Denormalize
|
| 155 |
+
cos_features = cos_features / self.scale_factor
|
| 156 |
+
sin_features = sin_features / self.scale_factor
|
| 157 |
+
|
| 158 |
+
# Compute phase angles
|
| 159 |
+
phases = torch.atan2(sin_features, cos_features)
|
| 160 |
+
|
| 161 |
+
# Use pseudo-inverse for approximate inversion
|
| 162 |
+
projection_pinv = torch.pinverse(self.projection)
|
| 163 |
+
reconstructed = phases @ projection_pinv
|
| 164 |
+
|
| 165 |
+
return reconstructed
|
| 166 |
+
|
utils/feature_extractors/__init__.py
ADDED
|
File without changes
|
utils/feature_extractors/audio_features.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import librosa
|
| 3 |
+
|
| 4 |
+
def compute_spectrogram(file_path, set_limit=False, limit_seconds=10):
|
| 5 |
+
"""Compute the spectrogram of an audio file."""
|
| 6 |
+
y, sr = librosa.load(file_path, sr=None)
|
| 7 |
+
if set_limit:
|
| 8 |
+
y = y[:int(sr*limit_seconds)]
|
| 9 |
+
S = librosa.stft(y)
|
| 10 |
+
S_db = librosa.amplitude_to_db(np.abs(S), ref=np.max)
|
| 11 |
+
return S_db, sr, y
|
| 12 |
+
|
| 13 |
+
def smooth_curve(data, window_size=30):
|
| 14 |
+
"""Smooth a curve using a moving average."""
|
| 15 |
+
return np.convolve(data, np.ones(window_size)/window_size, mode='same')
|
| 16 |
+
|
| 17 |
+
def compute_crest_factor(y, sr, frame_length=2048, hop_length=512):
|
| 18 |
+
# compute crest factor by windowing
|
| 19 |
+
crest_factor_total = []
|
| 20 |
+
for i in range(0, len(y), hop_length):
|
| 21 |
+
frame = y[i:i+frame_length]
|
| 22 |
+
peak = np.max(np.abs(frame))
|
| 23 |
+
rms = np.sqrt(np.mean(frame**2))
|
| 24 |
+
cur_crest_factor = peak / (rms + 1e-6)
|
| 25 |
+
crest_factor_total.append(cur_crest_factor)
|
| 26 |
+
crest_factor = np.asarray(crest_factor_total)
|
| 27 |
+
return crest_factor
|
| 28 |
+
|
| 29 |
+
def compute_audio_features(y, sr, smooth_window_size=1, average_time=True):
|
| 30 |
+
# features to compute: RMS energy, crest factor, dynamic spread, spectral centroid, spectral contrast, spectral flatness, spectral balance, and spectral bandwidth
|
| 31 |
+
y_mono = y.mean(axis=0)
|
| 32 |
+
spectral_centroid = smooth_curve(librosa.feature.spectral_centroid(y=y_mono, sr=sr)[0], window_size=smooth_window_size)
|
| 33 |
+
spectral_contrast = smooth_curve(np.mean(librosa.feature.spectral_contrast(y=y_mono, sr=sr), axis=0), window_size=smooth_window_size)
|
| 34 |
+
spectral_flatness = smooth_curve(librosa.feature.spectral_flatness(y=y_mono)[0], window_size=smooth_window_size)
|
| 35 |
+
rms_energy = smooth_curve(librosa.feature.rms(y=y_mono)[0], window_size=smooth_window_size)
|
| 36 |
+
# Crest Factor: Peak-to-RMS ratio
|
| 37 |
+
crest_factor = smooth_curve(compute_crest_factor(y_mono, sr, frame_length=2048, hop_length=512), window_size=smooth_window_size)
|
| 38 |
+
# Dynamic Spread
|
| 39 |
+
dynamic_spread = np.std(rms_energy)
|
| 40 |
+
# Spectral Balance (Low, Mid, High Frequency Energy Ratios)
|
| 41 |
+
spec = np.abs(librosa.stft(y_mono))**2
|
| 42 |
+
freqs = librosa.fft_frequencies(sr=sr)
|
| 43 |
+
low_energy = np.sum(spec[freqs < 200])
|
| 44 |
+
mid_energy = np.sum(spec[(freqs >= 200) & (freqs < 2000)])
|
| 45 |
+
high_energy = np.sum(spec[freqs >= 2000])
|
| 46 |
+
total_energy = low_energy + mid_energy + high_energy
|
| 47 |
+
spectral_balance = np.array([low_energy / total_energy, mid_energy / total_energy, high_energy / total_energy])
|
| 48 |
+
spectral_bandwidth = smooth_curve(librosa.feature.spectral_bandwidth(y=y_mono, sr=sr)[0], window_size=smooth_window_size)
|
| 49 |
+
# Stereo Width + Mid-Side Ratio
|
| 50 |
+
y_mid = (y[:len(y)//2] + y[len(y)//2:]) / 2
|
| 51 |
+
y_side = (y[:len(y)//2] - y[len(y)//2:]) / 2
|
| 52 |
+
stereo_width = np.mean(np.abs(y_side)) / (np.mean(np.abs(y_mid)) + 1e-6)
|
| 53 |
+
mid_side_ratio = np.mean(np.abs(y_mid)) / (np.mean(np.abs(y_side)) + 1e-6)
|
| 54 |
+
# average across time
|
| 55 |
+
if average_time:
|
| 56 |
+
spectral_centroid = np.mean(spectral_centroid)
|
| 57 |
+
spectral_flatness = np.mean(spectral_flatness)
|
| 58 |
+
spectral_contrast = np.mean(spectral_contrast)
|
| 59 |
+
spectral_flatness = np.mean(spectral_flatness)
|
| 60 |
+
rms_energy = np.mean(rms_energy)
|
| 61 |
+
crest_factor = np.mean(crest_factor)
|
| 62 |
+
spectral_bandwidth = np.mean(spectral_bandwidth)
|
| 63 |
+
|
| 64 |
+
# return as a dictionary
|
| 65 |
+
return {'spectral_centroid': spectral_centroid,
|
| 66 |
+
'spectral_contrast': spectral_contrast,
|
| 67 |
+
'spectral_flatness': spectral_flatness,
|
| 68 |
+
'rms_energy': rms_energy,
|
| 69 |
+
'crest_factor': crest_factor,
|
| 70 |
+
'dynamic_spread': dynamic_spread,
|
| 71 |
+
'spectral_balance': spectral_balance,
|
| 72 |
+
'spectral_bandwidth': spectral_bandwidth,
|
| 73 |
+
'stereo_width': stereo_width,
|
| 74 |
+
'mid_side_ratio': mid_side_ratio}
|
| 75 |
+
|
| 76 |
+
|
utils/feature_extractors/dsp_features.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
def compute_rms(x: torch.Tensor, **kwargs):
|
| 4 |
+
"""Compute root mean square energy.
|
| 5 |
+
|
| 6 |
+
Args:
|
| 7 |
+
x: (bs, 1, seq_len)
|
| 8 |
+
|
| 9 |
+
Returns:
|
| 10 |
+
rms: (bs, )
|
| 11 |
+
"""
|
| 12 |
+
rms = torch.sqrt(torch.mean(x**2, dim=-1).clamp(min=1e-8))
|
| 13 |
+
return rms
|
| 14 |
+
|
| 15 |
+
def compute_log_rms_gated_max(x: torch.Tensor, sample_rate=44100, **kwargs):
|
| 16 |
+
"""Compute gated log RMS energy.
|
| 17 |
+
|
| 18 |
+
Frames the signal in 400 ms windows with 75% overlap, computes RMS,
|
| 19 |
+
discards frames with RMS < -60 dBFS, and averages the log-RMS.
|
| 20 |
+
|
| 21 |
+
If all frames in a given (batch, channel) are below -60 dBFS,
|
| 22 |
+
returns -60 for that entry.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
x: Tensor of shape (bs, c, seq_len)
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
log_rms: Tensor of shape (bs, c)
|
| 29 |
+
"""
|
| 30 |
+
seg_size = int(sample_rate * 0.4)
|
| 31 |
+
hop_size = int(sample_rate * 0.1)
|
| 32 |
+
|
| 33 |
+
# (bs, c, num_frames, seg_size)
|
| 34 |
+
B, C, L = x.size()
|
| 35 |
+
|
| 36 |
+
assert C==1 or C==2
|
| 37 |
+
|
| 38 |
+
x_frames = x.unfold(2, seg_size, hop_size) # (bs, c, num_frames, seg_size)
|
| 39 |
+
|
| 40 |
+
# RMS over last dimension (seg_size)
|
| 41 |
+
rms = torch.sqrt((x_frames ** 2).mean(dim=-1)) # (bs, c, num_frames)
|
| 42 |
+
|
| 43 |
+
# dB conversion
|
| 44 |
+
rms_db = 20 * torch.log10(rms.clamp(min=1e-8)) # (bs, c, num_frames)
|
| 45 |
+
#print("rms db shape", rms_db.shape)
|
| 46 |
+
|
| 47 |
+
#take the maximum RMS across all frames
|
| 48 |
+
rms_max = rms_db.max(dim=2)[0] # (bs, c)
|
| 49 |
+
#print(f"RMS max shape: {rms_max.shape}")
|
| 50 |
+
|
| 51 |
+
return rms_max
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def compute_log_rms(x: torch.Tensor, **kwargs):
|
| 55 |
+
"""Compute root mean square energy.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
x: (bs, 1, seq_len)
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
rms: (bs, )
|
| 62 |
+
"""
|
| 63 |
+
rms=compute_rms(x)
|
| 64 |
+
return 20 * torch.log10(rms.clamp(min=1e-8))
|
| 65 |
+
|
| 66 |
+
def compute_crest_factor(x: torch.Tensor, **kwargs):
|
| 67 |
+
"""Compute crest factor as ratio of peak to rms energy in dB.
|
| 68 |
+
|
| 69 |
+
Args:
|
| 70 |
+
x: (bs, 2, seq_len)
|
| 71 |
+
|
| 72 |
+
"""
|
| 73 |
+
num = torch.max(torch.abs(x), dim=-1)[0]
|
| 74 |
+
den = compute_rms(x).clamp(min=1e-8)
|
| 75 |
+
cf = 20 * torch.log10((num / den).clamp(min=1e-8))
|
| 76 |
+
return cf
|
| 77 |
+
|
| 78 |
+
def compute_log_spread(x: torch.Tensor, **kwargs):
|
| 79 |
+
"""Compute log spread as the mean difference between log magnitude of samples and log RMS.
|
| 80 |
+
|
| 81 |
+
Args:
|
| 82 |
+
x: (bs, 1, seq_len)
|
| 83 |
+
|
| 84 |
+
Returns:
|
| 85 |
+
log_spread: (bs, )
|
| 86 |
+
"""
|
| 87 |
+
# Compute log RMS
|
| 88 |
+
log_rms = compute_log_rms(x).unsqueeze(-1) # (bs, 1, 1)
|
| 89 |
+
|
| 90 |
+
# Compute log magnitude of each sample
|
| 91 |
+
log_magnitude = 20 * torch.log10(torch.abs(x).clamp(min=1e-8)) # (bs, 1, seq_len)
|
| 92 |
+
|
| 93 |
+
# Compute the difference and take the mean
|
| 94 |
+
log_spread = torch.mean(log_magnitude - log_rms, dim=-1).squeeze(1) # (bs, )
|
| 95 |
+
|
| 96 |
+
return log_spread
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def compute_stereo_width(x: torch.Tensor, **kwargs):
|
| 100 |
+
"""Compute stereo width as ratio of energy in sum and difference signals.
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
x: (bs, 2, seq_len)
|
| 104 |
+
|
| 105 |
+
"""
|
| 106 |
+
bs, chs, seq_len = x.size()
|
| 107 |
+
|
| 108 |
+
assert chs == 2, "Input must be stereo"
|
| 109 |
+
|
| 110 |
+
# compute sum and diff of stereo channels
|
| 111 |
+
x_sum = x[:, 0, :] + x[:, 1, :]
|
| 112 |
+
x_diff = x[:, 0, :] - x[:, 1, :]
|
| 113 |
+
|
| 114 |
+
# compute power of sum and diff
|
| 115 |
+
sum_energy = torch.mean(x_sum**2, dim=-1)
|
| 116 |
+
diff_energy = torch.mean(x_diff**2, dim=-1)
|
| 117 |
+
|
| 118 |
+
# compute stereo width as ratio
|
| 119 |
+
stereo_width = diff_energy / sum_energy.clamp(min=1e-8)
|
| 120 |
+
|
| 121 |
+
return stereo_width
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def compute_stereo_imbalance(x: torch.Tensor, **kwargs):
|
| 125 |
+
"""Compute stereo imbalance as ratio of energy in left and right channels.
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
x: (bs, 2, seq_len)
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
stereo_imbalance: (bs, )
|
| 132 |
+
|
| 133 |
+
"""
|
| 134 |
+
left_energy = torch.mean(x[:, 0, :] ** 2, dim=-1)
|
| 135 |
+
right_energy = torch.mean(x[:, 1, :] ** 2, dim=-1)
|
| 136 |
+
|
| 137 |
+
stereo_imbalance = (right_energy - left_energy) / (
|
| 138 |
+
right_energy + left_energy
|
| 139 |
+
).clamp(min=1e-8)
|
| 140 |
+
|
| 141 |
+
return stereo_imbalance
|
utils/feature_extractors/fx_encoder_plus_plus.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from utils.fxencoder_plusplus import FxEncoderPlusPlus
|
| 3 |
+
from utils.fxencoder_plusplus.model import get_model_path
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def load_model(model_name="default", model_path=None, device="cuda", auto_download=True, cache_dir=None):
|
| 7 |
+
"""
|
| 8 |
+
Load FxEncoderPlusPlus model.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
model_name: Name of pretrained model ('default', 'musdb', 'medleydb')
|
| 12 |
+
model_path: Custom checkpoint path. If provided, ignores model_name
|
| 13 |
+
device: Device to load model on ('cuda' or 'cpu')
|
| 14 |
+
auto_download: Automatically download if model not found
|
| 15 |
+
cache_dir: Custom cache directory for downloaded models
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
Loaded FxEncoderPlusPlus model
|
| 19 |
+
|
| 20 |
+
Examples:
|
| 21 |
+
# Load default base model
|
| 22 |
+
model = load_model()
|
| 23 |
+
|
| 24 |
+
# Load musdb model
|
| 25 |
+
model = load_model(model_name="musdb")
|
| 26 |
+
|
| 27 |
+
# Load medleydb model
|
| 28 |
+
model = load_model(model_name="medleydb")
|
| 29 |
+
|
| 30 |
+
# Load custom checkpoint
|
| 31 |
+
model = load_model(model_path="/path/to/custom.pt")
|
| 32 |
+
|
| 33 |
+
# List available models
|
| 34 |
+
list_available_models()
|
| 35 |
+
"""
|
| 36 |
+
# Handle device
|
| 37 |
+
if device == "cuda" and not torch.cuda.is_available():
|
| 38 |
+
print("CUDA not available, using CPU")
|
| 39 |
+
device = "cpu"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# Determine model path
|
| 43 |
+
if model_path is None:
|
| 44 |
+
if auto_download:
|
| 45 |
+
model_path = get_model_path(model_name, cache_dir=cache_dir)
|
| 46 |
+
else:
|
| 47 |
+
raise ValueError("model_path is None and auto_download is False")
|
| 48 |
+
|
| 49 |
+
# Create model instance with specified device
|
| 50 |
+
model = FxEncoderPlusPlus(
|
| 51 |
+
embed_dim=2048,
|
| 52 |
+
audio_clap_module=False,
|
| 53 |
+
text_clap_module=False,
|
| 54 |
+
extractor_module=False,
|
| 55 |
+
device=device
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# Load checkpoint
|
| 59 |
+
checkpoint = torch.load(model_path, map_location=device, weights_only=False)
|
| 60 |
+
|
| 61 |
+
if "epoch" in checkpoint:
|
| 62 |
+
# resuming a train checkpoint w/ epoch and optimizer state
|
| 63 |
+
start_epoch = checkpoint["epoch"]
|
| 64 |
+
sd = checkpoint["state_dict"]
|
| 65 |
+
if next(iter(sd.items()))[0].startswith("module"):
|
| 66 |
+
sd = {k[len("module."):]: v for k, v in sd.items()}
|
| 67 |
+
model.load_state_dict(sd, strict=False )
|
| 68 |
+
print(f"Loaded checkpoint from epoch {start_epoch}")
|
| 69 |
+
else:
|
| 70 |
+
# loading a bare (model only) checkpoint for fine-tune or evaluation
|
| 71 |
+
model.load_state_dict(checkpoint)
|
| 72 |
+
print("Loaded model checkpoint")
|
| 73 |
+
|
| 74 |
+
model.to(device)
|
| 75 |
+
model.eval()
|
| 76 |
+
|
| 77 |
+
# Freeze parameters for inference
|
| 78 |
+
for param in model.parameters():
|
| 79 |
+
param.requires_grad = False
|
| 80 |
+
|
| 81 |
+
print(f"Model loaded successfully on {device}")
|
| 82 |
+
return model
|
utils/feature_extractors/load_features.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import math
|
| 3 |
+
import sys
|
| 4 |
+
import torchaudio
|
| 5 |
+
from importlib import import_module
|
| 6 |
+
import torch
|
| 7 |
+
import yaml
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
|
| 10 |
+
def load_fx_encoder_plusplus(model_args, device, *args, **kwargs):
|
| 11 |
+
from utils.feature_extractors.fx_encoder_plus_plus import load_model
|
| 12 |
+
|
| 13 |
+
assert model_args is not None, "model_args must be provided for fx_encoder type"
|
| 14 |
+
|
| 15 |
+
ckpt_path=model_args.ckpt_path
|
| 16 |
+
|
| 17 |
+
model=load_model(
|
| 18 |
+
model_path=ckpt_path,
|
| 19 |
+
device=device,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
def effects_encoder_fn(x):
|
| 23 |
+
assert x.ndim == 3, f"Input tensor x must be 2D, got {x.ndim}D"
|
| 24 |
+
assert x.shape[1] == 2, f"Input tensor x must have 2 channels, got {x.shape[1]} channels"
|
| 25 |
+
|
| 26 |
+
emb=model.get_fx_embedding(x)
|
| 27 |
+
#l2 normalize the embeddings
|
| 28 |
+
emb = torch.nn.functional.normalize(emb, p=2, dim=-1)
|
| 29 |
+
|
| 30 |
+
return emb
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
return lambda x: effects_encoder_fn(x)
|
| 34 |
+
|
| 35 |
+
def load_fx_encoder_plusplus_2048(model_args, device, *args, **kwargs):
|
| 36 |
+
from utils.feature_extractors.fx_encoder_plus_plus import load_model
|
| 37 |
+
|
| 38 |
+
assert model_args is not None, "model_args must be provided for fx_encoder type"
|
| 39 |
+
|
| 40 |
+
ckpt_path=model_args.ckpt_path
|
| 41 |
+
|
| 42 |
+
model=load_model(
|
| 43 |
+
model_path=ckpt_path,
|
| 44 |
+
device=device,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
def effects_encoder_fn(x):
|
| 48 |
+
assert x.ndim == 3, f"Input tensor x must be 2D, got {x.ndim}D"
|
| 49 |
+
assert x.shape[1] == 2, f"Input tensor x must have 2 channels, got {x.shape[1]} channels"
|
| 50 |
+
|
| 51 |
+
emb=model.fx_encoder(x)
|
| 52 |
+
emb=emb["embedding"] # Extract the embedding from the dictionary
|
| 53 |
+
|
| 54 |
+
return emb
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
return lambda x: effects_encoder_fn(x)
|
| 58 |
+
|
| 59 |
+
def add_isotropic_noise(z, sigma=0.1):
|
| 60 |
+
"""
|
| 61 |
+
z: [..., D] normalized embeddings (e.g., from CLAP or a regressor)
|
| 62 |
+
sigma: scale of noise to inject
|
| 63 |
+
Returns: z with orthogonal Gaussian noise added
|
| 64 |
+
"""
|
| 65 |
+
n=torch.randn_like(z) # isotropic noise
|
| 66 |
+
z_noisy = F.normalize(z + sigma * n, dim=-1)
|
| 67 |
+
return z_noisy
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def load_CLAP(model_args, device, *args, **kwargs):
|
| 73 |
+
|
| 74 |
+
#original_path = sys.path.copy()
|
| 75 |
+
from utils.laion_clap.hook import CLAP_Module
|
| 76 |
+
model= CLAP_Module(enable_fusion=False, amodel= 'HTSAT-base')
|
| 77 |
+
#sys.path = original_path
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
print("checkpoint",model_args.ckpt_path)
|
| 81 |
+
#print current sys.path
|
| 82 |
+
print("sys.path", sys.path)
|
| 83 |
+
model.load_ckpt(model_args.ckpt_path)
|
| 84 |
+
model.to(device)
|
| 85 |
+
|
| 86 |
+
normalize = model_args.normalize
|
| 87 |
+
|
| 88 |
+
if model_args.use_adaptor:
|
| 89 |
+
if model_args.adaptor_type == "MLP_CLAP_regressor":
|
| 90 |
+
from networks.MLP_CLAP_regressor import MLP_CLAP_regressor
|
| 91 |
+
adaptor=MLP_CLAP_regressor()
|
| 92 |
+
ckpt=torch.load(model_args.adaptor_checkpoint, map_location=device, weights_only=False)
|
| 93 |
+
adaptor.load_state_dict(ckpt["network"], strict=True)
|
| 94 |
+
adaptor.to(device)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def clap_fn(x, type=None):
|
| 100 |
+
B, C, T = x.shape
|
| 101 |
+
if C > 1:
|
| 102 |
+
x= x.mean(dim=1, keepdim=True) # Convert to mono if stereo
|
| 103 |
+
|
| 104 |
+
with torch.no_grad():
|
| 105 |
+
x=torchaudio.functional.resample(x, orig_freq=44100, new_freq=48000)
|
| 106 |
+
x= x.squeeze(1) # Remove channel dimension for CLAP
|
| 107 |
+
emb=model.get_audio_embedding_from_data(x,use_tensor=True)
|
| 108 |
+
|
| 109 |
+
if type is not None:
|
| 110 |
+
if type == "wet":
|
| 111 |
+
#print("wet mode")
|
| 112 |
+
if model_args.use_adaptor:
|
| 113 |
+
emb= adaptor(emb) # Apply the adaptor if specified
|
| 114 |
+
|
| 115 |
+
if model_args.add_noise:
|
| 116 |
+
emb= torch.nn.functional.normalize(emb, p=2, dim=-1) # Normalize before adding noise
|
| 117 |
+
emb = add_isotropic_noise(emb, sigma=model_args.noise_sigma)
|
| 118 |
+
|
| 119 |
+
# Normalize the embeddings
|
| 120 |
+
if normalize:
|
| 121 |
+
emb = torch.nn.functional.normalize(emb, p=2, dim=-1)
|
| 122 |
+
|
| 123 |
+
return emb
|
| 124 |
+
|
| 125 |
+
return lambda x, type: clap_fn(x, type=type)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def load_fx_encoder(model_args, device, *args, **kwargs):
|
| 130 |
+
"""
|
| 131 |
+
Load the FX Encoder model.
|
| 132 |
+
|
| 133 |
+
Args:
|
| 134 |
+
model_args: Arguments for the FX Encoder model.
|
| 135 |
+
device: Device to load the model on (CPU or GPU).
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
a function that extracts features from audio.
|
| 139 |
+
"""
|
| 140 |
+
assert model_args is not None, "model_args must be provided for fx_encoder type"
|
| 141 |
+
|
| 142 |
+
ckpt_path=model_args.ckpt_path
|
| 143 |
+
|
| 144 |
+
#from utils.feature_extractors.fx_encoder import load_effects_encoder
|
| 145 |
+
from utils.feature_extractors.networks import Effects_Encoder
|
| 146 |
+
|
| 147 |
+
def reload_weights(model, ckpt_path, device):
|
| 148 |
+
checkpoint = torch.load(ckpt_path, map_location=device)
|
| 149 |
+
|
| 150 |
+
from collections import OrderedDict
|
| 151 |
+
new_state_dict = OrderedDict()
|
| 152 |
+
for k, v in checkpoint["model"].items():
|
| 153 |
+
name = k[7:] # remove `module.`
|
| 154 |
+
new_state_dict[name] = v
|
| 155 |
+
model.load_state_dict(new_state_dict, strict=False)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
try:
|
| 159 |
+
with open(os.path.join('.','utils','feature_extractors', 'networks', 'configs.yaml'), 'r') as f:
|
| 160 |
+
configs = yaml.full_load(f)
|
| 161 |
+
except:
|
| 162 |
+
with open(model_args.config_file, 'r') as f:
|
| 163 |
+
configs = yaml.full_load(f)
|
| 164 |
+
|
| 165 |
+
cfg_enc = configs['Effects_Encoder']['default']
|
| 166 |
+
|
| 167 |
+
effects_encoder = Effects_Encoder(cfg_enc)
|
| 168 |
+
reload_weights(effects_encoder, ckpt_path, device)
|
| 169 |
+
effects_encoder.to(device)
|
| 170 |
+
effects_encoder.eval()
|
| 171 |
+
|
| 172 |
+
def effects_encoder_fn(x):
|
| 173 |
+
emb=effects_encoder(x)
|
| 174 |
+
#l2 normalize the embeddings
|
| 175 |
+
emb = torch.nn.functional.normalize(emb, p=2, dim=-1)
|
| 176 |
+
return emb
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
return lambda x, *args: effects_encoder_fn(x)
|
| 180 |
+
|
| 181 |
+
def load_AFxRep(model_args, device, sample_rate=44100, peak_scaling=True, *args, **kwargs):
|
| 182 |
+
|
| 183 |
+
assert model_args is not None, "model_args must be provided for AFxRep type"
|
| 184 |
+
|
| 185 |
+
ckpt_path=model_args.ckpt_path
|
| 186 |
+
|
| 187 |
+
config_path = os.path.join(os.path.dirname(ckpt_path), "config.yaml")
|
| 188 |
+
|
| 189 |
+
with open(config_path) as f:
|
| 190 |
+
config = yaml.safe_load(f)
|
| 191 |
+
|
| 192 |
+
encoder_configs = config["model"]["init_args"]["encoder"]
|
| 193 |
+
|
| 194 |
+
module_path, class_name = encoder_configs["class_path"].rsplit(".", 1)
|
| 195 |
+
module_path = module_path.replace("lcap", "utils.st_ito")
|
| 196 |
+
|
| 197 |
+
module = import_module(module_path)
|
| 198 |
+
|
| 199 |
+
model = getattr(module, class_name)(**encoder_configs["init_args"])
|
| 200 |
+
|
| 201 |
+
checkpoint = torch.load(ckpt_path, map_location="cpu")
|
| 202 |
+
|
| 203 |
+
# load state dicts
|
| 204 |
+
state_dict = {}
|
| 205 |
+
for k, v in checkpoint["state_dict"].items():
|
| 206 |
+
if k.startswith("encoder"):
|
| 207 |
+
state_dict[k.replace("encoder.", "", 1)] = v
|
| 208 |
+
|
| 209 |
+
model.load_state_dict(state_dict)
|
| 210 |
+
|
| 211 |
+
model.eval()
|
| 212 |
+
|
| 213 |
+
model.to(device)
|
| 214 |
+
|
| 215 |
+
def wrapper_fn(x, sample_rate):
|
| 216 |
+
|
| 217 |
+
x=x.to(device)
|
| 218 |
+
|
| 219 |
+
#x=torch.transpose(x,-1,-2)
|
| 220 |
+
|
| 221 |
+
if sample_rate != 48000:
|
| 222 |
+
x=torchaudio.functional.resample(x, sample_rate, 48000)
|
| 223 |
+
|
| 224 |
+
bs= x.shape[0]
|
| 225 |
+
#peak normalization. I do it because this is what ST-ITO get_param_embeds does. Not sure if it is good that this representation is invariant to gain
|
| 226 |
+
if peak_scaling:
|
| 227 |
+
x_max=[]
|
| 228 |
+
for batch_idx in range(bs):
|
| 229 |
+
#x[batch_idx, ...] /= x[batch_idx, ...].abs().max().clamp(1e-8)
|
| 230 |
+
x_max.append( x[batch_idx, ...].abs().max().clamp(1e-8) )
|
| 231 |
+
|
| 232 |
+
if x.ndim == 3:
|
| 233 |
+
x_max=torch.stack(x_max, dim=0).view(bs, 1, 1)
|
| 234 |
+
elif x.ndim == 2:
|
| 235 |
+
x_max=torch.stack(x_max, dim=0).view(bs, 1)
|
| 236 |
+
|
| 237 |
+
x=x/ x_max
|
| 238 |
+
|
| 239 |
+
mid_embeddings, side_embeddings = model(x)
|
| 240 |
+
|
| 241 |
+
# check for nan
|
| 242 |
+
if torch.isnan(mid_embeddings).any():
|
| 243 |
+
print("Warning: NaNs found in mid_embeddings")
|
| 244 |
+
mid_embeddings = torch.nan_to_num(mid_embeddings)
|
| 245 |
+
elif torch.isnan(side_embeddings).any():
|
| 246 |
+
print("Warning: NaNs found in side_embeddings")
|
| 247 |
+
side_embeddings = torch.nan_to_num(side_embeddings)
|
| 248 |
+
|
| 249 |
+
mid_embeddings = torch.nn.functional.normalize(mid_embeddings, p=2, dim=-1)
|
| 250 |
+
side_embeddings = torch.nn.functional.normalize(side_embeddings, p=2, dim=-1)
|
| 251 |
+
|
| 252 |
+
embeddings_all= torch.cat([mid_embeddings, side_embeddings], dim=-1)
|
| 253 |
+
|
| 254 |
+
return embeddings_all
|
| 255 |
+
|
| 256 |
+
feat_extractor = lambda x, *args: wrapper_fn(x, sample_rate=sample_rate)
|
| 257 |
+
|
| 258 |
+
return feat_extractor
|
| 259 |
+
|
| 260 |
+
|
utils/feature_extractors/networks/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .architectures import *
|
| 2 |
+
from .network_utils import *
|
utils/feature_extractors/networks/architectures.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Implementation of neural networks used in the task 'Music Mastering Style Transfer'
|
| 3 |
+
- 'Effects Encoder'
|
| 4 |
+
- 'Mastering Style Transfer'
|
| 5 |
+
- 'Differentiable Mastering Style Transfer'
|
| 6 |
+
"""
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
import torch.nn.init as init
|
| 11 |
+
#import dasp_pytorch
|
| 12 |
+
|
| 13 |
+
import os
|
| 14 |
+
import sys
|
| 15 |
+
import time
|
| 16 |
+
|
| 17 |
+
# compute receptive field
|
| 18 |
+
def compute_receptive_field(kernels, strides, dilations):
|
| 19 |
+
rf = 0
|
| 20 |
+
for i in range(len(kernels)):
|
| 21 |
+
rf += rf * strides[i] + (kernels[i]-strides[i]) * dilations[i]
|
| 22 |
+
return rf
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Encoder of music effects for contrastive learning of music effects
|
| 26 |
+
class Effects_Encoder(nn.Module):
|
| 27 |
+
def __init__(self, config):
|
| 28 |
+
super(Effects_Encoder, self).__init__()
|
| 29 |
+
# input is stereo channeled audio
|
| 30 |
+
config["channels"].insert(0, 2)
|
| 31 |
+
|
| 32 |
+
# encoder layers
|
| 33 |
+
encoder = []
|
| 34 |
+
for i in range(len(config["kernels"])):
|
| 35 |
+
if config["conv_block"]=='res':
|
| 36 |
+
encoder.append(Res_ConvBlock(dimension=1, \
|
| 37 |
+
in_channels=config["channels"][i], \
|
| 38 |
+
out_channels=config["channels"][i+1], \
|
| 39 |
+
kernel_size=config["kernels"][i], \
|
| 40 |
+
stride=config["strides"][i], \
|
| 41 |
+
padding="SAME", \
|
| 42 |
+
dilation=config["dilation"][i], \
|
| 43 |
+
norm=config["norm"], \
|
| 44 |
+
activation=config["activation"], \
|
| 45 |
+
last_activation=config["activation"]))
|
| 46 |
+
self.encoder = nn.Sequential(*encoder)
|
| 47 |
+
|
| 48 |
+
# pooling method
|
| 49 |
+
self.glob_pool = nn.AdaptiveAvgPool1d(1)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# network forward operation
|
| 53 |
+
def forward(self, input):
|
| 54 |
+
enc_output = self.encoder(input)
|
| 55 |
+
glob_pooled = self.glob_pool(enc_output).squeeze(-1)
|
| 56 |
+
|
| 57 |
+
# outputs c feature
|
| 58 |
+
return glob_pooled
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# Residual Block
|
| 62 |
+
# the input is added after the first convolutional layer, retaining its original channel size
|
| 63 |
+
# therefore, the second convolutional layer's output channel may differ
|
| 64 |
+
class Res_ConvBlock(nn.Module):
|
| 65 |
+
def __init__(self, dimension, \
|
| 66 |
+
in_channels, out_channels, \
|
| 67 |
+
kernel_size, \
|
| 68 |
+
stride=1, padding="SAME", \
|
| 69 |
+
dilation=1, \
|
| 70 |
+
bias=True, \
|
| 71 |
+
norm="batch", \
|
| 72 |
+
activation="relu", last_activation="relu", \
|
| 73 |
+
mode="conv"):
|
| 74 |
+
super(Res_ConvBlock, self).__init__()
|
| 75 |
+
|
| 76 |
+
if dimension==1:
|
| 77 |
+
self.conv1 = Conv1d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation)
|
| 78 |
+
self.conv2 = Conv1d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode)
|
| 79 |
+
|
| 80 |
+
def forward(self, input):
|
| 81 |
+
c1_out = self.conv1(input) + input
|
| 82 |
+
c2_out = self.conv2(c1_out)
|
| 83 |
+
return c2_out
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# 1-dimensional convolutional layer
|
| 87 |
+
# in the order of conv -> norm -> activation
|
| 88 |
+
class Conv1d_layer(nn.Module):
|
| 89 |
+
def __init__(self, in_channels, out_channels, kernel_size, \
|
| 90 |
+
stride=1, \
|
| 91 |
+
padding="SAME", dilation=1, bias=True, \
|
| 92 |
+
norm="batch", activation="relu", \
|
| 93 |
+
mode="conv"):
|
| 94 |
+
super(Conv1d_layer, self).__init__()
|
| 95 |
+
|
| 96 |
+
self.conv1d = nn.Sequential()
|
| 97 |
+
|
| 98 |
+
''' padding '''
|
| 99 |
+
if mode=="deconv":
|
| 100 |
+
padding = int(dilation * (kernel_size-1) / 2)
|
| 101 |
+
out_padding = 0 if stride==1 else 1
|
| 102 |
+
elif mode=="conv" or "alias_free" in mode:
|
| 103 |
+
if padding == "SAME":
|
| 104 |
+
pad = int((kernel_size-1) * dilation)
|
| 105 |
+
l_pad = int(pad//2)
|
| 106 |
+
r_pad = pad - l_pad
|
| 107 |
+
padding_area = (l_pad, r_pad)
|
| 108 |
+
elif padding == "VALID":
|
| 109 |
+
padding_area = (0, 0)
|
| 110 |
+
else:
|
| 111 |
+
pass
|
| 112 |
+
|
| 113 |
+
''' convolutional layer '''
|
| 114 |
+
if mode=="deconv":
|
| 115 |
+
self.conv1d.add_module("deconv1d", nn.ConvTranspose1d(in_channels, out_channels, kernel_size, \
|
| 116 |
+
stride=stride, padding=padding, output_padding=out_padding, \
|
| 117 |
+
dilation=dilation, \
|
| 118 |
+
bias=bias))
|
| 119 |
+
elif mode=="conv":
|
| 120 |
+
self.conv1d.add_module(f"{mode}1d_pad", nn.ReflectionPad1d(padding_area))
|
| 121 |
+
self.conv1d.add_module(f"{mode}1d", nn.Conv1d(in_channels, out_channels, kernel_size, \
|
| 122 |
+
stride=stride, padding=0, \
|
| 123 |
+
dilation=dilation, \
|
| 124 |
+
bias=bias))
|
| 125 |
+
elif "alias_free" in mode:
|
| 126 |
+
if "up" in mode:
|
| 127 |
+
up_factor = stride * 2
|
| 128 |
+
down_factor = 2
|
| 129 |
+
elif "down" in mode:
|
| 130 |
+
up_factor = 2
|
| 131 |
+
down_factor = stride * 2
|
| 132 |
+
else:
|
| 133 |
+
raise ValueError("choose alias-free method : 'up' or 'down'")
|
| 134 |
+
# procedure : conv -> upsample -> lrelu -> low-pass filter -> downsample
|
| 135 |
+
# the torchaudio.transforms.Resample's default resampling_method is 'sinc_interpolation' which performs low-pass filter during the process
|
| 136 |
+
# details at https://pytorch.org/audio/stable/transforms.html
|
| 137 |
+
self.conv1d.add_module(f"{mode}1d_pad", nn.ReflectionPad1d(padding_area))
|
| 138 |
+
self.conv1d.add_module(f"{mode}1d", nn.Conv1d(in_channels, out_channels, kernel_size, \
|
| 139 |
+
stride=1, padding=0, \
|
| 140 |
+
dilation=dilation, \
|
| 141 |
+
bias=bias))
|
| 142 |
+
self.conv1d.add_module(f"{mode}upsample", torchaudio.transforms.Resample(orig_freq=1, new_freq=up_factor))
|
| 143 |
+
self.conv1d.add_module(f"{mode}lrelu", nn.LeakyReLU())
|
| 144 |
+
self.conv1d.add_module(f"{mode}downsample", torchaudio.transforms.Resample(orig_freq=down_factor, new_freq=1))
|
| 145 |
+
|
| 146 |
+
''' normalization '''
|
| 147 |
+
if norm=="batch":
|
| 148 |
+
self.conv1d.add_module("batch_norm", nn.BatchNorm1d(out_channels))
|
| 149 |
+
# self.conv1d.add_module("batch_norm", nn.SyncBatchNorm(out_channels))
|
| 150 |
+
|
| 151 |
+
''' activation '''
|
| 152 |
+
if 'alias_free' not in mode:
|
| 153 |
+
if activation=="relu":
|
| 154 |
+
self.conv1d.add_module("relu", nn.ReLU())
|
| 155 |
+
elif activation=="lrelu":
|
| 156 |
+
self.conv1d.add_module("lrelu", nn.LeakyReLU())
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def forward(self, input):
|
| 160 |
+
# input shape should be : batch x channel x height x width
|
| 161 |
+
output = self.conv1d(input)
|
| 162 |
+
return output
|
| 163 |
+
|
utils/feature_extractors/networks/configs.yaml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# model architecture configurations
|
| 2 |
+
|
| 3 |
+
# Music Effects Encoder
|
| 4 |
+
Effects_Encoder:
|
| 5 |
+
|
| 6 |
+
default:
|
| 7 |
+
channels: [16, 32, 64, 128, 256, 256, 512, 512, 1024, 1024, 2048, 2048]
|
| 8 |
+
kernels: [25, 25, 15, 15, 10, 10, 10, 10, 5, 5, 5, 5]
|
| 9 |
+
strides: [4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1]
|
| 10 |
+
dilation: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
| 11 |
+
bias: True
|
| 12 |
+
norm: 'batch'
|
| 13 |
+
conv_block: 'res'
|
| 14 |
+
activation: "relu"
|
utils/feature_extractors/networks/network_utils.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Utility File
|
| 3 |
+
containing functions for neural networks
|
| 4 |
+
"""
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
import torch.nn.init as init
|
| 8 |
+
import torch
|
| 9 |
+
import torchaudio
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# 2-dimensional convolutional layer
|
| 14 |
+
# in the order of conv -> norm -> activation
|
| 15 |
+
class Conv2d_layer(nn.Module):
|
| 16 |
+
def __init__(self, in_channels, out_channels, kernel_size, \
|
| 17 |
+
stride=1, \
|
| 18 |
+
padding="SAME", dilation=(1,1), bias=True, \
|
| 19 |
+
norm="batch", activation="relu", \
|
| 20 |
+
mode="conv"):
|
| 21 |
+
super(Conv2d_layer, self).__init__()
|
| 22 |
+
|
| 23 |
+
self.conv2d = nn.Sequential()
|
| 24 |
+
|
| 25 |
+
if isinstance(kernel_size, int):
|
| 26 |
+
kernel_size = [kernel_size, kernel_size]
|
| 27 |
+
if isinstance(stride, int):
|
| 28 |
+
stride = [stride, stride]
|
| 29 |
+
if isinstance(dilation, int):
|
| 30 |
+
dilation = [dilation, dilation]
|
| 31 |
+
|
| 32 |
+
''' padding '''
|
| 33 |
+
if mode=="deconv":
|
| 34 |
+
padding = tuple(int((current_kernel - 1)/2) for current_kernel in kernel_size)
|
| 35 |
+
out_padding = tuple(0 if current_stride == 1 else 1 for current_stride in stride)
|
| 36 |
+
elif mode=="conv":
|
| 37 |
+
if padding == "SAME":
|
| 38 |
+
f_pad = int((kernel_size[0]-1) * dilation[0])
|
| 39 |
+
t_pad = int((kernel_size[1]-1) * dilation[1])
|
| 40 |
+
t_l_pad = int(t_pad//2)
|
| 41 |
+
t_r_pad = t_pad - t_l_pad
|
| 42 |
+
f_l_pad = int(f_pad//2)
|
| 43 |
+
f_r_pad = f_pad - f_l_pad
|
| 44 |
+
padding_area = (t_l_pad, t_r_pad, f_l_pad, f_r_pad)
|
| 45 |
+
elif padding == "VALID":
|
| 46 |
+
padding = 0
|
| 47 |
+
else:
|
| 48 |
+
pass
|
| 49 |
+
|
| 50 |
+
''' convolutional layer '''
|
| 51 |
+
if mode=="deconv":
|
| 52 |
+
self.conv2d.add_module("deconv2d", nn.ConvTranspose2d(in_channels, out_channels, \
|
| 53 |
+
(kernel_size[0], kernel_size[1]), \
|
| 54 |
+
stride=stride, \
|
| 55 |
+
padding=padding, output_padding=out_padding, \
|
| 56 |
+
dilation=dilation, \
|
| 57 |
+
bias=bias))
|
| 58 |
+
elif mode=="conv":
|
| 59 |
+
self.conv2d.add_module(f"{mode}2d_pad", nn.ReflectionPad2d(padding_area))
|
| 60 |
+
self.conv2d.add_module(f"{mode}2d", nn.Conv2d(in_channels, out_channels, \
|
| 61 |
+
(kernel_size[0], kernel_size[1]), \
|
| 62 |
+
stride=stride, \
|
| 63 |
+
padding=0, \
|
| 64 |
+
dilation=dilation, \
|
| 65 |
+
bias=bias))
|
| 66 |
+
|
| 67 |
+
''' normalization '''
|
| 68 |
+
if norm=="batch":
|
| 69 |
+
self.conv2d.add_module("batch_norm", nn.BatchNorm2d(out_channels))
|
| 70 |
+
|
| 71 |
+
''' activation '''
|
| 72 |
+
if activation=="relu":
|
| 73 |
+
self.conv2d.add_module("relu", nn.ReLU())
|
| 74 |
+
elif activation=="lrelu":
|
| 75 |
+
self.conv2d.add_module("lrelu", nn.LeakyReLU())
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def forward(self, input):
|
| 79 |
+
# input shape should be : batch x channel x height x width
|
| 80 |
+
output = self.conv2d(input)
|
| 81 |
+
return output
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# 1-dimensional convolutional layer
|
| 86 |
+
# in the order of conv -> norm -> activation
|
| 87 |
+
class Conv1d_layer(nn.Module):
|
| 88 |
+
def __init__(self, in_channels, out_channels, kernel_size, \
|
| 89 |
+
stride=1, \
|
| 90 |
+
padding="SAME", dilation=1, bias=True, \
|
| 91 |
+
norm="batch", activation="relu", \
|
| 92 |
+
mode="conv"):
|
| 93 |
+
super(Conv1d_layer, self).__init__()
|
| 94 |
+
|
| 95 |
+
self.conv1d = nn.Sequential()
|
| 96 |
+
|
| 97 |
+
''' padding '''
|
| 98 |
+
if mode=="deconv":
|
| 99 |
+
padding = int(dilation * (kernel_size-1) / 2)
|
| 100 |
+
out_padding = 0 if stride==1 else 1
|
| 101 |
+
elif mode=="conv" or "alias_free" in mode:
|
| 102 |
+
if padding == "SAME":
|
| 103 |
+
pad = int((kernel_size-1) * dilation)
|
| 104 |
+
l_pad = int(pad//2)
|
| 105 |
+
r_pad = pad - l_pad
|
| 106 |
+
padding_area = (l_pad, r_pad)
|
| 107 |
+
elif padding == "VALID":
|
| 108 |
+
padding_area = (0, 0)
|
| 109 |
+
else:
|
| 110 |
+
pass
|
| 111 |
+
|
| 112 |
+
''' convolutional layer '''
|
| 113 |
+
if mode=="deconv":
|
| 114 |
+
self.conv1d.add_module("deconv1d", nn.ConvTranspose1d(in_channels, out_channels, kernel_size, \
|
| 115 |
+
stride=stride, padding=padding, output_padding=out_padding, \
|
| 116 |
+
dilation=dilation, \
|
| 117 |
+
bias=bias))
|
| 118 |
+
elif mode=="conv":
|
| 119 |
+
self.conv1d.add_module(f"{mode}1d_pad", nn.ReflectionPad1d(padding_area))
|
| 120 |
+
self.conv1d.add_module(f"{mode}1d", nn.Conv1d(in_channels, out_channels, kernel_size, \
|
| 121 |
+
stride=stride, padding=0, \
|
| 122 |
+
dilation=dilation, \
|
| 123 |
+
bias=bias))
|
| 124 |
+
elif "alias_free" in mode:
|
| 125 |
+
if "up" in mode:
|
| 126 |
+
up_factor = stride * 2
|
| 127 |
+
down_factor = 2
|
| 128 |
+
elif "down" in mode:
|
| 129 |
+
up_factor = 2
|
| 130 |
+
down_factor = stride * 2
|
| 131 |
+
else:
|
| 132 |
+
raise ValueError("choose alias-free method : 'up' or 'down'")
|
| 133 |
+
# procedure : conv -> upsample -> lrelu -> low-pass filter -> downsample
|
| 134 |
+
# the torchaudio.transforms.Resample's default resampling_method is 'sinc_interpolation' which performs low-pass filter during the process
|
| 135 |
+
# details at https://pytorch.org/audio/stable/transforms.html
|
| 136 |
+
self.conv1d.add_module(f"{mode}1d_pad", nn.ReflectionPad1d(padding_area))
|
| 137 |
+
self.conv1d.add_module(f"{mode}1d", nn.Conv1d(in_channels, out_channels, kernel_size, \
|
| 138 |
+
stride=1, padding=0, \
|
| 139 |
+
dilation=dilation, \
|
| 140 |
+
bias=bias))
|
| 141 |
+
self.conv1d.add_module(f"{mode}upsample", torchaudio.transforms.Resample(orig_freq=1, new_freq=up_factor))
|
| 142 |
+
self.conv1d.add_module(f"{mode}lrelu", nn.LeakyReLU())
|
| 143 |
+
self.conv1d.add_module(f"{mode}downsample", torchaudio.transforms.Resample(orig_freq=down_factor, new_freq=1))
|
| 144 |
+
|
| 145 |
+
''' normalization '''
|
| 146 |
+
if norm=="batch":
|
| 147 |
+
self.conv1d.add_module("batch_norm", nn.BatchNorm1d(out_channels))
|
| 148 |
+
# self.conv1d.add_module("batch_norm", nn.SyncBatchNorm(out_channels))
|
| 149 |
+
|
| 150 |
+
''' activation '''
|
| 151 |
+
if 'alias_free' not in mode:
|
| 152 |
+
if activation=="relu":
|
| 153 |
+
self.conv1d.add_module("relu", nn.ReLU())
|
| 154 |
+
elif activation=="lrelu":
|
| 155 |
+
self.conv1d.add_module("lrelu", nn.LeakyReLU())
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def forward(self, input):
|
| 159 |
+
# input shape should be : batch x channel x height x width
|
| 160 |
+
output = self.conv1d(input)
|
| 161 |
+
return output
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
# Residual Block
|
| 166 |
+
# the input is added after the first convolutional layer, retaining its original channel size
|
| 167 |
+
# therefore, the second convolutional layer's output channel may differ
|
| 168 |
+
class Res_ConvBlock(nn.Module):
|
| 169 |
+
def __init__(self, dimension, \
|
| 170 |
+
in_channels, out_channels, \
|
| 171 |
+
kernel_size, \
|
| 172 |
+
stride=1, padding="SAME", \
|
| 173 |
+
dilation=1, \
|
| 174 |
+
bias=True, \
|
| 175 |
+
norm="batch", \
|
| 176 |
+
activation="relu", last_activation="relu", \
|
| 177 |
+
mode="conv"):
|
| 178 |
+
super(Res_ConvBlock, self).__init__()
|
| 179 |
+
|
| 180 |
+
if dimension==1:
|
| 181 |
+
self.conv1 = Conv1d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation)
|
| 182 |
+
self.conv2 = Conv1d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode)
|
| 183 |
+
elif dimension==2:
|
| 184 |
+
self.conv1 = Conv2d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation)
|
| 185 |
+
self.conv2 = Conv2d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def forward(self, input):
|
| 189 |
+
c1_out = self.conv1(input) + input
|
| 190 |
+
c2_out = self.conv2(c1_out)
|
| 191 |
+
return c2_out
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# Convoluaionl Block
|
| 196 |
+
# consists of multiple (number of layer_num) convolutional layers
|
| 197 |
+
# only the final convoluational layer outputs the desired 'out_channels'
|
| 198 |
+
class ConvBlock(nn.Module):
|
| 199 |
+
def __init__(self, dimension, layer_num, \
|
| 200 |
+
in_channels, out_channels, \
|
| 201 |
+
kernel_size, \
|
| 202 |
+
stride=1, padding="SAME", \
|
| 203 |
+
dilation=1, \
|
| 204 |
+
bias=True, \
|
| 205 |
+
norm="batch", \
|
| 206 |
+
activation="relu", last_activation="relu", \
|
| 207 |
+
mode="conv"):
|
| 208 |
+
super(ConvBlock, self).__init__()
|
| 209 |
+
|
| 210 |
+
conv_block = []
|
| 211 |
+
if dimension==1:
|
| 212 |
+
for i in range(layer_num-1):
|
| 213 |
+
conv_block.append(Conv1d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation))
|
| 214 |
+
conv_block.append(Conv1d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode))
|
| 215 |
+
elif dimension==2:
|
| 216 |
+
for i in range(layer_num-1):
|
| 217 |
+
conv_block.append(Conv2d_layer(in_channels, in_channels, kernel_size, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=activation))
|
| 218 |
+
conv_block.append(Conv2d_layer(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=bias, norm=norm, activation=last_activation, mode=mode))
|
| 219 |
+
self.conv_block = nn.Sequential(*conv_block)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def forward(self, input):
|
| 223 |
+
return self.conv_block(input)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
# Feature-wise Linear Modulation
|
| 227 |
+
class FiLM(nn.Module):
|
| 228 |
+
def __init__(self, condition_len=2048, feature_len=1024):
|
| 229 |
+
super(FiLM, self).__init__()
|
| 230 |
+
self.film_fc = nn.Linear(condition_len, feature_len*2)
|
| 231 |
+
self.feat_len = feature_len
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def forward(self, feature, condition, sefa=None):
|
| 235 |
+
# SeFA
|
| 236 |
+
if sefa:
|
| 237 |
+
weight = self.film_fc.weight.T
|
| 238 |
+
weight = weight / torch.linalg.norm((weight+1e-07), dim=0, keepdims=True)
|
| 239 |
+
eigen_values, eigen_vectors = torch.eig(torch.matmul(weight, weight.T), eigenvectors=True)
|
| 240 |
+
|
| 241 |
+
####### custom parameters #######
|
| 242 |
+
chosen_eig_idx = sefa[0]
|
| 243 |
+
alpha = eigen_values[chosen_eig_idx][0] * sefa[1]
|
| 244 |
+
#################################
|
| 245 |
+
|
| 246 |
+
An = eigen_vectors[chosen_eig_idx].repeat(condition.shape[0], 1)
|
| 247 |
+
alpha_An = alpha * An
|
| 248 |
+
|
| 249 |
+
condition += alpha_An
|
| 250 |
+
|
| 251 |
+
film_factor = self.film_fc(condition).unsqueeze(-1)
|
| 252 |
+
r, b = torch.split(film_factor, self.feat_len, dim=1)
|
| 253 |
+
return r*feature + b
|
| 254 |
+
|
utils/feature_extractors/networks/pytorch_utils.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
From: https://github.com/qiuqiangkong/audioset_tagging_cnn/blob/master/pytorch/pytorch_utils.py
|
| 3 |
+
|
| 4 |
+
Copyright (c) 2018-2020 Qiuqiang Kong
|
| 5 |
+
"""
|
| 6 |
+
import numpy as np
|
| 7 |
+
import time
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def move_data_to_device(x, device):
|
| 13 |
+
if 'float' in str(x.dtype):
|
| 14 |
+
x = torch.Tensor(x)
|
| 15 |
+
elif 'int' in str(x.dtype):
|
| 16 |
+
x = torch.LongTensor(x)
|
| 17 |
+
else:
|
| 18 |
+
return x
|
| 19 |
+
|
| 20 |
+
return x.to(device)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def do_mixup(x, mixup_lambda):
|
| 24 |
+
"""Mixup x of even indexes (0, 2, 4, ...) with x of odd indexes
|
| 25 |
+
(1, 3, 5, ...).
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
x: (batch_size * 2, ...)
|
| 29 |
+
mixup_lambda: (batch_size * 2,)
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
out: (batch_size, ...)
|
| 33 |
+
"""
|
| 34 |
+
out = (x[0 :: 2].transpose(0, -1) * mixup_lambda[0 :: 2] + \
|
| 35 |
+
x[1 :: 2].transpose(0, -1) * mixup_lambda[1 :: 2]).transpose(0, -1)
|
| 36 |
+
return out
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def append_to_dict(dict, key, value):
|
| 40 |
+
if key in dict.keys():
|
| 41 |
+
dict[key].append(value)
|
| 42 |
+
else:
|
| 43 |
+
dict[key] = [value]
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def forward(model, generator, return_input=False,
|
| 47 |
+
return_target=False):
|
| 48 |
+
"""Forward data to a model.
|
| 49 |
+
|
| 50 |
+
Args:
|
| 51 |
+
model: object
|
| 52 |
+
generator: object
|
| 53 |
+
return_input: bool
|
| 54 |
+
return_target: bool
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
audio_name: (audios_num,)
|
| 58 |
+
clipwise_output: (audios_num, classes_num)
|
| 59 |
+
(ifexist) segmentwise_output: (audios_num, segments_num, classes_num)
|
| 60 |
+
(ifexist) framewise_output: (audios_num, frames_num, classes_num)
|
| 61 |
+
(optional) return_input: (audios_num, segment_samples)
|
| 62 |
+
(optional) return_target: (audios_num, classes_num)
|
| 63 |
+
"""
|
| 64 |
+
output_dict = {}
|
| 65 |
+
device = next(model.parameters()).device
|
| 66 |
+
time1 = time.time()
|
| 67 |
+
|
| 68 |
+
# Forward data to a model in mini-batches
|
| 69 |
+
for n, batch_data_dict in enumerate(generator):
|
| 70 |
+
print(n)
|
| 71 |
+
batch_waveform = move_data_to_device(batch_data_dict['waveform'], device)
|
| 72 |
+
|
| 73 |
+
with torch.no_grad():
|
| 74 |
+
model.eval()
|
| 75 |
+
batch_output = model(batch_waveform)
|
| 76 |
+
|
| 77 |
+
append_to_dict(output_dict, 'audio_name', batch_data_dict['audio_name'])
|
| 78 |
+
|
| 79 |
+
append_to_dict(output_dict, 'clipwise_output',
|
| 80 |
+
batch_output['clipwise_output'].data.cpu().numpy())
|
| 81 |
+
|
| 82 |
+
if 'segmentwise_output' in batch_output.keys():
|
| 83 |
+
append_to_dict(output_dict, 'segmentwise_output',
|
| 84 |
+
batch_output['segmentwise_output'].data.cpu().numpy())
|
| 85 |
+
|
| 86 |
+
if 'framewise_output' in batch_output.keys():
|
| 87 |
+
append_to_dict(output_dict, 'framewise_output',
|
| 88 |
+
batch_output['framewise_output'].data.cpu().numpy())
|
| 89 |
+
|
| 90 |
+
if return_input:
|
| 91 |
+
append_to_dict(output_dict, 'waveform', batch_data_dict['waveform'])
|
| 92 |
+
|
| 93 |
+
if return_target:
|
| 94 |
+
if 'target' in batch_data_dict.keys():
|
| 95 |
+
append_to_dict(output_dict, 'target', batch_data_dict['target'])
|
| 96 |
+
|
| 97 |
+
if n % 10 == 0:
|
| 98 |
+
print(' --- Inference time: {:.3f} s / 10 iterations ---'.format(
|
| 99 |
+
time.time() - time1))
|
| 100 |
+
time1 = time.time()
|
| 101 |
+
|
| 102 |
+
for key in output_dict.keys():
|
| 103 |
+
output_dict[key] = np.concatenate(output_dict[key], axis=0)
|
| 104 |
+
|
| 105 |
+
return output_dict
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def interpolate(x, ratio):
|
| 109 |
+
"""Interpolate data in time domain. This is used to compensate the
|
| 110 |
+
resolution reduction in downsampling of a CNN.
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
x: (batch_size, time_steps, classes_num)
|
| 114 |
+
ratio: int, ratio to interpolate
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
upsampled: (batch_size, time_steps * ratio, classes_num)
|
| 118 |
+
"""
|
| 119 |
+
(batch_size, time_steps, classes_num) = x.shape
|
| 120 |
+
upsampled = x[:, :, None, :].repeat(1, 1, ratio, 1)
|
| 121 |
+
upsampled = upsampled.reshape(batch_size, time_steps * ratio, classes_num)
|
| 122 |
+
return upsampled
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def pad_framewise_output(framewise_output, frames_num):
|
| 126 |
+
"""Pad framewise_output to the same length as input frames. The pad value
|
| 127 |
+
is the same as the value of the last frame.
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
framewise_output: (batch_size, frames_num, classes_num)
|
| 131 |
+
frames_num: int, number of frames to pad
|
| 132 |
+
|
| 133 |
+
Outputs:
|
| 134 |
+
output: (batch_size, frames_num, classes_num)
|
| 135 |
+
"""
|
| 136 |
+
pad = framewise_output[:, -1 :, :].repeat(1, frames_num - framewise_output.shape[1], 1)
|
| 137 |
+
"""tensor for padding"""
|
| 138 |
+
|
| 139 |
+
output = torch.cat((framewise_output, pad), dim=1)
|
| 140 |
+
"""(batch_size, frames_num, classes_num)"""
|
| 141 |
+
|
| 142 |
+
return output
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def count_parameters(model):
|
| 146 |
+
return sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def count_flops(model, audio_length):
|
| 150 |
+
"""Count flops. Code modified from others' implementation.
|
| 151 |
+
"""
|
| 152 |
+
multiply_adds = True
|
| 153 |
+
list_conv2d=[]
|
| 154 |
+
def conv2d_hook(self, input, output):
|
| 155 |
+
batch_size, input_channels, input_height, input_width = input[0].size()
|
| 156 |
+
output_channels, output_height, output_width = output[0].size()
|
| 157 |
+
|
| 158 |
+
kernel_ops = self.kernel_size[0] * self.kernel_size[1] * (self.in_channels / self.groups) * (2 if multiply_adds else 1)
|
| 159 |
+
bias_ops = 1 if self.bias is not None else 0
|
| 160 |
+
|
| 161 |
+
params = output_channels * (kernel_ops + bias_ops)
|
| 162 |
+
flops = batch_size * params * output_height * output_width
|
| 163 |
+
|
| 164 |
+
list_conv2d.append(flops)
|
| 165 |
+
|
| 166 |
+
list_conv1d=[]
|
| 167 |
+
def conv1d_hook(self, input, output):
|
| 168 |
+
batch_size, input_channels, input_length = input[0].size()
|
| 169 |
+
output_channels, output_length = output[0].size()
|
| 170 |
+
|
| 171 |
+
kernel_ops = self.kernel_size[0] * (self.in_channels / self.groups) * (2 if multiply_adds else 1)
|
| 172 |
+
bias_ops = 1 if self.bias is not None else 0
|
| 173 |
+
|
| 174 |
+
params = output_channels * (kernel_ops + bias_ops)
|
| 175 |
+
flops = batch_size * params * output_length
|
| 176 |
+
|
| 177 |
+
list_conv1d.append(flops)
|
| 178 |
+
|
| 179 |
+
list_linear=[]
|
| 180 |
+
def linear_hook(self, input, output):
|
| 181 |
+
batch_size = input[0].size(0) if input[0].dim() == 2 else 1
|
| 182 |
+
|
| 183 |
+
weight_ops = self.weight.nelement() * (2 if multiply_adds else 1)
|
| 184 |
+
bias_ops = self.bias.nelement()
|
| 185 |
+
|
| 186 |
+
flops = batch_size * (weight_ops + bias_ops)
|
| 187 |
+
list_linear.append(flops)
|
| 188 |
+
|
| 189 |
+
list_bn=[]
|
| 190 |
+
def bn_hook(self, input, output):
|
| 191 |
+
list_bn.append(input[0].nelement() * 2)
|
| 192 |
+
|
| 193 |
+
list_relu=[]
|
| 194 |
+
def relu_hook(self, input, output):
|
| 195 |
+
list_relu.append(input[0].nelement() * 2)
|
| 196 |
+
|
| 197 |
+
list_pooling2d=[]
|
| 198 |
+
def pooling2d_hook(self, input, output):
|
| 199 |
+
batch_size, input_channels, input_height, input_width = input[0].size()
|
| 200 |
+
output_channels, output_height, output_width = output[0].size()
|
| 201 |
+
|
| 202 |
+
kernel_ops = self.kernel_size * self.kernel_size
|
| 203 |
+
bias_ops = 0
|
| 204 |
+
params = output_channels * (kernel_ops + bias_ops)
|
| 205 |
+
flops = batch_size * params * output_height * output_width
|
| 206 |
+
|
| 207 |
+
list_pooling2d.append(flops)
|
| 208 |
+
|
| 209 |
+
list_pooling1d=[]
|
| 210 |
+
def pooling1d_hook(self, input, output):
|
| 211 |
+
batch_size, input_channels, input_length = input[0].size()
|
| 212 |
+
output_channels, output_length = output[0].size()
|
| 213 |
+
|
| 214 |
+
kernel_ops = self.kernel_size[0]
|
| 215 |
+
bias_ops = 0
|
| 216 |
+
|
| 217 |
+
params = output_channels * (kernel_ops + bias_ops)
|
| 218 |
+
flops = batch_size * params * output_length
|
| 219 |
+
|
| 220 |
+
list_pooling2d.append(flops)
|
| 221 |
+
|
| 222 |
+
def foo(net):
|
| 223 |
+
childrens = list(net.children())
|
| 224 |
+
if not childrens:
|
| 225 |
+
if isinstance(net, nn.Conv2d):
|
| 226 |
+
net.register_forward_hook(conv2d_hook)
|
| 227 |
+
elif isinstance(net, nn.Conv1d):
|
| 228 |
+
net.register_forward_hook(conv1d_hook)
|
| 229 |
+
elif isinstance(net, nn.Linear):
|
| 230 |
+
net.register_forward_hook(linear_hook)
|
| 231 |
+
elif isinstance(net, nn.BatchNorm2d) or isinstance(net, nn.BatchNorm1d):
|
| 232 |
+
net.register_forward_hook(bn_hook)
|
| 233 |
+
elif isinstance(net, nn.ReLU):
|
| 234 |
+
net.register_forward_hook(relu_hook)
|
| 235 |
+
elif isinstance(net, nn.AvgPool2d) or isinstance(net, nn.MaxPool2d):
|
| 236 |
+
net.register_forward_hook(pooling2d_hook)
|
| 237 |
+
elif isinstance(net, nn.AvgPool1d) or isinstance(net, nn.MaxPool1d):
|
| 238 |
+
net.register_forward_hook(pooling1d_hook)
|
| 239 |
+
else:
|
| 240 |
+
print('Warning: flop of module {} is not counted!'.format(net))
|
| 241 |
+
return
|
| 242 |
+
for c in childrens:
|
| 243 |
+
foo(c)
|
| 244 |
+
|
| 245 |
+
# Register hook
|
| 246 |
+
foo(model)
|
| 247 |
+
|
| 248 |
+
device = device = next(model.parameters()).device
|
| 249 |
+
input = torch.rand(1, audio_length).to(device)
|
| 250 |
+
|
| 251 |
+
out = model(input)
|
| 252 |
+
|
| 253 |
+
total_flops = sum(list_conv2d) + sum(list_conv1d) + sum(list_linear) + \
|
| 254 |
+
sum(list_bn) + sum(list_relu) + sum(list_pooling2d) + sum(list_pooling1d)
|
| 255 |
+
|
| 256 |
+
return total_flops
|
utils/fx_normalization/__init__.py
ADDED
|
File without changes
|
utils/fx_normalization/features.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1271839aa88a321deb76fb3e4f3059eeee26cf64c4071cd116e1e4a69d896d74
|
| 3 |
+
size 1574572
|
utils/fx_normalization/fxnorm.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
from utils.training_utils import Gauss_smooth_vectorized, prepare_smooth_filter
|
| 5 |
+
|
| 6 |
+
def T602logmag(t60, sample_rate=44100, hop_length=512):
|
| 7 |
+
return 6.908 / (t60 * (sample_rate / hop_length)) # Convert T60 to delta log magnitude
|
| 8 |
+
|
| 9 |
+
from utils.data_utils import apply_RMS_normalization
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class FxNormAug:
|
| 13 |
+
|
| 14 |
+
def __init__(self,
|
| 15 |
+
sample_rate=44100, # Sample rate of the audio
|
| 16 |
+
device="cuda" if torch.cuda.is_available() else "cpu",
|
| 17 |
+
mode="train", # Mode can be "train" or "eval"
|
| 18 |
+
seed=42,
|
| 19 |
+
features_path="features_tency1_4instr_v4.npy", # Path to the features file
|
| 20 |
+
):
|
| 21 |
+
|
| 22 |
+
torch.random.manual_seed(seed)
|
| 23 |
+
|
| 24 |
+
#the path is in the same directory as this file
|
| 25 |
+
self.features_path = features_path
|
| 26 |
+
|
| 27 |
+
self.sample_rate = sample_rate
|
| 28 |
+
self.device = device
|
| 29 |
+
self.train_setup()
|
| 30 |
+
|
| 31 |
+
self.EQ_normalize_setup() # Initialize EQ normalization function
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def train_setup(self):
|
| 35 |
+
#hardcoded setuo used for training
|
| 36 |
+
|
| 37 |
+
self.RMS_norm=-25 # Target RMS level in dB, used for normalization
|
| 38 |
+
|
| 39 |
+
def EQ_normalize_setup(self ):
|
| 40 |
+
|
| 41 |
+
features_mean = np.load(self.features_path, allow_pickle='TRUE')[()]
|
| 42 |
+
|
| 43 |
+
target_cuves_original= {
|
| 44 |
+
"vocals": torch.tensor(features_mean["eq"]["vocals"]).to(torch.float32).to(self.device),
|
| 45 |
+
"drums": torch.tensor(features_mean["eq"]["drums"]).to(torch.float32).to(self.device),
|
| 46 |
+
"bass": torch.tensor(features_mean["eq"]["bass"]).to(torch.float32).to(self.device),
|
| 47 |
+
"other": torch.tensor(features_mean["eq"]["other"]).to(torch.float32).to(self.device),
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
nfft=4096 # FFT size hardcoded
|
| 52 |
+
nfft_orig = 65536 # FFT size for the smoothing filter
|
| 53 |
+
|
| 54 |
+
win_length=2048 # Window length hardcoded
|
| 55 |
+
hop_length=1024 # Hop length hardcoded
|
| 56 |
+
|
| 57 |
+
window = torch.sqrt(torch.hann_window(win_length, device=self.device))
|
| 58 |
+
window_energy = window.pow(2).sum().sqrt() # Energy of the window
|
| 59 |
+
|
| 60 |
+
freqs = torch.fft.rfftfreq(nfft, d=1.0).to(self.device)
|
| 61 |
+
freqs_Hz=torch.fft.rfftfreq(nfft, d=1.0).to(self.device) * self.sample_rate
|
| 62 |
+
|
| 63 |
+
smooth_filter = prepare_smooth_filter(freqs_Hz, Noct=3).to(self.device) # Prepare the smoothing filter
|
| 64 |
+
|
| 65 |
+
freqs_Hz_orig=torch.fft.rfftfreq(nfft_orig, d=1.0).to(self.device) * self.sample_rate
|
| 66 |
+
smooth_filter_orig = prepare_smooth_filter(freqs_Hz_orig, Noct=3).to(self.device) # Prepare the smoothing filter
|
| 67 |
+
|
| 68 |
+
def downsample_curve(x):
|
| 69 |
+
return torch.nn.functional.interpolate(
|
| 70 |
+
x.unsqueeze(0).unsqueeze(0),
|
| 71 |
+
size=(nfft // 2 + 1,),
|
| 72 |
+
mode='linear',
|
| 73 |
+
align_corners=False
|
| 74 |
+
).squeeze(0).squeeze(0)
|
| 75 |
+
|
| 76 |
+
target_curves = {
|
| 77 |
+
"vocals": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["vocals"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 78 |
+
"drums": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["drums"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 79 |
+
"bass": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["bass"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 80 |
+
"other": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["other"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def EQ_normalize_fn(x):
|
| 85 |
+
|
| 86 |
+
shape= x.shape
|
| 87 |
+
|
| 88 |
+
target_curves_tensor=torch.zeros((shape[0], nfft // 2 + 1), device=self.device, dtype=torch.float32)
|
| 89 |
+
|
| 90 |
+
for i in range(shape[0]):
|
| 91 |
+
track_class = "other"
|
| 92 |
+
|
| 93 |
+
assert track_class in target_curves, f"track_class {track_class} not found in target_curves"
|
| 94 |
+
target_curves_tensor[i] = target_curves[track_class]
|
| 95 |
+
|
| 96 |
+
x=x.view(-1, shape[-1])
|
| 97 |
+
#ensure x.shape[-1] is divisible by hop_length
|
| 98 |
+
if x.shape[-1] % hop_length != 0:
|
| 99 |
+
# Pad the input to make it divisible by hop_length
|
| 100 |
+
pad_length = hop_length - (x.shape[-1] % hop_length)
|
| 101 |
+
x = torch.nn.functional.pad(x, (0, pad_length), mode='constant', value=0)
|
| 102 |
+
X=torch.stft(x, n_fft=nfft, hop_length=hop_length, win_length=win_length, window=window, return_complex=True)/ window_energy
|
| 103 |
+
X_pow=X.abs().pow(2)
|
| 104 |
+
X_mean= torch.sqrt(X_pow.mean(dim=-1, keepdim=False)) # Mean power spectrum
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
ratio= target_curves_tensor / (X_mean + 1e-6)
|
| 109 |
+
|
| 110 |
+
ratio = torch.clamp(ratio, max=10.0**(20.0/20.0))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
ratio_smooth = Gauss_smooth_vectorized(ratio, freqs_Hz, Noct=3, smooth_filter=smooth_filter)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
X= X * ratio_smooth.unsqueeze(-1)
|
| 118 |
+
|
| 119 |
+
X_unnormalized=X* window_energy
|
| 120 |
+
|
| 121 |
+
x_reconstructed = torch.istft(X_unnormalized,
|
| 122 |
+
n_fft=nfft,
|
| 123 |
+
hop_length=hop_length,
|
| 124 |
+
win_length=win_length,
|
| 125 |
+
window=window,
|
| 126 |
+
return_complex=False) # Set to True if you want complex outpu
|
| 127 |
+
|
| 128 |
+
#remove the padding if it was added
|
| 129 |
+
if x_reconstructed.shape[-1] > shape[-1]:
|
| 130 |
+
x_reconstructed = x_reconstructed[..., :shape[-1]]
|
| 131 |
+
x_reconstructed = x_reconstructed.view(shape)
|
| 132 |
+
return x_reconstructed
|
| 133 |
+
|
| 134 |
+
self.EQ_normalize = EQ_normalize_fn
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def __call__(self, x, use_gate=False, RMS=None):
|
| 138 |
+
|
| 139 |
+
B, C, T = x.shape
|
| 140 |
+
if C > 1:
|
| 141 |
+
x = x.mean(dim=1, keepdim=True)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
x= apply_RMS_normalization(x, self.RMS_norm , use_gate=use_gate) # Apply RMS normalization to the input
|
| 145 |
+
|
| 146 |
+
x=self.EQ_normalize(x)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
x= apply_RMS_normalization(x, self.RMS_norm, use_gate=use_gate)
|
| 151 |
+
assert not torch.isnan(x).any(), "NaN detected in x after EQ normalization"
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
return x
|
utils/fx_normalization/fxnorm_v2_public.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
from utils.training_utils import Gauss_smooth_vectorized, prepare_smooth_filter
|
| 5 |
+
|
| 6 |
+
def T602logmag(t60, sample_rate=44100, hop_length=512):
|
| 7 |
+
return 6.908 / (t60 * (sample_rate / hop_length)) # Convert T60 to delta log magnitude
|
| 8 |
+
|
| 9 |
+
from utils.data_utils import apply_RMS_normalization
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class FxNormAug:
|
| 13 |
+
|
| 14 |
+
def __init__(self,
|
| 15 |
+
sample_rate=44100, # Sample rate of the audio
|
| 16 |
+
device="cuda" if torch.cuda.is_available() else "cpu",
|
| 17 |
+
mode="train", # Mode can be "train" or "eval"
|
| 18 |
+
seed=42,
|
| 19 |
+
features_path="features_tency1_4instr_v4.npy", # Path to the features file
|
| 20 |
+
):
|
| 21 |
+
|
| 22 |
+
torch.random.manual_seed(seed)
|
| 23 |
+
|
| 24 |
+
#the path is in the same directory as this file
|
| 25 |
+
self.features_path = features_path
|
| 26 |
+
|
| 27 |
+
self.sample_rate = sample_rate
|
| 28 |
+
self.device = device
|
| 29 |
+
|
| 30 |
+
self.EQ_normalize_setup() # Initialize EQ normalization function
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def EQ_normalize_setup(self ):
|
| 35 |
+
|
| 36 |
+
features_mean = np.load(self.features_path, allow_pickle='TRUE')[()]
|
| 37 |
+
|
| 38 |
+
target_cuves_original= {
|
| 39 |
+
"vocals": torch.tensor(features_mean["eq"]["vocals"]).to(torch.float32).to(self.device),
|
| 40 |
+
"drums": torch.tensor(features_mean["eq"]["drums"]).to(torch.float32).to(self.device),
|
| 41 |
+
"bass": torch.tensor(features_mean["eq"]["bass"]).to(torch.float32).to(self.device),
|
| 42 |
+
"other": torch.tensor(features_mean["eq"]["other"]).to(torch.float32).to(self.device),
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
nfft=4096 # FFT size hardcoded
|
| 47 |
+
nfft_orig = 65536 # FFT size for the smoothing filter
|
| 48 |
+
|
| 49 |
+
win_length=2048 # Window length hardcoded
|
| 50 |
+
hop_length=1024 # Hop length hardcoded
|
| 51 |
+
|
| 52 |
+
window = torch.sqrt(torch.hann_window(win_length, device=self.device))
|
| 53 |
+
window_energy = window.pow(2).sum().sqrt() # Energy of the window
|
| 54 |
+
|
| 55 |
+
freqs = torch.fft.rfftfreq(nfft, d=1.0).to(self.device)
|
| 56 |
+
freqs_Hz=torch.fft.rfftfreq(nfft, d=1.0).to(self.device) * self.sample_rate
|
| 57 |
+
|
| 58 |
+
smooth_filter = prepare_smooth_filter(freqs_Hz, Noct=3).to(self.device) # Prepare the smoothing filter
|
| 59 |
+
|
| 60 |
+
freqs_Hz_orig=torch.fft.rfftfreq(nfft_orig, d=1.0).to(self.device) * self.sample_rate
|
| 61 |
+
smooth_filter_orig = prepare_smooth_filter(freqs_Hz_orig, Noct=3).to(self.device) # Prepare the smoothing filter
|
| 62 |
+
|
| 63 |
+
def downsample_curve(x):
|
| 64 |
+
return torch.nn.functional.interpolate(
|
| 65 |
+
x.unsqueeze(0).unsqueeze(0),
|
| 66 |
+
size=(nfft // 2 + 1,),
|
| 67 |
+
mode='linear',
|
| 68 |
+
align_corners=False
|
| 69 |
+
).squeeze(0).squeeze(0)
|
| 70 |
+
|
| 71 |
+
target_curves = {
|
| 72 |
+
"vocals": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["vocals"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 73 |
+
"drums": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["drums"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 74 |
+
"bass": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["bass"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 75 |
+
"other": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["other"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def EQ_normalize_fn(x):
|
| 80 |
+
|
| 81 |
+
shape= x.shape
|
| 82 |
+
|
| 83 |
+
target_curves_tensor=torch.zeros((shape[0], nfft // 2 + 1), device=self.device, dtype=torch.float32)
|
| 84 |
+
|
| 85 |
+
for i in range(shape[0]):
|
| 86 |
+
track_class = "other"
|
| 87 |
+
|
| 88 |
+
assert track_class in target_curves, f"track_class {track_class} not found in target_curves"
|
| 89 |
+
target_curves_tensor[i] = target_curves[track_class]
|
| 90 |
+
|
| 91 |
+
x=x.view(-1, shape[-1])
|
| 92 |
+
#ensure x.shape[-1] is divisible by hop_length
|
| 93 |
+
if x.shape[-1] % hop_length != 0:
|
| 94 |
+
# Pad the input to make it divisible by hop_length
|
| 95 |
+
pad_length = hop_length - (x.shape[-1] % hop_length)
|
| 96 |
+
x = torch.nn.functional.pad(x, (0, pad_length), mode='constant', value=0)
|
| 97 |
+
X=torch.stft(x, n_fft=nfft, hop_length=hop_length, win_length=win_length, window=window, return_complex=True)/ window_energy
|
| 98 |
+
X_pow=X.abs().pow(2)
|
| 99 |
+
X_mean= torch.sqrt(X_pow.mean(dim=-1, keepdim=False)) # Mean power spectrum
|
| 100 |
+
|
| 101 |
+
ratio= target_curves_tensor / (X_mean + 1e-6)
|
| 102 |
+
|
| 103 |
+
ratio = torch.clamp(ratio, max=10.0**(40.0/20.0))
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
ratio_smooth = Gauss_smooth_vectorized(ratio, freqs_Hz, Noct=3, smooth_filter=smooth_filter)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
X= X * ratio_smooth.unsqueeze(-1)
|
| 110 |
+
|
| 111 |
+
X_unnormalized=X* window_energy
|
| 112 |
+
|
| 113 |
+
x_reconstructed = torch.istft(X_unnormalized,
|
| 114 |
+
n_fft=nfft,
|
| 115 |
+
hop_length=hop_length,
|
| 116 |
+
win_length=win_length,
|
| 117 |
+
window=window,
|
| 118 |
+
return_complex=False) # Set to True if you want complex outpu
|
| 119 |
+
|
| 120 |
+
#remove the padding if it was added
|
| 121 |
+
if x_reconstructed.shape[-1] > shape[-1]:
|
| 122 |
+
x_reconstructed = x_reconstructed[..., :shape[-1]]
|
| 123 |
+
x_reconstructed = x_reconstructed.view(shape)
|
| 124 |
+
return x_reconstructed
|
| 125 |
+
|
| 126 |
+
self.EQ_normalize = EQ_normalize_fn
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def __call__(self, x, use_gate=False, RMS=-25):
|
| 130 |
+
|
| 131 |
+
B, C, T = x.shape
|
| 132 |
+
if C > 1:
|
| 133 |
+
x = x.mean(dim=1, keepdim=True)
|
| 134 |
+
|
| 135 |
+
x=x/x.max()
|
| 136 |
+
|
| 137 |
+
x=self.EQ_normalize(x)
|
| 138 |
+
|
| 139 |
+
x= apply_RMS_normalization(x, RMS, use_gate=use_gate)
|
| 140 |
+
assert not torch.isnan(x).any(), "NaN detected in x after EQ normalization"
|
| 141 |
+
|
| 142 |
+
return x
|
utils/fxencoder_plusplus/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .model import FxEncoderPlusPlus, load_model, load_default_model
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
__version__ = "0.1.0"
|
| 5 |
+
|
| 6 |
+
# This defines what gets imported when someone does "from fxencoder_plusplus import *"
|
| 7 |
+
__all__ = [
|
| 8 |
+
'FxEncoderPlusPlus',
|
| 9 |
+
'load_model',
|
| 10 |
+
'load_default_model'
|
| 11 |
+
]
|
| 12 |
+
|
utils/fxencoder_plusplus/model.py
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
#import laion_clap
|
| 3 |
+
import logging
|
| 4 |
+
import sys
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from torchlibrosa.stft import Spectrogram, LogmelFilterBank
|
| 9 |
+
from collections import OrderedDict
|
| 10 |
+
from dataclasses import dataclass
|
| 11 |
+
import numpy as np
|
| 12 |
+
import os
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from huggingface_hub import hf_hub_download
|
| 15 |
+
|
| 16 |
+
def init_layer(layer):
|
| 17 |
+
"""Initialize a Linear or Convolutional layer. """
|
| 18 |
+
nn.init.xavier_uniform_(layer.weight)
|
| 19 |
+
|
| 20 |
+
if hasattr(layer, 'bias'):
|
| 21 |
+
if layer.bias is not None:
|
| 22 |
+
layer.bias.data.fill_(0.)
|
| 23 |
+
|
| 24 |
+
def init_bn(bn):
|
| 25 |
+
"""Initialize a Batchnorm layer. """
|
| 26 |
+
bn.bias.data.fill_(0.)
|
| 27 |
+
bn.weight.data.fill_(1.)
|
| 28 |
+
|
| 29 |
+
class ConvBlock(nn.Module):
|
| 30 |
+
def __init__(self, in_channels, out_channels):
|
| 31 |
+
|
| 32 |
+
super(ConvBlock, self).__init__()
|
| 33 |
+
|
| 34 |
+
self.conv1 = nn.Conv2d(in_channels=in_channels,
|
| 35 |
+
out_channels=out_channels,
|
| 36 |
+
kernel_size=(3, 3), stride=(1, 1),
|
| 37 |
+
padding=(1, 1), bias=False)
|
| 38 |
+
|
| 39 |
+
self.conv2 = nn.Conv2d(in_channels=out_channels,
|
| 40 |
+
out_channels=out_channels,
|
| 41 |
+
kernel_size=(3, 3), stride=(1, 1),
|
| 42 |
+
padding=(1, 1), bias=False)
|
| 43 |
+
|
| 44 |
+
self.bn1 = nn.BatchNorm2d(out_channels)
|
| 45 |
+
self.bn2 = nn.BatchNorm2d(out_channels)
|
| 46 |
+
|
| 47 |
+
self.init_weight()
|
| 48 |
+
|
| 49 |
+
def init_weight(self):
|
| 50 |
+
init_layer(self.conv1)
|
| 51 |
+
init_layer(self.conv2)
|
| 52 |
+
init_bn(self.bn1)
|
| 53 |
+
init_bn(self.bn2)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def forward(self, input, pool_size=(2, 2), pool_type='avg'):
|
| 57 |
+
|
| 58 |
+
x = input
|
| 59 |
+
x = F.relu_(self.bn1(self.conv1(x)))
|
| 60 |
+
x = F.relu_(self.bn2(self.conv2(x)))
|
| 61 |
+
if pool_type == 'max':
|
| 62 |
+
x = F.max_pool2d(x, kernel_size=pool_size)
|
| 63 |
+
elif pool_type == 'avg':
|
| 64 |
+
x = F.avg_pool2d(x, kernel_size=pool_size)
|
| 65 |
+
elif pool_type == 'avg+max':
|
| 66 |
+
x1 = F.avg_pool2d(x, kernel_size=pool_size)
|
| 67 |
+
x2 = F.max_pool2d(x, kernel_size=pool_size)
|
| 68 |
+
x = x1 + x2
|
| 69 |
+
else:
|
| 70 |
+
raise Exception('Incorrect argument!')
|
| 71 |
+
|
| 72 |
+
return x
|
| 73 |
+
|
| 74 |
+
class CLAP_AUDIO_ENCODER(torch.nn.Module):
|
| 75 |
+
def __init__(self, pretrained: bool = True, frozen: bool = False) -> None:
|
| 76 |
+
super().__init__()
|
| 77 |
+
self.pretrained = pretrained
|
| 78 |
+
self.frozen = frozen
|
| 79 |
+
|
| 80 |
+
# load the model
|
| 81 |
+
self.encoder = laion_clap.CLAP_Module(enable_fusion=False, amodel= 'HTSAT-tiny', tmodel='roberta')
|
| 82 |
+
if self.pretrained:
|
| 83 |
+
self.encoder.load_ckpt() # download the default pretrained checkpoint.
|
| 84 |
+
|
| 85 |
+
self.embed_dim = 512
|
| 86 |
+
|
| 87 |
+
def forward(self, x: torch.Tensor):
|
| 88 |
+
if self.frozen:
|
| 89 |
+
with torch.no_grad():
|
| 90 |
+
embed = self.encoder.get_audio_embedding_from_data(
|
| 91 |
+
x=x, use_tensor=True
|
| 92 |
+
)
|
| 93 |
+
else:
|
| 94 |
+
embed = self.encoder.get_audio_embedding_from_data(
|
| 95 |
+
x=x, use_tensor=True
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
return embed
|
| 99 |
+
|
| 100 |
+
class CLAP_TEXT_ENCODER(torch.nn.Module):
|
| 101 |
+
def __init__(self, pretrained: bool = True, frozen: bool = False) -> None:
|
| 102 |
+
super().__init__()
|
| 103 |
+
self.pretrained = pretrained
|
| 104 |
+
self.frozen = frozen
|
| 105 |
+
|
| 106 |
+
# load the model
|
| 107 |
+
self.encoder = laion_clap.CLAP_Module(enable_fusion=False, amodel= 'HTSAT-tiny', tmodel='roberta')
|
| 108 |
+
if self.pretrained:
|
| 109 |
+
self.encoder.load_ckpt() # download the default pretrained checkpoint.
|
| 110 |
+
|
| 111 |
+
self.embed_dim = 512
|
| 112 |
+
|
| 113 |
+
def forward(self, x):
|
| 114 |
+
if self.frozen:
|
| 115 |
+
with torch.no_grad():
|
| 116 |
+
embed = self.encoder.get_text_embedding(
|
| 117 |
+
x=x, use_tensor=True
|
| 118 |
+
)
|
| 119 |
+
else:
|
| 120 |
+
embed = self.encoder.get_text_embedding(
|
| 121 |
+
x=x, use_tensor=True
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
return embed
|
| 125 |
+
|
| 126 |
+
# > ================ Proposed =================== <
|
| 127 |
+
class MixtureFxEncoder(nn.Module):
|
| 128 |
+
def __init__(self, sample_rate, window_size, hop_size, mel_bins, fmin, fmax, enable_fusion=False, fusion_type='None'):
|
| 129 |
+
super().__init__()
|
| 130 |
+
self.enable_fusion = enable_fusion
|
| 131 |
+
self.fusion_type = fusion_type
|
| 132 |
+
|
| 133 |
+
window = "hann"
|
| 134 |
+
center = True
|
| 135 |
+
pad_mode = "reflect"
|
| 136 |
+
ref = 1.0
|
| 137 |
+
amin = 1e-10
|
| 138 |
+
top_db = None
|
| 139 |
+
self.input_norm = "minmax"
|
| 140 |
+
# Spectrogram extractor
|
| 141 |
+
self.spectrogram_extractor = Spectrogram(
|
| 142 |
+
n_fft=window_size,
|
| 143 |
+
hop_length=hop_size,
|
| 144 |
+
win_length=window_size,
|
| 145 |
+
window=window,
|
| 146 |
+
center=center,
|
| 147 |
+
pad_mode=pad_mode,
|
| 148 |
+
freeze_parameters=True,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# Logmel feature extractor
|
| 152 |
+
self.logmel_extractor = LogmelFilterBank(
|
| 153 |
+
sr=sample_rate,
|
| 154 |
+
n_fft=window_size,
|
| 155 |
+
n_mels=mel_bins,
|
| 156 |
+
fmin=fmin,
|
| 157 |
+
fmax=fmax,
|
| 158 |
+
ref=ref,
|
| 159 |
+
amin=amin,
|
| 160 |
+
top_db=top_db,
|
| 161 |
+
freeze_parameters=True,
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
self.bn0 = nn.BatchNorm2d(64)
|
| 165 |
+
|
| 166 |
+
self.conv_block1 = ConvBlock(in_channels=2, out_channels=64)
|
| 167 |
+
self.conv_block2 = ConvBlock(in_channels=64, out_channels=128)
|
| 168 |
+
self.conv_block3 = ConvBlock(in_channels=128, out_channels=256)
|
| 169 |
+
self.conv_block4 = ConvBlock(in_channels=256, out_channels=512)
|
| 170 |
+
self.conv_block5 = ConvBlock(in_channels=512, out_channels=1024)
|
| 171 |
+
self.conv_block6 = ConvBlock(in_channels=1024, out_channels=2048)
|
| 172 |
+
|
| 173 |
+
self.fc_1 = nn.Linear(2048, 2048, bias=True)
|
| 174 |
+
|
| 175 |
+
self.init_weight()
|
| 176 |
+
|
| 177 |
+
def init_weight(self):
|
| 178 |
+
init_bn(self.bn0)
|
| 179 |
+
init_layer(self.fc_1)
|
| 180 |
+
|
| 181 |
+
def forward(self, x):
|
| 182 |
+
"""
|
| 183 |
+
Input: (batch_size, 2, data_length)
|
| 184 |
+
"""
|
| 185 |
+
batch_size, chs, seq_len = x.size()
|
| 186 |
+
|
| 187 |
+
# move to batch dim
|
| 188 |
+
x = x.view(batch_size * chs, seq_len)
|
| 189 |
+
|
| 190 |
+
# extract logmel features
|
| 191 |
+
x = self.spectrogram_extractor(x) # (batch_size, 1, time_steps, freq_bins)
|
| 192 |
+
x = self.logmel_extractor(x) # (batch_size, 1, time_steps, mel_bins)
|
| 193 |
+
|
| 194 |
+
if self.input_norm == "batchnorm":
|
| 195 |
+
# this normalizes over mel bins which is problematic for equalization
|
| 196 |
+
x = x.transpose(1, 3)
|
| 197 |
+
x = self.bn0(x)
|
| 198 |
+
x = x.transpose(1, 3)
|
| 199 |
+
elif self.input_norm == "minmax":
|
| 200 |
+
x = x.clamp(-80, 40.0) # clamp the logmels between -80 and 40
|
| 201 |
+
x = (x + 80) / 120 # normalize the logmels between 0 and 1
|
| 202 |
+
x = (x * 2) - 1 # normalize the logmels between -1 and 1
|
| 203 |
+
elif self.input_norm == "none":
|
| 204 |
+
pass
|
| 205 |
+
else:
|
| 206 |
+
raise ValueError(f"Invalid input_norm: {self.input_norm}")
|
| 207 |
+
|
| 208 |
+
x = x.view(batch_size, chs, x.size(-2), x.size(-1))
|
| 209 |
+
|
| 210 |
+
x = self.conv_block1(x, pool_size=(2, 2), pool_type='avg')
|
| 211 |
+
x = F.dropout(x, p=0.2, training=self.training)
|
| 212 |
+
x = self.conv_block2(x, pool_size=(2, 2), pool_type='avg')
|
| 213 |
+
x = F.dropout(x, p=0.2, training=self.training)
|
| 214 |
+
x = self.conv_block3(x, pool_size=(2, 2), pool_type='avg')
|
| 215 |
+
x = F.dropout(x, p=0.2, training=self.training)
|
| 216 |
+
x = self.conv_block4(x, pool_size=(2, 2), pool_type='avg')
|
| 217 |
+
x = F.dropout(x, p=0.2, training=self.training)
|
| 218 |
+
x = self.conv_block5(x, pool_size=(2, 2), pool_type='avg')
|
| 219 |
+
x = F.dropout(x, p=0.2, training=self.training)
|
| 220 |
+
x = self.conv_block6(x, pool_size=(1, 1), pool_type='avg')
|
| 221 |
+
x = F.dropout(x, p=0.2, training=self.training)
|
| 222 |
+
x = torch.mean(x, dim=3)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
(x1, _) = torch.max(x, dim=2)
|
| 226 |
+
x2 = torch.mean(x, dim=2)
|
| 227 |
+
x = x1 + x2
|
| 228 |
+
x = F.relu_(self.fc_1(x))
|
| 229 |
+
embedding = x
|
| 230 |
+
|
| 231 |
+
output_dict = {
|
| 232 |
+
'embedding': embedding,
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
return output_dict
|
| 236 |
+
|
| 237 |
+
def create_MixtureFxEncoder():
|
| 238 |
+
model = MixtureFxEncoder(
|
| 239 |
+
sample_rate = 44100, #audio_cfg.sample_rate,
|
| 240 |
+
window_size = 2048, #audio_cfg.window_size,
|
| 241 |
+
hop_size = 512, #audio_cfg.hop_size,
|
| 242 |
+
mel_bins = 64, #audio_cfg.mel_bins,
|
| 243 |
+
fmin = 50, #audio_cfg.fmin,
|
| 244 |
+
fmax = 18000, #audio_cfg.fmax,
|
| 245 |
+
)
|
| 246 |
+
return model
|
| 247 |
+
|
| 248 |
+
class MLPLayers(nn.Module):
|
| 249 |
+
def __init__(self, units=[512, 512, 512], nonlin=nn.ReLU(), dropout=0.1):
|
| 250 |
+
super(MLPLayers, self).__init__()
|
| 251 |
+
self.nonlin = nonlin
|
| 252 |
+
self.dropout = dropout
|
| 253 |
+
|
| 254 |
+
sequence = []
|
| 255 |
+
for u0, u1 in zip(units[:-1], units[1:]):
|
| 256 |
+
sequence.append(nn.Linear(u0, u1))
|
| 257 |
+
sequence.append(self.nonlin)
|
| 258 |
+
sequence.append(nn.Dropout(self.dropout))
|
| 259 |
+
sequence = sequence[:-2]
|
| 260 |
+
|
| 261 |
+
self.sequential = nn.Sequential(*sequence)
|
| 262 |
+
|
| 263 |
+
def forward(self, X):
|
| 264 |
+
X = self.sequential(X)
|
| 265 |
+
return X
|
| 266 |
+
|
| 267 |
+
class BernoulliDynamicDropout(nn.Module):
|
| 268 |
+
def __init__(self):
|
| 269 |
+
super().__init__()
|
| 270 |
+
self.p_min = 0.75
|
| 271 |
+
self.p_max = 0.95
|
| 272 |
+
|
| 273 |
+
def get_random_dropout_rate(self):
|
| 274 |
+
return torch.empty(1).uniform_(self.p_min, self.p_max).item()
|
| 275 |
+
|
| 276 |
+
def forward(self, x):
|
| 277 |
+
if self.training:
|
| 278 |
+
p = self.get_random_dropout_rate()
|
| 279 |
+
mask = torch.bernoulli(torch.full_like(x, 1-p))
|
| 280 |
+
return x * mask / (1 - p)
|
| 281 |
+
return x
|
| 282 |
+
|
| 283 |
+
class AudioExtracter(nn.Module):
|
| 284 |
+
def __init__(self, fx_embedding_dim=128, clap_embedding_dim=512):
|
| 285 |
+
super().__init__()
|
| 286 |
+
|
| 287 |
+
# Simple fusion network
|
| 288 |
+
self.fusion = nn.Sequential(
|
| 289 |
+
nn.Linear(fx_embedding_dim+clap_embedding_dim, 128),
|
| 290 |
+
nn.LeakyReLU(0.1),
|
| 291 |
+
nn.Linear(128, 128),
|
| 292 |
+
nn.LeakyReLU(0.1),
|
| 293 |
+
nn.Linear(128, 128),
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
def forward(self, mixture_emb, query_emb):
|
| 297 |
+
# Concatenate and project
|
| 298 |
+
x = torch.cat([mixture_emb, query_emb], dim=-1) # [B, 2D]
|
| 299 |
+
stem_emb = self.fusion(x) # [B, D]
|
| 300 |
+
|
| 301 |
+
return stem_emb
|
| 302 |
+
|
| 303 |
+
@dataclass
|
| 304 |
+
class AudioCfg:
|
| 305 |
+
model_type: str = "PANN"
|
| 306 |
+
model_name: str = "Cnn14"
|
| 307 |
+
sample_rate: int = 44100
|
| 308 |
+
# Param
|
| 309 |
+
audio_length: int = 1024
|
| 310 |
+
window_size: int = 1024
|
| 311 |
+
hop_size: int = 1024
|
| 312 |
+
fmin: int = 50
|
| 313 |
+
fmax: int = 14000
|
| 314 |
+
mel_bins: int = 64
|
| 315 |
+
clip_samples: int = 441000
|
| 316 |
+
class_num: int = 527
|
| 317 |
+
condition_dim: int = 512
|
| 318 |
+
|
| 319 |
+
class FxEncoderPlusPlus(nn.Module):
|
| 320 |
+
def __init__(
|
| 321 |
+
self,
|
| 322 |
+
embed_dim: int = 2048,
|
| 323 |
+
mixture_cfg: AudioCfg = None,
|
| 324 |
+
enable_fusion: bool = False,
|
| 325 |
+
fusion_type: str = 'None',
|
| 326 |
+
joint_embed_shape: int = 128,
|
| 327 |
+
mlp_act: str = 'relu',
|
| 328 |
+
audio_clap_module: bool = True,
|
| 329 |
+
text_clap_module: bool = False,
|
| 330 |
+
extractor_module: bool = True,
|
| 331 |
+
device: str = "cpu",
|
| 332 |
+
):
|
| 333 |
+
super().__init__()
|
| 334 |
+
|
| 335 |
+
self.mixture_cfg = mixture_cfg
|
| 336 |
+
self.enable_fusion = enable_fusion
|
| 337 |
+
self.fusion_type = fusion_type
|
| 338 |
+
self.joint_embed_shape = joint_embed_shape
|
| 339 |
+
self.mlp_act = mlp_act
|
| 340 |
+
self.device = device
|
| 341 |
+
|
| 342 |
+
if mlp_act == 'relu':
|
| 343 |
+
mlp_act_layer = nn.ReLU()
|
| 344 |
+
elif mlp_act == 'gelu':
|
| 345 |
+
mlp_act_layer = nn.GELU()
|
| 346 |
+
else:
|
| 347 |
+
raise NotImplementedError
|
| 348 |
+
|
| 349 |
+
# > ========================= FX Encoder ========================= <
|
| 350 |
+
self.fx_encoder = create_MixtureFxEncoder()
|
| 351 |
+
self.fx_encoder_transform = MLPLayers(units=[self.joint_embed_shape, self.joint_embed_shape, self.joint_embed_shape], dropout=0.1)
|
| 352 |
+
|
| 353 |
+
self.fx_encoder_projection = nn.Sequential(
|
| 354 |
+
nn.Linear(embed_dim, self.joint_embed_shape),
|
| 355 |
+
mlp_act_layer,
|
| 356 |
+
nn.Linear(self.joint_embed_shape, self.joint_embed_shape)
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
self.logit_scale_m = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
|
| 360 |
+
self.logit_scale_t = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
|
| 361 |
+
|
| 362 |
+
if audio_clap_module:
|
| 363 |
+
# Freeze all layers
|
| 364 |
+
# print("Loading CLAP Audio Model")
|
| 365 |
+
self.audio_clap_model = CLAP_AUDIO_ENCODER(pretrained=True, frozen=True)
|
| 366 |
+
self.audio_clap_model.to(device)
|
| 367 |
+
for param in self.audio_clap_model.parameters():
|
| 368 |
+
param.requires_grad = False
|
| 369 |
+
self.clap_dropout = BernoulliDynamicDropout()
|
| 370 |
+
if text_clap_module:
|
| 371 |
+
# Freeze all layers
|
| 372 |
+
# print("Loading CLAP Text Model")
|
| 373 |
+
self.text_clap_model = CLAP_TEXT_ENCODER(pretrained=True, frozen=True)
|
| 374 |
+
self.text_clap_model.to(device)
|
| 375 |
+
for param in self.text_clap_model.parameters():
|
| 376 |
+
param.requires_grad = False
|
| 377 |
+
|
| 378 |
+
if extractor_module:
|
| 379 |
+
# extractor
|
| 380 |
+
self.extractor = AudioExtracter()
|
| 381 |
+
|
| 382 |
+
self.use_audio_clap_module = audio_clap_module
|
| 383 |
+
self.use_text_clap_module = text_clap_module
|
| 384 |
+
self.use_extractor_module = extractor_module
|
| 385 |
+
|
| 386 |
+
def get_fx_embedding(self, x):
|
| 387 |
+
fx_emb = self.fx_encoder(x)
|
| 388 |
+
fx_emb = self.fx_encoder_projection(fx_emb["embedding"])
|
| 389 |
+
fx_emb = F.normalize(fx_emb, dim=-1)
|
| 390 |
+
return fx_emb
|
| 391 |
+
|
| 392 |
+
def get_fx_embedding_by_audio_query(self, x, audio_query):
|
| 393 |
+
# mixture fx embedding
|
| 394 |
+
fx_mixture_emb = self.fx_encoder(x)
|
| 395 |
+
fx_mixture_emb = self.fx_encoder_projection(fx_mixture_emb["embedding"])
|
| 396 |
+
fx_mixture_emb = F.normalize(fx_mixture_emb, dim=-1)
|
| 397 |
+
|
| 398 |
+
# stem fx embedding
|
| 399 |
+
query_content_embeded = self.audio_clap_model(torch.mean(audio_query, dim=1))
|
| 400 |
+
fx_stem_emb = self.extractor(fx_mixture_emb, query_content_embeded)
|
| 401 |
+
fx_stem_emb = F.normalize(fx_stem_emb, dim=-1)
|
| 402 |
+
return fx_mixture_emb, fx_stem_emb
|
| 403 |
+
|
| 404 |
+
def get_fx_embedding_by_text_query(self, x, text_query):
|
| 405 |
+
# mixture fx embedding
|
| 406 |
+
fx_mixture_emb = self.fx_encoder(x)
|
| 407 |
+
fx_mixture_emb = self.fx_encoder_projection(fx_mixture_emb["embedding"])
|
| 408 |
+
fx_mixture_emb = F.normalize(fx_mixture_emb, dim=-1)
|
| 409 |
+
|
| 410 |
+
# stem fx embedding
|
| 411 |
+
query_embeded = self.text_clap_model(text_query)
|
| 412 |
+
fx_stem_emb = self.extractor(fx_mixture_emb, query_embeded)
|
| 413 |
+
fx_stem_emb = F.normalize(fx_stem_emb, dim=-1)
|
| 414 |
+
return fx_mixture_emb, fx_stem_emb
|
| 415 |
+
|
| 416 |
+
def forward(
|
| 417 |
+
self,
|
| 418 |
+
mixture_a,
|
| 419 |
+
mixture_b,
|
| 420 |
+
stem_a,
|
| 421 |
+
query_stem,
|
| 422 |
+
device = None
|
| 423 |
+
):
|
| 424 |
+
|
| 425 |
+
if device is None:
|
| 426 |
+
if mixture_a is not None:
|
| 427 |
+
device = mixture_a.device
|
| 428 |
+
elif mixture_b is not None:
|
| 429 |
+
device = mixture_b.device
|
| 430 |
+
if mixture_a is None and mixture_b is None:
|
| 431 |
+
# a hack to get the logit scale
|
| 432 |
+
return self.logit_scale_m.exp(), self.logit_scale_t.exp()
|
| 433 |
+
|
| 434 |
+
# ======== Global ========
|
| 435 |
+
mixture_a_features = self.fx_encoder_projection(
|
| 436 |
+
self.fx_encoder(mixture_a)["embedding"]
|
| 437 |
+
)
|
| 438 |
+
mixture_a_features = F.normalize(mixture_a_features, dim=-1)
|
| 439 |
+
|
| 440 |
+
mixture_b_features = self.fx_encoder_projection(
|
| 441 |
+
self.fx_encoder(mixture_b)["embedding"]
|
| 442 |
+
)
|
| 443 |
+
mixture_b_features = F.normalize(mixture_b_features, dim=-1)
|
| 444 |
+
|
| 445 |
+
mixture_a_features_mlp = self.fx_encoder_transform(mixture_a_features)
|
| 446 |
+
mixture_b_features_mlp = self.fx_encoder_transform(mixture_b_features)
|
| 447 |
+
|
| 448 |
+
# ======= Local ========
|
| 449 |
+
stem_a_features = self.fx_encoder_projection(
|
| 450 |
+
self.fx_encoder(stem_a)["embedding"]
|
| 451 |
+
)
|
| 452 |
+
stem_a_features = F.normalize(stem_a_features, dim=-1)
|
| 453 |
+
|
| 454 |
+
if self.use_audio_clap_module and self.use_extractor_module:
|
| 455 |
+
query_stem_content_embeded = self.clap_dropout(
|
| 456 |
+
self.audio_clap_model(
|
| 457 |
+
torch.mean(query_stem, dim=1)
|
| 458 |
+
)
|
| 459 |
+
)
|
| 460 |
+
extracted_stem_a_features = self.extractor(mixture_a_features, query_stem_content_embeded)
|
| 461 |
+
extracted_stem_a_features = F.normalize(extracted_stem_a_features, dim=-1)
|
| 462 |
+
elif self.use_text_clap_module and self.use_extractor_module:
|
| 463 |
+
query_stem_content_embeded = self.text_clap_model(query_stem)
|
| 464 |
+
extracted_stem_a_features = self.extractor(mixture_a_features, query_stem_content_embeded)
|
| 465 |
+
extracted_stem_a_features = F.normalize(extracted_stem_a_features, dim=-1)
|
| 466 |
+
|
| 467 |
+
return (
|
| 468 |
+
mixture_a_features, # global
|
| 469 |
+
mixture_b_features, # global
|
| 470 |
+
stem_a_features, # local
|
| 471 |
+
extracted_stem_a_features, # local
|
| 472 |
+
mixture_a_features_mlp,
|
| 473 |
+
mixture_b_features_mlp,
|
| 474 |
+
self.logit_scale_m.exp(),
|
| 475 |
+
self.logit_scale_t.exp(),
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
def get_logit_scale(self):
|
| 479 |
+
return self.logit_scale_m.exp(), self.logit_scale_t.exp()
|
| 480 |
+
|
| 481 |
+
# def load_model(model_path, device):
|
| 482 |
+
|
| 483 |
+
# model = FxEncoderPlusPlus(
|
| 484 |
+
# embed_dim = 2048,
|
| 485 |
+
# audio_clap_module = True,
|
| 486 |
+
# extractor_module = True
|
| 487 |
+
# )
|
| 488 |
+
|
| 489 |
+
# # load model
|
| 490 |
+
# checkpoint = torch.load(model_path, map_location=device, weights_only=False)
|
| 491 |
+
# if "epoch" in checkpoint:
|
| 492 |
+
# # resuming a train checkpoint w/ epoch and optimizer state
|
| 493 |
+
# start_epoch = checkpoint["epoch"]
|
| 494 |
+
# sd = checkpoint["state_dict"]
|
| 495 |
+
# if next(iter(sd.items()))[0].startswith(
|
| 496 |
+
# "module"
|
| 497 |
+
# ):
|
| 498 |
+
# sd = {k[len("module."):]: v for k, v in sd.items()}
|
| 499 |
+
# model.load_state_dict(sd)
|
| 500 |
+
# logging.info(
|
| 501 |
+
# f"=> resuming checkpoint '{model_path}' (epoch {start_epoch})"
|
| 502 |
+
# )
|
| 503 |
+
# else:
|
| 504 |
+
# # loading a bare (model only) checkpoint for fine-tune or evaluation
|
| 505 |
+
# model.load_state_dict(checkpoint)
|
| 506 |
+
# start_epoch = 0
|
| 507 |
+
|
| 508 |
+
# model.to(device)
|
| 509 |
+
# model.eval()
|
| 510 |
+
# for param in model.parameters():
|
| 511 |
+
# param.requires_grad = False
|
| 512 |
+
# return model
|
| 513 |
+
|
| 514 |
+
# Define available models
|
| 515 |
+
MODEL_REGISTRY = {
|
| 516 |
+
"default": {
|
| 517 |
+
"repo_id": "yytung/fxencoder-plusplus",
|
| 518 |
+
"filename": "fxenc_plusplus_default.pt",
|
| 519 |
+
"description": "Default model",
|
| 520 |
+
},
|
| 521 |
+
# "musdb": {
|
| 522 |
+
# "repo_id": "yytung/fxencoder-plusplus",
|
| 523 |
+
# "filename": "fxenc_plusplus_musdb.pt",
|
| 524 |
+
# "description": "Fx-Encoder++ trained on musdb",
|
| 525 |
+
# },
|
| 526 |
+
# "medleydb": {
|
| 527 |
+
# "repo_id": "yytung/fxencoder-plusplus",
|
| 528 |
+
# "filename": "fxenc_plusplus_medleydb.pt",
|
| 529 |
+
# "description": "Fx-Encoder++ trained on medleydb",
|
| 530 |
+
# },
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
def get_model_path(model_name="default", cache_dir=None, force_download=False):
|
| 534 |
+
"""
|
| 535 |
+
Download or retrieve the path to a pretrained model.
|
| 536 |
+
|
| 537 |
+
Args:
|
| 538 |
+
model_name: Name of the model variant ('default', 'musdb', 'medleydb')
|
| 539 |
+
cache_dir: Custom cache directory. If None, uses ~/.cache/fxencoder_plusplus
|
| 540 |
+
force_download: Force re-download even if file exists
|
| 541 |
+
|
| 542 |
+
Returns:
|
| 543 |
+
Path to the model file
|
| 544 |
+
"""
|
| 545 |
+
if model_name not in MODEL_REGISTRY:
|
| 546 |
+
available = ", ".join(MODEL_REGISTRY.keys())
|
| 547 |
+
raise ValueError(f"Unknown model: {model_name}. Available models: {available}")
|
| 548 |
+
|
| 549 |
+
if cache_dir is None:
|
| 550 |
+
cache_dir = Path.home() / ".cache" / "fxencoder_plusplus"
|
| 551 |
+
else:
|
| 552 |
+
cache_dir = Path(cache_dir)
|
| 553 |
+
|
| 554 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 555 |
+
|
| 556 |
+
model_info = MODEL_REGISTRY[model_name]
|
| 557 |
+
model_path = cache_dir / model_info["filename"]
|
| 558 |
+
|
| 559 |
+
# Check if already downloaded
|
| 560 |
+
if model_path.exists() and not force_download:
|
| 561 |
+
print(f"Using cached model: {model_path}")
|
| 562 |
+
return str(model_path)
|
| 563 |
+
|
| 564 |
+
print(f"Description: {model_info['description']}")
|
| 565 |
+
|
| 566 |
+
# Download from Hugging Face
|
| 567 |
+
downloaded_path = hf_hub_download(
|
| 568 |
+
repo_id=model_info["repo_id"],
|
| 569 |
+
filename=model_info["filename"],
|
| 570 |
+
cache_dir=str(cache_dir),
|
| 571 |
+
force_download=force_download
|
| 572 |
+
)
|
| 573 |
+
|
| 574 |
+
print(f"Model downloaded successfully to: {downloaded_path}")
|
| 575 |
+
return downloaded_path
|
| 576 |
+
|
| 577 |
+
def list_available_models():
|
| 578 |
+
"""List all available pretrained models."""
|
| 579 |
+
print("Available FxEncoder++ models:")
|
| 580 |
+
print("-" * 50)
|
| 581 |
+
for name, info in MODEL_REGISTRY.items():
|
| 582 |
+
print(f" {name}:")
|
| 583 |
+
print(f" - Description: {info['description']}")
|
| 584 |
+
print("-" * 50)
|
| 585 |
+
|
| 586 |
+
def load_model(model_name="default", model_path=None, device="cuda", auto_download=True, cache_dir=None):
|
| 587 |
+
"""
|
| 588 |
+
Load FxEncoderPlusPlus model.
|
| 589 |
+
|
| 590 |
+
Args:
|
| 591 |
+
model_name: Name of pretrained model ('default', 'musdb', 'medleydb')
|
| 592 |
+
model_path: Custom checkpoint path. If provided, ignores model_name
|
| 593 |
+
device: Device to load model on ('cuda' or 'cpu')
|
| 594 |
+
auto_download: Automatically download if model not found
|
| 595 |
+
cache_dir: Custom cache directory for downloaded models
|
| 596 |
+
|
| 597 |
+
Returns:
|
| 598 |
+
Loaded FxEncoderPlusPlus model
|
| 599 |
+
|
| 600 |
+
Examples:
|
| 601 |
+
# Load default base model
|
| 602 |
+
model = load_model()
|
| 603 |
+
|
| 604 |
+
# Load musdb model
|
| 605 |
+
model = load_model(model_name="musdb")
|
| 606 |
+
|
| 607 |
+
# Load medleydb model
|
| 608 |
+
model = load_model(model_name="medleydb")
|
| 609 |
+
|
| 610 |
+
# Load custom checkpoint
|
| 611 |
+
model = load_model(model_path="/path/to/custom.pt")
|
| 612 |
+
|
| 613 |
+
# List available models
|
| 614 |
+
list_available_models()
|
| 615 |
+
"""
|
| 616 |
+
# Handle device
|
| 617 |
+
if device == "cuda" and not torch.cuda.is_available():
|
| 618 |
+
print("CUDA not available, using CPU")
|
| 619 |
+
device = "cpu"
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
# Determine model path
|
| 623 |
+
if model_path is None:
|
| 624 |
+
if auto_download:
|
| 625 |
+
model_path = get_model_path(model_name, cache_dir=cache_dir)
|
| 626 |
+
else:
|
| 627 |
+
raise ValueError("model_path is None and auto_download is False")
|
| 628 |
+
|
| 629 |
+
# Create model instance with specified device
|
| 630 |
+
model = FxEncoderPlusPlus(
|
| 631 |
+
embed_dim=2048,
|
| 632 |
+
audio_clap_module=True,
|
| 633 |
+
text_clap_module=True,
|
| 634 |
+
extractor_module=True,
|
| 635 |
+
device=device
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
# Load checkpoint
|
| 639 |
+
checkpoint = torch.load(model_path, map_location=device, weights_only=False)
|
| 640 |
+
|
| 641 |
+
if "epoch" in checkpoint:
|
| 642 |
+
# resuming a train checkpoint w/ epoch and optimizer state
|
| 643 |
+
start_epoch = checkpoint["epoch"]
|
| 644 |
+
sd = checkpoint["state_dict"]
|
| 645 |
+
if next(iter(sd.items()))[0].startswith("module"):
|
| 646 |
+
sd = {k[len("module."):]: v for k, v in sd.items()}
|
| 647 |
+
model.load_state_dict(sd)
|
| 648 |
+
print(f"Loaded checkpoint from epoch {start_epoch}")
|
| 649 |
+
else:
|
| 650 |
+
# loading a bare (model only) checkpoint for fine-tune or evaluation
|
| 651 |
+
model.load_state_dict(checkpoint)
|
| 652 |
+
print("Loaded model checkpoint")
|
| 653 |
+
|
| 654 |
+
model.to(device)
|
| 655 |
+
model.eval()
|
| 656 |
+
|
| 657 |
+
# Freeze parameters for inference
|
| 658 |
+
for param in model.parameters():
|
| 659 |
+
param.requires_grad = False
|
| 660 |
+
|
| 661 |
+
print(f"Model loaded successfully on {device}")
|
| 662 |
+
return model
|
| 663 |
+
|
| 664 |
+
# Convenience functions for specific models
|
| 665 |
+
def load_default_model(device="cuda", **kwargs):
|
| 666 |
+
"""Load the default FxEncoder++ model."""
|
| 667 |
+
return load_model(model_name="default", device=device, **kwargs)
|
| 668 |
+
|
| 669 |
+
# def load_musdb_model(device="cuda", **kwargs):
|
| 670 |
+
# """Load the musdb FxEncoder++ model."""
|
| 671 |
+
# return load_model(model_name="musdb", device=device, **kwargs)
|
| 672 |
+
|
| 673 |
+
# def load_medleydb_model(device="cuda", **kwargs):
|
| 674 |
+
# """Load the medleydb FxEncoder++ model."""
|
| 675 |
+
# return load_model(model_name="medleydb", device=device, **kwargs)
|
| 676 |
+
|
utils/laion_clap/__init__.py
ADDED
|
File without changes
|
utils/laion_clap/clap_module/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .factory import list_models, create_model, create_model_and_transforms, add_model_config
|
| 2 |
+
from .loss import ClipLoss, gather_features, LPLoss, lp_gather_features, LPMetrics
|
| 3 |
+
from .model import CLAP, CLAPTextCfg, CLAPVisionCfg, CLAPAudioCfp, convert_weights_to_fp16, trace_model
|
| 4 |
+
from .openai import load_openai_model, list_openai_models
|
| 5 |
+
from .pretrained import list_pretrained, list_pretrained_tag_models, list_pretrained_model_tags,\
|
| 6 |
+
get_pretrained_url, download_pretrained
|
| 7 |
+
from .tokenizer import SimpleTokenizer, tokenize
|
| 8 |
+
from .transform import image_transform
|
utils/laion_clap/clap_module/bert.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import BertTokenizer, BertModel
|
| 2 |
+
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
|
| 3 |
+
model = BertModel.from_pretrained("bert-base-uncased")
|
| 4 |
+
text = "Replace me by any text you'd like."
|
| 5 |
+
|
| 6 |
+
def bert_embeddings(text):
|
| 7 |
+
# text = "Replace me by any text you'd like."
|
| 8 |
+
encoded_input = tokenizer(text, return_tensors='pt')
|
| 9 |
+
output = model(**encoded_input)
|
| 10 |
+
return output
|
| 11 |
+
|
| 12 |
+
from transformers import RobertaTokenizer, RobertaModel
|
| 13 |
+
|
| 14 |
+
tokenizer = RobertaTokenizer.from_pretrained('roberta-base')
|
| 15 |
+
model = RobertaModel.from_pretrained('roberta-base')
|
| 16 |
+
text = "Replace me by any text you'd like."
|
| 17 |
+
def Roberta_embeddings(text):
|
| 18 |
+
# text = "Replace me by any text you'd like."
|
| 19 |
+
encoded_input = tokenizer(text, return_tensors='pt')
|
| 20 |
+
output = model(**encoded_input)
|
| 21 |
+
return output
|
| 22 |
+
|
| 23 |
+
from transformers import BartTokenizer, BartModel
|
| 24 |
+
|
| 25 |
+
tokenizer = BartTokenizer.from_pretrained('facebook/bart-base')
|
| 26 |
+
model = BartModel.from_pretrained('facebook/bart-base')
|
| 27 |
+
text = "Replace me by any text you'd like."
|
| 28 |
+
def bart_embeddings(text):
|
| 29 |
+
# text = "Replace me by any text you'd like."
|
| 30 |
+
encoded_input = tokenizer(text, return_tensors='pt')
|
| 31 |
+
output = model(**encoded_input)
|
| 32 |
+
return output
|
utils/laion_clap/clap_module/bpe_simple_vocab_16e6.txt.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:924691ac288e54409236115652ad4aa250f48203de50a9e4722a6ecd48d6804a
|
| 3 |
+
size 1356917
|