Download python/ls_eend_sdk/feature.py from AXERA-TECH/FS-EEND.AXERA: direct link, hf CLI and curl.
- Browser
- Download file 2.14 kB
-
https://huggingface.co/AXERA-TECH/FS-EEND.AXERA/resolve/main/python/ls_eend_sdk/feature.py
- Command line
-
hf download hf://AXERA-TECH/FS-EEND.AXERA/python/ls_eend_sdk/feature.py
-
curl -L -o feature.py https://huggingface.co/AXERA-TECH/FS-EEND.AXERA/resolve/main/python/ls_eend_sdk/feature.py
2.14 kB
| """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 # 0.1 s | |
| 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)) | |
| # Cumulative mean normalization: frame t is normalized by the mean of frames 0..t. | |
| 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 | |