File size: 3,056 Bytes
d8bfe4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Shared helpers for ESD eval pipeline."""
import os
import re
import string
from typing import List, Optional, Tuple

import numpy as np
import soundfile as sf
import torch
import torchaudio


def load_wav_mono_16k(path: str, target_sr: int = 16000) -> np.ndarray:
    """Load wav as mono float32 numpy at target_sr (default 16k)."""
    wav, sr = sf.read(path, always_2d=False)
    if wav.ndim > 1:
        wav = wav.mean(axis=-1)
    wav = wav.astype(np.float32)
    if sr != target_sr:
        t = torch.from_numpy(wav).unsqueeze(0)
        t = torchaudio.transforms.Resample(orig_freq=sr, new_freq=target_sr)(t)
        wav = t.squeeze(0).numpy().astype(np.float32)
    return wav


def slice_audio(audio: np.ndarray, max_sec: float = 8.0, sr: int = 16000) -> List[np.ndarray]:
    """Same chunking as InnerEmoEval/utils.py:slice_audio (kept compatible)."""
    n = len(audio)
    L = int(max_sec * sr)
    if n <= L:
        return [audio]
    parts = n // L + 1
    return [a for a in np.array_split(audio, parts, axis=0)]


def detect_lang_for_seg_text(txt: str) -> str:
    """Return 'zh' if any CJK char, else 'en'."""
    for ch in txt:
        if "\u4e00" <= ch <= "\u9fff":
            return "zh"
    return "en"


def detect_lang_for_speaker(spk_id: str) -> str:
    """ESD 0001-0010 -> zh, 0011-0020 -> en."""
    try:
        i = int(spk_id)
    except Exception:
        return "en"
    return "zh" if i <= 10 else "en"


def cosine_sim_np(a: np.ndarray, b: np.ndarray, eps: float = 1e-8) -> float:
    a = a.flatten().astype(np.float32)
    b = b.flatten().astype(np.float32)
    na = float(np.linalg.norm(a))
    nb = float(np.linalg.norm(b))
    if na < eps or nb < eps:
        return 0.0
    return float(np.dot(a, b) / (na * nb))


def cosine_sim_matrix(query: np.ndarray, db: np.ndarray, eps: float = 1e-8) -> np.ndarray:
    """query: (D,) or (1,D); db: (N,D). returns (N,)."""
    q = query.reshape(1, -1).astype(np.float32)
    qn = q / (np.linalg.norm(q, axis=1, keepdims=True) + eps)
    dn = db / (np.linalg.norm(db, axis=1, keepdims=True) + eps)
    return (dn @ qn.T).reshape(-1)


def minmax_norm(x: np.ndarray, eps: float = 1e-8) -> np.ndarray:
    mn, mx = float(np.min(x)), float(np.max(x))
    if mx - mn < eps:
        return np.zeros_like(x)
    return (x - mn) / (mx - mn)


# -------- text normalization for alignment / matching --------
_ALLOWED_PUNC_TRANS = str.maketrans({
    ",": ",", "。": ".", ";": ";", ":": ":", "?": "?", "!": "!",
    "(": "(", ")": ")", "【": "[", "】": "]",
    "“": '"', "”": '"', "‘": "'", "’": "'",
})


def normalize_text_basic(text: str) -> str:
    text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f\xa0]", "", text)
    text = text.translate(_ALLOWED_PUNC_TRANS)
    return text


def list_files(root: str, exts: Tuple[str, ...] = (".wav",)) -> List[str]:
    out = []
    for dp, _, fns in os.walk(root):
        for fn in fns:
            if fn.lower().endswith(exts):
                out.append(os.path.join(dp, fn))
    out.sort()
    return out