Download src/data/audio_features.py from Frankenstein-Labs/Cortex-0: direct link, hf CLI and curl.
- Browser
- Download file 3.94 kB
-
https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/data/audio_features.py
- Command line
-
hf download hf://Frankenstein-Labs/Cortex-0/src/data/audio_features.py
-
curl -L -o audio_features.py https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/data/audio_features.py
3.94 kB
| """Extraction de mel-spectrogrammes **écrite à la main** (Phase 7). | |
| Aucune bibliothèque d'audio pré-entraînée : STFT via `torch.fft`, banc de filtres | |
| mel triangulaires construit à la main, puis log. Utilisé par l'encodeur audio. | |
| Justification : docs/architecture.md §6. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import torch | |
| def hann_window(n: int, device=None, dtype=torch.float32) -> torch.Tensor: | |
| """Fenêtre de Hann périodique (comme librosa/torch).""" | |
| t = torch.arange(n, device=device, dtype=dtype) | |
| return 0.5 - 0.5 * torch.cos(2 * math.pi * t / n) | |
| def frame_signal(x: torch.Tensor, frame_length: int, hop_length: int, | |
| center: bool = True) -> torch.Tensor: | |
| """Découpe ``(B, N)`` en trames ``(B, n_frames, frame_length)``. | |
| ``center=True`` ajoute un padding de réflexion de ``frame_length//2`` de chaque | |
| côté (comme torch.stft), ce qui aligne les trames sur les échantillons. | |
| """ | |
| if x.dim() == 1: | |
| x = x.unsqueeze(0) | |
| if center: | |
| pad = frame_length // 2 | |
| x = torch.nn.functional.pad(x.unsqueeze(1), (pad, pad), mode="reflect").squeeze(1) | |
| n_frames = 1 + (x.size(1) - frame_length) // hop_length | |
| idx = (torch.arange(n_frames, device=x.device) * hop_length).unsqueeze(1) | |
| idx = idx + torch.arange(frame_length, device=x.device).unsqueeze(0) | |
| return x[:, idx] # (B, n_frames, frame_length) | |
| def stft_magnitude(x: torch.Tensor, n_fft: int, hop_length: int, | |
| win_length: int | None = None) -> torch.Tensor: | |
| """Magnitude de la STFT ``(B, n_frames, n_fft//2 + 1)``.""" | |
| win_length = win_length or n_fft | |
| window = hann_window(win_length, device=x.device, dtype=x.dtype) | |
| frames = frame_signal(x, win_length, hop_length) * window | |
| spec = torch.fft.rfft(frames, n=n_fft, dim=-1) | |
| return spec.abs() | |
| def hz_to_mel(f: torch.Tensor | float) -> torch.Tensor: | |
| """Échelle mel (formule de O'Shaughnessy).""" | |
| f = torch.as_tensor(f, dtype=torch.float64) | |
| return 2595.0 * torch.log10(1.0 + f / 700.0) | |
| def mel_to_hz(m: torch.Tensor | float) -> torch.Tensor: | |
| m = torch.as_tensor(m, dtype=torch.float64) | |
| return 700.0 * (10.0 ** (m / 2595.0) - 1.0) | |
| def mel_filterbank(sr: int, n_fft: int, n_mels: int, fmin: float = 0.0, | |
| fmax: float | None = None) -> torch.Tensor: | |
| """Banc de filtres mel triangulaires ``(n_mels, n_fft//2 + 1)``.""" | |
| fmax = fmax if fmax is not None else sr / 2 | |
| n_freqs = n_fft // 2 + 1 | |
| mel_pts = torch.linspace(float(hz_to_mel(fmin)), float(hz_to_mel(fmax)), n_mels + 2) | |
| hz_pts = mel_to_hz(mel_pts).to(torch.float32) # (n_mels+2,) | |
| fft_freqs = torch.linspace(0, sr / 2, n_freqs) # (n_freqs,) | |
| fb = torch.zeros(n_mels, n_freqs) | |
| for i in range(n_mels): | |
| left, center, right = hz_pts[i], hz_pts[i + 1], hz_pts[i + 2] | |
| rising = (fft_freqs - left) / max(float(center - left), 1e-8) | |
| falling = (right - fft_freqs) / max(float(right - center), 1e-8) | |
| fb[i] = torch.clamp(torch.minimum(rising, falling), min=0.0) | |
| return fb | |
| class LogMelSpectrogram(torch.nn.Module): | |
| """Transforme une forme d'onde ``(B, N)`` en log-mel ``(B, 1, n_mels, n_frames)``.""" | |
| def __init__(self, sr: int = 16000, n_fft: int = 256, hop_length: int = 64, | |
| n_mels: int = 64, fmin: float = 0.0, fmax: float | None = None): | |
| super().__init__() | |
| self.sr, self.n_fft, self.hop_length = sr, n_fft, hop_length | |
| self.register_buffer("fb", mel_filterbank(sr, n_fft, n_mels, fmin, fmax)) | |
| def forward(self, waveform: torch.Tensor) -> torch.Tensor: | |
| mag = stft_magnitude(waveform, self.n_fft, self.hop_length) # (B, T, F) | |
| mel = torch.einsum("btf,mf->btm", mag, self.fb) # (B, T, n_mels) | |
| log_mel = torch.log(mel.clamp(min=1e-6)) | |
| return log_mel.transpose(1, 2).unsqueeze(1) # (B, 1, n_mels, T) | |