File size: 9,798 Bytes
fb0011a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import numpy as np
import torch
import matplotlib.pyplot as plt
from munch import Munch
import os
import subprocess
from pynvml import nvmlInit, nvmlDeviceGetHandleByIndex, nvmlDeviceGetMemoryInfo

nv_init = False
def init_weights(m, mean=0.0, std=0.01):
    classname = m.__class__.__name__
    if classname.find("Conv") != -1:
        m.weight.data.normal_(mean, std)


def apply_weight_norm(m):
    classname = m.__class__.__name__
    if classname.find("Conv") != -1:
        weight_norm(m)


def get_padding(kernel_size, dilation=1):
    return int((kernel_size * dilation - dilation) / 2)

def print_gpu_vram(tag):
    if False:
        global nv_init
        if not nv_init:
            nvmlInit()
            nv_init = True
        handle = nvmlDeviceGetHandleByIndex(0)
        info = nvmlDeviceGetMemoryInfo(handle)
        print(f"{tag} - GPU memory occupied: {info.used//1024**2} MB.")


def maximum_path(neg_cent, mask):
    """Cython optimized version.
    neg_cent: [b, t_t, t_s]
    mask: [b, t_t, t_s]
    """
    device = neg_cent.device
    dtype = neg_cent.dtype
    neg_cent = np.ascontiguousarray(neg_cent.data.cpu().numpy().astype(np.float32))
    path = np.ascontiguousarray(np.zeros(neg_cent.shape, dtype=np.int32))

    t_t_max = np.ascontiguousarray(
        mask.sum(1)[:, 0].data.cpu().numpy().astype(np.int32)
    )
    t_s_max = np.ascontiguousarray(
        mask.sum(2)[:, 0].data.cpu().numpy().astype(np.int32)
    )
    maximum_path_c(path, neg_cent, t_t_max, t_s_max)
    return torch.from_numpy(path).to(device=device, dtype=dtype)


def get_data_path_list(path):
    result = []
    if os.path.isfile(path):
        with open(path, "r", encoding="utf-8", errors="ignore") as f:
            result = f.readlines()
    return result


def sequence_mask(length, max_length=None):
    if max_length is None:
        max_length = length.max()
    x = torch.arange(max_length, dtype=length.dtype, device=length.device)
    return x.unsqueeze(0) < length.unsqueeze(1)


def length_to_mask(lengths) -> torch.Tensor:
    mask = (
        torch.arange(lengths.max())
        .unsqueeze(0)
        .expand(lengths.shape[0], -1)
        .type_as(lengths)
    )
    mask = torch.gt(mask + 1, lengths.unsqueeze(1))
    return mask


# for norm consistency loss
def log_norm(x, mean=-4, std=4, dim=2):
    """
    normalized log mel -> mel -> norm -> log(norm)
    """
    # x = torch.log(torch.exp(x * std + mean).norm(dim=dim))
    x = (torch.exp(x * std + mean) ** 0.33).sum(dim=dim)
    return x


def plot_spectrogram_to_figure(
    spectrogram,
    title="Spectrogram",
    figsize=(12, 5),  # Increased width for better time resolution view
    dpi=150,  # Increased DPI for higher resolution image
    interpolation="bilinear",  # Smoother interpolation
    cmap="viridis",  # Default colormap, can change to 'magma', 'inferno', etc.
):
    """Converts a spectrogram tensor/numpy array to a matplotlib figure with improved quality."""
    plt.switch_backend("agg")  # Use non-interactive backend

    # Ensure input is a numpy array on CPU
    if isinstance(spectrogram, torch.Tensor):
        spectrogram_np = spectrogram.detach().cpu().numpy()
    elif isinstance(spectrogram, np.ndarray):
        spectrogram_np = spectrogram
    else:
        raise TypeError("Input spectrogram must be a torch.Tensor or numpy.ndarray")

    # Handle potential extra dimensions (e.g., channel dim)
    if spectrogram_np.ndim > 2:
        if spectrogram_np.shape[0] == 1:  # Remove channel dim if it's 1
            spectrogram_np = spectrogram_np.squeeze(0)
        else:
            # If multiple channels, you might want to plot only the first
            # or handle it differently (e.g., separate plots)
            spectrogram_np = spectrogram_np[0, :, :]  # Plot only the first channel
            # Or raise an error/warning:
            # raise ValueError(f"Spectrogram has unexpected shape: {spectrogram_np.shape}")

    fig, ax = plt.subplots(figsize=figsize, dpi=dpi)  # Apply figsize and dpi

    # Ensure valid interpolation string
    valid_interpolations = [
        None,
        "none",
        "nearest",
        "bilinear",
        "bicubic",
        "spline16",
        "spline36",
        "hanning",
        "hamming",
        "hermite",
        "kaiser",
        "quadric",
        "catrom",
        "gaussian",
        "bessel",
        "mitchell",
        "sinc",
        "lanczos",
        "blackman",
    ]
    if interpolation not in valid_interpolations:
        print(f"Warning: Invalid interpolation '{interpolation}'. Using 'bilinear'.")
        interpolation = "bilinear"

    im = ax.imshow(
        spectrogram_np,
        aspect="auto",
        origin="lower",
        interpolation=interpolation,
        cmap=cmap,
    )  # Apply interpolation and cmap

    plt.colorbar(im, ax=ax)
    plt.xlabel("Frames")
    plt.ylabel("Mel Channels")  # More specific label
    plt.title(title)
    plt.tight_layout()
    # plt.close(fig) # Don't close here if returning the figure object
    return fig  # Return the figure object directly


def plot_mel_signed_difference_to_figure(
    mel_gt_normalized_np,  # Ground truth (already normalized log mel)
    mel_pred_log_np,  # Predicted (raw log mel)
    mean: float,  # Dataset mean used for normalization
    std: float,  # Dataset std used for normalization
    title="Signed Mel Log Difference (GT - Pred)",  # Updated title
    figsize=(12, 5),
    dpi=150,
    cmap="vanimo",
    max_abs_diff_clip=None,  # Optional: Clip the color range e.g., 3.0
    static_max_abs=None,  # Optional: Static max abs value for consistent color range
):
    """Plots the signed difference between two mel spectrograms using a diverging colormap."""
    plt.switch_backend("agg")

    # Ensure shapes match by trimming to the minimum length
    min_len = min(mel_gt_normalized_np.shape[1], mel_pred_log_np.shape[1])
    mel_gt_trimmed = mel_gt_normalized_np[:, :min_len]
    mel_pred_log_trimmed = mel_pred_log_np[:, :min_len]

    # Normalize the predicted log mel
    mel_pred_normalized_np = (mel_pred_log_trimmed - mean) / std

    # Calculate SIGNED difference in the *normalized* log domain
    diff = mel_gt_trimmed - mel_pred_normalized_np

    fig, ax = plt.subplots(figsize=figsize, dpi=dpi)

    if static_max_abs is not None:
        # Use static max abs value for color limits
        vmin = -static_max_abs
        vmax = static_max_abs
    else:
        # Determine symmetric color limits centered at 0
        max_abs_val = np.max(np.abs(diff)) + 1e-9  # Add epsilon for stability
        if max_abs_diff_clip is not None:
            max_abs_val = min(
                max_abs_val, max_abs_diff_clip
            )  # Apply clipping if specified

        vmin = -max_abs_val
        vmax = max_abs_val

    im = ax.imshow(
        diff,
        aspect="auto",
        origin="lower",
        interpolation="none",
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
    )  # Use 'none' for raw diff

    plt.colorbar(
        im, ax=ax, label="Signed Normalized Log Difference (GT - Pred)"
    )  # Updated label
    plt.xlabel("Frames")
    plt.ylabel("Mel Channels")
    plt.title(title)
    plt.tight_layout()
    # plt.close(fig) # Don't close if returning fig
    return fig


def get_image(arrs):
    plt.switch_backend("agg")
    fig = plt.figure()
    ax = plt.gca()
    im = ax.imshow(arrs)
    plt.colorbar(im, ax=ax)
    return fig


def recursive_munch(d):
    if isinstance(d, dict):
        return Munch((k, recursive_munch(v)) for k, v in d.items())
    elif isinstance(d, list):
        return [recursive_munch(v) for v in d]
    else:
        return d


def get_git_commit_hash():
    try:
        commit_hash = (
            subprocess.check_output(["git", "rev-parse", "HEAD"])
            .strip()
            .decode("utf-8")
        )
        return commit_hash
    except subprocess.CalledProcessError as e:
        print("Error obtaining git commit hash:", e)
        return "unknown"


def get_git_diff():
    try:
        # Run the git diff command
        diff_output = subprocess.check_output(["git", "diff"]).decode("utf-8")
        return diff_output
    except subprocess.CalledProcessError as e:
        print("Error obtaining git diff:", e)
        return ""


def save_git_diff(out_dir):
    hash = get_git_commit_hash()
    diff = get_git_diff()
    diff_file = os.path.join(out_dir, "git_state.txt")
    with open(diff_file, "w") as f:
        f.write(f"Git commit hash: {hash}\n\n")
        f.write(diff)
    print(f"Git diff saved to {diff_file}")


def duration_to_alignment(duration: torch.Tensor) -> torch.Tensor:
    """Convert a sequence of duration values to an attention matrix.

    duration -- [t]ext length
    result -- [t]ext length x [a]udio length"""
    indices = torch.repeat_interleave(
        torch.arange(duration.shape[0], device=duration.device), duration.to(torch.int)
    )
    result = torch.zeros((duration.shape[0], indices.shape[0]), device=duration.device)
    result[indices, torch.arange(indices.shape[0])] = 1
    return result


def clamped_exp(x: torch.Tensor) -> torch.Tensor:
    x = x.clamp(-35, 35)
    return torch.exp(x)


def leaky_clamp(
    x_in: torch.Tensor, min_f: float, max_f: float, slope: float = 0.001
) -> torch.Tensor:
    x = x_in
    min_t = torch.full_like(x, min_f, device=x.device)
    max_t = torch.full_like(x, max_f, device=x.device)
    x = torch.maximum(x, min_t + slope * (x - min_t))
    x = torch.minimum(x, max_t + slope * (x - max_t))
    return x


class DecoderPrediction:
    def __init__(
        self,
        audio=None,
        x=None,
        y=None,
        magnitude=None,
    ):
        self.audio = audio
        self.x = x
        self.y = y
        self.magnitude = magnitude