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

main
henrique 6 hours ago
parent 42e00ab5cf
commit 67ecabcc0c

@ -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 play = useCallback(() => {
if (audioRef.current) {
audioRef.current.currentTime = 0;
audioRef.current.play().catch(() => {});
const playFast = useCallback(() => {
if (audioFastRef.current) {
audioFastRef.current.currentTime = 0;
audioFastRef.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={audioSlowRef}
src={`/audio/${audioKey}--slow.mp3`}
onPlay={() => setPlayingSlow(true)}
onEnded={() => setPlayingSlow(false)}
onError={handleSlowError}
/>
<audio
ref={audioRef}
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