File size: 3,371 Bytes
579ca46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Numpy-only audio helpers for the CuteTTS AX650 runtime.

WAV I/O, polyphase resampling and the exact log-mel front end used by the
speaker encoder (matches torch.stft(center=True) + mel matmul + log).
"""

import math
import wave

import numpy as np


def read_wav(path):
    with wave.open(path, "rb") as w:
        sr = w.getframerate()
        n_ch = w.getnchannels()
        sw = w.getsampwidth()
        raw = w.readframes(w.getnframes())
    if sw == 2:
        x = np.frombuffer(raw, dtype="<i2").astype(np.float32) / 32768.0
    elif sw == 3:
        b = np.frombuffer(raw, dtype=np.uint8).reshape(-1, 3).astype(np.int32)
        v = b[:, 0] | (b[:, 1] << 8) | (b[:, 2] << 16)
        v = np.where(v >= 1 << 23, v - (1 << 24), v)
        x = v.astype(np.float32) / float(1 << 23)
    elif sw == 4:
        x = np.frombuffer(raw, dtype="<i4").astype(np.float32) / float(1 << 31)
    else:
        raise ValueError(f"unsupported sample width: {sw}")
    if n_ch > 1:
        x = x.reshape(-1, n_ch).mean(axis=1)
    return np.ascontiguousarray(x, dtype=np.float32), sr


def write_wav(path, x, sr):
    x = np.clip(np.asarray(x, dtype=np.float32), -1.0, 1.0)
    pcm = (x * 32767.0).astype("<i2")
    with wave.open(path, "wb") as w:
        w.setnchannels(1)
        w.setsampwidth(2)
        w.setframerate(int(sr))
        w.writeframes(pcm.tobytes())


def _firwin(cutoff, numtaps, beta=5.0):
    n = np.arange(numtaps)
    alpha = (numtaps - 1) / 2.0
    m = n - alpha
    h = cutoff * np.sinc(cutoff * m)
    h *= np.kaiser(numtaps, beta)
    h /= h.sum()
    return h


def resample_poly(x, up, down):
    x = np.asarray(x, dtype=np.float64)
    g = math.gcd(int(up), int(down))
    up, down = int(up) // g, int(down) // g
    if up == down:
        return x.astype(np.float32)
    max_rate = max(up, down)
    half = 10 * max_rate
    h = (_firwin(1.0 / max_rate, 2 * half + 1) * up).astype(np.float64)
    hl = len(h)
    n_out = int(math.ceil(len(x) * up / down))
    base = half + np.arange(n_out, dtype=np.int64) * down
    m0 = (base - hl + 1 + up - 1) // up
    m0 = np.maximum(m0, 0)
    acc = np.zeros(n_out, dtype=np.float64)
    for t in range(hl // up + 2):
        m = m0 + t
        j = base - m * up
        valid = (j >= 0) & (j < hl) & (m < len(x))
        acc[valid] += h[j[valid]] * x[m[valid]]
    return acc.astype(np.float32)


def resample_to(x, sr_in, sr_out):
    if int(sr_in) == int(sr_out):
        return np.asarray(x, dtype=np.float32).copy()
    return resample_poly(x, int(sr_out), int(sr_in))


def logmel(wav, mel_filter, window, n_fft=512, hop=160, win_length=400, eps=1e-6):
    """wav: mono float32 -> [n_mels, frames] float32 (torch-compatible)."""
    wav = np.asarray(wav, dtype=np.float32).reshape(-1)
    pad = n_fft // 2
    if len(wav) <= pad:
        wav = np.pad(wav, (0, pad + 1 - len(wav)))
    x = np.pad(wav, (pad, pad), mode="reflect")
    frames = np.lib.stride_tricks.sliding_window_view(x, n_fft)[::hop]
    n = 1 + len(wav) // hop
    frames = frames[:n]
    w = np.zeros(n_fft, dtype=np.float32)
    s = (n_fft - win_length) // 2
    w[s:s + win_length] = window
    spec = np.fft.rfft(frames * w[None, :], n=n_fft, axis=-1)
    power = (spec.real ** 2 + spec.imag ** 2).T.astype(np.float32)
    mel = mel_filter.astype(np.float32) @ power
    return np.log(np.maximum(mel, eps)).astype(np.float32)