Vansh Chugh commited on
Commit
1a3ce68
·
1 Parent(s): 84c9833

initial deploy

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +12 -0
  2. README.md +17 -4
  3. app.py +209 -0
  4. conf/conf_FxGenerator_Public.yaml +71 -0
  5. conf/conf_FxProcessor_Public.yaml +40 -0
  6. diff_params/__init__.py +0 -0
  7. diff_params/edm_multitrack_embs.py +432 -0
  8. inference/__init__.py +0 -0
  9. inference/inference.py +517 -0
  10. inference/sampler.py +29 -0
  11. inference/sampler_euler_heun_multitrack.py +302 -0
  12. inference/validator_FxProcessor.py +436 -0
  13. networks/MLP_CLAP_regressor.py +23 -0
  14. networks/__init__.py +0 -0
  15. networks/blackbox_TCN.py +346 -0
  16. networks/dit_multitrack.py +488 -0
  17. networks/transformer.py +932 -0
  18. requirements.txt +15 -0
  19. utils/MSS_loss.py +260 -0
  20. utils/__init__.py +0 -0
  21. utils/collators.py +262 -0
  22. utils/common_audioeffects.py +1729 -0
  23. utils/data_utils.py +190 -0
  24. utils/dnnlib/__init__.py +8 -0
  25. utils/dnnlib/util.py +491 -0
  26. utils/evaluation/KAD_evaluate.py +307 -0
  27. utils/evaluation/dist_metrics.py +971 -0
  28. utils/evaluation/dist_metrics_multitrack.py +619 -0
  29. utils/evaluation/pairwise_metrics.py +958 -0
  30. utils/feature_extractors/AF_features_embedding.py +166 -0
  31. utils/feature_extractors/__init__.py +0 -0
  32. utils/feature_extractors/audio_features.py +76 -0
  33. utils/feature_extractors/dsp_features.py +141 -0
  34. utils/feature_extractors/fx_encoder_plus_plus.py +82 -0
  35. utils/feature_extractors/load_features.py +260 -0
  36. utils/feature_extractors/networks/__init__.py +2 -0
  37. utils/feature_extractors/networks/architectures.py +163 -0
  38. utils/feature_extractors/networks/configs.yaml +14 -0
  39. utils/feature_extractors/networks/network_utils.py +254 -0
  40. utils/feature_extractors/networks/pytorch_utils.py +256 -0
  41. utils/fx_normalization/__init__.py +0 -0
  42. utils/fx_normalization/features.npy +3 -0
  43. utils/fx_normalization/fxnorm.py +154 -0
  44. utils/fx_normalization/fxnorm_v2_public.py +142 -0
  45. utils/fxencoder_plusplus/__init__.py +12 -0
  46. utils/fxencoder_plusplus/model.py +676 -0
  47. utils/laion_clap/__init__.py +0 -0
  48. utils/laion_clap/clap_module/__init__.py +8 -0
  49. utils/laion_clap/clap_module/bert.py +32 -0
  50. 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: 6.19.0
8
- python_version: '3.13'
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
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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