From f7c4793dc218683de862d140edc10d690b375edb Mon Sep 17 00:00:00 2001 From: julio Date: Tue, 6 Oct 2026 02:57:32 -0300 Subject: [PATCH] feat(voz): voz de robo sintetizada no estilo dos robozinhos de cinema - voz-robo.js: silabas com deslize de tom, vibrato e filtro de formante ("uou", "ohh"), motorzinho de ruido filtrado e cliques; tudo gerado na hora com Web Audio, sem gravacoes nem arquivos de terceiros - um som proprio para cada gesto, incluindo maos (motor ao subir e descer) - balbucio de robo na resposta da conversa (mesma frase, mesmo som; sobe no fim quando e pergunta) e silencio dos gestos enquanto ele "fala" - tela Rosto: ouvir a voz no PC e botao "Testar fala"; bipes antigos ficam como reserva Co-Authored-By: Claude Opus 5.5 --- android/app/assets/carinha.html | 23 ++- android/app/assets/voz-robo.js | 157 +++++++++++++++++++++ android/build-apk.py | 2 +- carinha.html | 23 ++- deploy.ps1 | 2 +- kiosk-server.py | 2 +- voz-robo.js | 157 +++++++++++++++++++++ web/index.html | 1 + web/src/lib/componentes/RostoPrevia.svelte | 5 + web/src/telas/Rosto.svelte | 30 +++- web/vite.config.ts | 2 +- 11 files changed, 395 insertions(+), 9 deletions(-) create mode 100644 android/app/assets/voz-robo.js create mode 100644 voz-robo.js diff --git a/android/app/assets/carinha.html b/android/app/assets/carinha.html index 72b4ca5..09db809 100644 --- a/android/app/assets/carinha.html +++ b/android/app/assets/carinha.html @@ -26,6 +26,7 @@
+ + + diff --git a/web/src/lib/componentes/RostoPrevia.svelte b/web/src/lib/componentes/RostoPrevia.svelte index 1dde145..bafb06e 100644 --- a/web/src/lib/componentes/RostoPrevia.svelte +++ b/web/src/lib/componentes/RostoPrevia.svelte @@ -42,6 +42,11 @@ enviar({ tipo: 'ordem', ordem: texto }); } + /** Mostra uma legenda na faixa de baixo da prévia. */ + export function legenda(texto: string) { + enviar({ tipo: 'legenda', texto }); + } + /** Para a tela de reações: mostra uma receita sem mandar ao celular. */ export function previa(receita: unknown) { enviar({ tipo: 'previa-reacao', receita }); diff --git a/web/src/telas/Rosto.svelte b/web/src/telas/Rosto.svelte index 1eab648..8fca3d0 100644 --- a/web/src/telas/Rosto.svelte +++ b/web/src/telas/Rosto.svelte @@ -7,7 +7,23 @@ import Toggle from '../lib/componentes/Toggle.svelte'; import { estado } from '../lib/estado.svelte'; - let previa = $state<{ ordem: (t: string) => void }>(); + let previa = $state<{ ordem: (t: string) => void; legenda: (t: string) => void }>(); + // Ouvir a voz de robô aqui no PC (o clique na tecla libera o áudio do navegador). + let ouvirAqui = $state(true); + let audio: AudioContext | null = null; + function voz(): AudioContext | null { + const AC = window.AudioContext || (window as any).webkitAudioContext; + if (!ouvirAqui || !AC || !(window as any).VozRobo) return null; + audio ??= new AC(); + if (audio.state === 'suspended') audio.resume(); + return audio; + } + const FRASE = 'Oi! Eu sou o Carinha. Quer brincar comigo?'; + function testarFala() { + previa?.legenda(FRASE); + const c = voz(); + if (c) (window as any).VozRobo.balbuciar(c, FRASE, 0.5); + } // Por padrão as emoções só aparecem aqui na prévia; ligado, vão também para o celular. let noCelular = $state(false); @@ -22,6 +38,8 @@ function emocao(g: string) { if (noCelular) gesto(g); else previa?.ordem(g); + const c = voz(); + if (c) (window as any).VozRobo.tocar(c, g, 0.5); } const IDEIAS: { titulo: string; texto: string; feito: boolean; onde: string }[] = [ @@ -30,6 +48,7 @@ { titulo: 'Toque na tela', texto: 'Cutucar dá susto e risada; deslizar o dedo é carinho; tocar acorda.', feito: true, onde: 'rosto' }, { titulo: 'Mãos', texto: 'Mãozinhas na cor dos olhos: esconde o rosto, espia, acena e pensa com a mão no queixo.', feito: true, onde: 'rosto' }, { titulo: 'Faixa de baixo', texto: 'Legenda da conversa sendo digitada, pontinhos enquanto pensa, equalizador ao dançar e carga quando está no carregador.', feito: true, onde: 'rosto' }, + { titulo: 'Voz de robô', texto: 'Chilreios, “uou!”, “ohh” e um balbucio quando responde, sintetizados na hora; motorzinho quando as mãos se mexem.', feito: true, onde: 'rosto' }, { titulo: 'Fala pelo próprio celular', texto: 'A resposta da conversa sai no alto-falante do S10, com os olhos pulsando na voz.', feito: false, onde: 'app' }, { titulo: 'Ritmo do dia', texto: 'Boceja à noite, fica sonolento com bateria baixa, mostra um raio quando carrega.', feito: false, onde: 'rosto e servidor' }, { titulo: 'Contato visual', texto: 'Sustenta o olhar de quem a câmera vê e desvia quando fica tímido.', feito: false, onde: 'rosto' }, @@ -59,6 +78,15 @@ {/each} +
+ +
+ (ouvirAqui = v)} + />