Download src/data/toy_audio.py from Frankenstein-Labs/Cortex-0: direct link, hf CLI and curl.
- Browser
- Download file 3.08 kB
-
https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/data/toy_audio.py
- Command line
-
hf download hf://Frankenstein-Labs/Cortex-0/src/data/toy_audio.py
-
curl -L -o toy_audio.py https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/data/toy_audio.py
3.08 kB
| """Générateur d'audio synthétique jouet (Phase 7). | |
| Faute de téléchargement audio dans l'environnement de dev, on synthétise des | |
| **voyelles** par un modèle simple de formants (somme de sinus au F0 et aux | |
| fréquences de formants F1/F2), avec deux hauteurs (grave/aigu). Cela donne un jeu | |
| de 10 classes (5 voyelles × 2 hauteurs), suffisant pour valider un encodeur audio | |
| from scratch et une transcription de mots simples. | |
| Aucun modèle pré-entraîné : c'est du signal pur. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import numpy as np | |
| #: Voyelles françaises et leurs deux premiers formants (Hz). | |
| VOWELS: dict[str, tuple[float, float]] = { | |
| "a": (800.0, 1200.0), | |
| "e": (500.0, 1800.0), | |
| "i": (300.0, 2300.0), | |
| "o": (500.0, 900.0), | |
| "u": (300.0, 700.0), | |
| } | |
| #: Hauteurs : grave / aigu (Hz du fondamental). | |
| PITCHES: dict[str, float] = {"grave": 100.0, "aigu": 200.0} | |
| SR = 16000 | |
| N_SAMPLES = 6144 # 0.384 s -> 96 trames avec hop 64 | |
| class AudioItem: | |
| waveform: np.ndarray # (N_SAMPLES,) float32 | |
| vowel: str | |
| pitch: str | |
| def caption(self) -> str: | |
| return f"voyelle {self.vowel} {self.pitch}" | |
| def synth_vowel(vowel: str, pitch: str, sr: int = SR, n: int = N_SAMPLES, | |
| rng: np.random.Generator | None = None) -> np.ndarray: | |
| """Synthétise une voyelle : harmoniques du F0 pondérées par les formants.""" | |
| rng = rng or np.random.default_rng(0) | |
| f0 = PITCHES[pitch] * float(rng.uniform(0.95, 1.05)) | |
| f1, f2 = VOWELS[vowel] | |
| f1 *= float(rng.uniform(0.95, 1.05)) | |
| f2 *= float(rng.uniform(0.95, 1.05)) | |
| t = np.arange(n) / sr | |
| sig = np.zeros(n, dtype=np.float64) | |
| # Harmoniques du fondamental, amplitude = proximité aux formants. | |
| for h in range(1, 40): | |
| fh = h * f0 | |
| if fh > sr / 2: | |
| break | |
| amp = (np.exp(-((fh - f1) ** 2) / (2 * 120.0 ** 2)) | |
| + 0.7 * np.exp(-((fh - f2) ** 2) / (2 * 180.0 ** 2)) | |
| + 0.05) | |
| phase = float(rng.uniform(0, 2 * np.pi)) | |
| sig += amp * np.sin(2 * np.pi * fh * t + phase) | |
| sig += 0.02 * rng.standard_normal(n) # bruit de fond | |
| # Enveloppe douce (attaque/relâchement). | |
| env = np.ones(n) | |
| ramp = n // 10 | |
| env[:ramp] = np.linspace(0, 1, ramp) | |
| env[-ramp:] = np.linspace(1, 0, ramp) | |
| sig *= env | |
| sig /= (np.max(np.abs(sig)) + 1e-8) | |
| return (sig * float(rng.uniform(0.6, 1.0))).astype(np.float32) | |
| def generate_dataset(n: int, seed: int = 0) -> list[AudioItem]: | |
| """Génère ``n`` exemples répartis sur les 10 classes.""" | |
| rng = np.random.default_rng(seed) | |
| items: list[AudioItem] = [] | |
| classes = [(v, p) for v in VOWELS for p in PITCHES] | |
| for i in range(n): | |
| v, p = classes[i % len(classes)] | |
| items.append(AudioItem(synth_vowel(v, p, rng=rng), v, p)) | |
| rng.shuffle(items) | |
| return items | |
| def to_arrays(items: list[AudioItem]) -> np.ndarray: | |
| """Empile les formes d'onde en ``(N, N_SAMPLES)`` float32.""" | |
| return np.stack([it.waveform for it in items]).astype(np.float32) | |