| """8 kHz log-mel frontend for LS-EEND (``logmel23_cummn``). |
| |
| Mirrors ``FS-EEND/LS-EEND/datasets/feature.py`` exactly: 23 mel bins, cumulative |
| mean normalization, +-7 frame splicing, then 10x subsampling. One output frame |
| therefore covers 0.1 s of audio. |
| """ |
| from __future__ import annotations |
|
|
| import numpy as np |
|
|
| SAMPLE_RATE = 8000 |
| N_MELS = 23 |
| CONTEXT = 7 |
| SUBSAMPLING = 10 |
| FRAME_SHIFT = 80 |
| WIN_LENGTH = 200 |
| N_FFT = 1024 |
| FEATURE_DIM = (2 * CONTEXT + 1) * N_MELS |
| FRAME_SEC = FRAME_SHIFT * SUBSAMPLING / SAMPLE_RATE |
|
|
|
|
| def load_audio(wav_path): |
| """Read a wav as mono float32 at 8 kHz. Returns (audio, duration_seconds).""" |
| import soundfile as sf |
|
|
| audio, sr = sf.read(str(wav_path), dtype='float32', always_2d=False) |
| if audio.ndim > 1: |
| audio = audio[:, 0] |
| duration = len(audio) / sr |
| if sr != SAMPLE_RATE: |
| import librosa |
| audio = librosa.resample(audio, orig_sr=sr, target_sr=SAMPLE_RATE) |
| return audio, duration |
|
|
|
|
| def extract_features(audio): |
| """audio (float32, 8 kHz) -> features (T, 345) float32.""" |
| import librosa |
|
|
| spec = librosa.stft(audio, n_fft=N_FFT, win_length=WIN_LENGTH, hop_length=FRAME_SHIFT).T |
| if len(audio) % FRAME_SHIFT == 0: |
| spec = spec[:-1] |
| mag = np.abs(spec) |
| mel_fb = librosa.filters.mel(sr=SAMPLE_RATE, n_fft=2 * (mag.shape[1] - 1), n_mels=N_MELS) |
| logmel = np.log10(np.maximum(np.dot(mag ** 2, mel_fb.T), 1e-10)) |
|
|
| |
| cum = np.cumsum(logmel, axis=0) |
| idx = np.arange(1, logmel.shape[0] + 1, dtype=np.float32) |
| logmel = logmel - cum / idx[:, None] |
|
|
| padded = np.pad(logmel, ((CONTEXT, CONTEXT), (0, 0)), mode='constant') |
| n = logmel.shape[0] |
| spliced = np.lib.stride_tricks.as_strided( |
| padded, (n, FEATURE_DIM), (padded.itemsize * N_MELS, padded.itemsize) |
| ).copy() |
| return spliced[::SUBSAMPLING].astype(np.float32) |
|
|
|
|
| def wav_to_features(wav_path): |
| """Convenience wrapper: wav path -> (features (T,345), duration_seconds).""" |
| audio, duration = load_audio(wav_path) |
| return extract_features(audio), duration |
|
|