audio duplo real: variante lenta (TTS rate -30%) para todas as 487 frases + player tartaruga/coelho

henrique 9 hours ago
parent 42e00ab5cf
commit d3e3c0b6c7

@ -124,6 +124,32 @@ cursoitaliano/
└── package.json # Monorepo root (npm workspaces)
```
## Dois ritmos de áudio 🐢🐇
Cada frase do catálogo possui **dois arquivos de áudio** gerados com velocidades de fala **reais** (não playbackRate):
- `<key>.mp3` — velocidade natural (padrão do TTS)
- `<key>--slow.mp3` — fala desacelerada em **-30%** via `edge-tts --rate=-30%`
### Por que rate real e não playback?
Crianças aprendem ouvindo devagar **de verdade**: a prosódia, entonação e pausas naturais são preservadas quando o TTS gera a fala mais lenta desde a síntese. Playback acelerado/desacelerado distorce formantes e perde a cadência natural da língua.
### Regerar áudios
```bash
# Gerar apenas variantes lentas (idempotente, pula >500 bytes)
node tools/gen-audio.mjs --only slow
# Gerar apenas variantes rápidas/naturais
node tools/gen-audio.mjs --only fast
# Gerar ambas (default)
node tools/gen-audio.mjs
```
No player da app, dois botões lado a lado: 🐢 lento e 🐇 naturale. Se o arquivo lento falhar (404), fallback silencioso para o natural.
## Modelos Utilizados
| Modelo | Uso | Variável |

@ -116,36 +116,73 @@ function HomeView({ onSelectMission }: { onSelectMission: (id: string) => void }
);
}
// ─── Player de áudio ───
// ─── Player de áudio com duas velocidades ───
function AudioPlayer({ audioKey, label }: { audioKey: string; label?: string }) {
const [playing, setPlaying] = useState(false);
const audioRef = useRef<HTMLAudioElement | null>(null);
const [playingSlow, setPlayingSlow] = useState(false);
const [playingFast, setPlayingFast] = useState(false);
const audioSlowRef = useRef<HTMLAudioElement | null>(null);
const audioFastRef = useRef<HTMLAudioElement | null>(null);
const slowFallbackDone = useRef(false);
const playSlow = useCallback(() => {
if (audioSlowRef.current) {
audioSlowRef.current.currentTime = 0;
audioSlowRef.current.play().catch(() => {});
}
}, []);
const playFast = useCallback(() => {
if (audioFastRef.current) {
audioFastRef.current.currentTime = 0;
audioFastRef.current.play().catch(() => {});
}
}, []);
const play = useCallback(() => {
if (audioRef.current) {
audioRef.current.currentTime = 0;
audioRef.current.play().catch(() => {});
// Fallback: se slow falhar (404), redireciona para fast uma vez
const handleSlowError = useCallback(() => {
if (!slowFallbackDone.current && audioFastRef.current) {
slowFallbackDone.current = true;
setPlayingSlow(false);
audioFastRef.current.currentTime = 0;
audioFastRef.current.play().catch(() => {});
} else {
setPlayingSlow(false);
}
}, []);
return (
<>
<div className="audio-pair">
<button
className="btn-play"
onClick={play}
title={label ?? "Tocar áudio"}
aria-label={label ?? "Tocar áudio"}
className={`btn-play btn-slow ${playingSlow ? "playing" : ""}`}
onClick={playSlow}
title={label ? `${label} (lento)` : "Tocar áudio lento"}
aria-label={label ? `${label} (lento)` : "Tocar áudio lento"}
>
{playing ? "⏸" : "▶"}
🐢
</button>
<button
className={`btn-play btn-fast ${playingFast ? "playing" : ""}`}
onClick={playFast}
title={label ? `${label} (naturale)` : "Tocar áudio naturale"}
aria-label={label ? `${label} (naturale)` : "Tocar áudio naturale"}
>
🐇
</button>
<audio
ref={audioRef}
ref={audioSlowRef}
src={`/audio/${audioKey}--slow.mp3`}
onPlay={() => setPlayingSlow(true)}
onEnded={() => setPlayingSlow(false)}
onError={handleSlowError}
/>
<audio
ref={audioFastRef}
src={`/audio/${audioKey}.mp3`}
onPlay={() => setPlaying(true)}
onEnded={() => setPlaying(false)}
onError={() => setPlaying(false)}
onPlay={() => setPlayingFast(true)}
onEnded={() => setPlayingFast(false)}
onError={() => setPlayingFast(false)}
/>
</>
</div>
);
}

@ -148,6 +148,13 @@ p {
50% { transform: scale(1.08); }
}
.audio-pair {
display: flex;
gap: 8px;
align-items: center;
flex-shrink: 0;
}
.btn-play {
width: 44px;
height: 44px;
@ -156,6 +163,21 @@ p {
color: white;
font-size: 1.2rem;
flex-shrink: 0;
transition: transform 0.1s ease, background 0.2s ease;
}
.btn-play.playing {
animation: pulse 1s infinite;
}
.btn-slow {
background: #6c757d;
font-size: 1.1rem;
}
.btn-fast {
background: var(--verde);
font-size: 1.1rem;
}
/* Trail badges */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save