尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Pads de som que tocam no instante do toque, direto no navegador — incluindo o Safari do iPhone. No iPhone: abra esse endereço no Safari e faça Compartilhar → Adicionar à Tela de Início. Vira um app em tela cheia que funciona offline. O terminal mostra dois endereços: um para este Mac e um para o iPhone (precisa estar no mesmo Wi-Fi). Não há dependências nem build — é HTML, CSS e JS puros, então o que roda local é exatamente o que está publicado. Publicar uma mudança é só git push: o GitHub Pages serve a branch main.查看完整 README ↗
Pads de som que tocam no instante do toque, direto no navegador — incluindo o Safari do iPhone.
No ar em https://ruansantos01.github.io/SoundPad/
No iPhone: abra esse endereço no Safari e faça Compartilhar → Adicionar à Tela de Início. Vira um app em tela cheia que funciona offline.
node serve.mjs
O terminal mostra dois endereços: um para este Mac e um para o iPhone (precisa estar no mesmo Wi-Fi). Não há dependências nem build — é HTML, CSS e JS puros, então o que roda local é exatamente o que está publicado.
Publicar uma mudança é só git push: o GitHub Pages serve a branch main.
Eles ficam no IndexedDB, no navegador daquele aparelho. Três riscos, em ordem de probabilidade:
Contra os dois primeiros o app pede navigator.storage.persist() ao abrir. Medido no
Safari do iOS (simulador, iPhone 16 Pro):
| Como o app foi aberto | persist() |
|---|---|
| Aba normal do Safari | negado |
| Ícone na Tela de Início (standalone) | concedido |
Ou seja: adicionar à Tela de Início é o que protege os sons, não só conforto de tela cheia. Quando o pedido é negado e existem sons próprios carregados, o app mostra um aviso.
Contra o terceiro risco (limpeza manual, ou apagar o ícone) não há API que ajude — só uma cópia dos arquivos originais resolve.
O iOS abre toda página com o áudio suspenso, e suspende de novo toda vez que o app vai para segundo plano (trocar de app, ligação, bloquear a tela). O indicador ao lado do título fica cinza quando isso acontece.
O toque seguinte resolve, e o som desse toque sai — não é um toque desperdiçado. Isso
exigiu cuidado: medido no Safari do iOS, com o contexto suspenso o start() não toca nada
e o onended nunca dispara, ou seja, a voz é descartada em silêncio. Como resume()
é assíncrono, "retomar e tocar na linha seguinte" era uma corrida que às vezes perdia — era
essa a causa do som falhar de forma intermitente.
Hoje o trigger() dispara na hora quando o contexto está de pé, e quando não está,
destrava dentro do gesto e só solta a voz depois que o contexto volta.
Cada pasta é uma situação — uma live, um podcast, um set — com 16 pads próprios.
A barra logo abaixo do título lista as pastas; o + cria mais.
← e → trocam de pasta.disparo — toca até o fimsegurar — toca enquanto o dedo estiver no padloop — toca em ciclo; o segundo toque desliga1234 QWER ASDF ZXCV tocam os 16 pads.Para montar uma pasta de 16 sons sem repetir o mesmo caminho 16 vezes: Editar → toque no nome da pasta → Carregar vários sons… → selecione tudo de uma vez. Os arquivos entram nos pads vazios, na ordem, e o nome de cada pad vira o nome do arquivo. Pads que já têm som são preservados. Se sobrarem arquivos além dos 16 pads, o app diz quantos ficaram de fora; se algum arquivo não puder ser lido, ele diz qual.
A pasta "Bateria" vem com 16 sons sintetizados pelo próprio navegador — nenhum arquivo para baixar. Seus arquivos (MP3, M4A, WAV, AAC) ficam salvos no aparelho, no IndexedDB, e voltam sempre que você reabrir.
Web Audio API com o áudio já decodificado na memória. Um <audio>.play() no iOS
custa de dezenas a centenas de milissegundos; AudioBufferSourceNode.start() dispara no
próximo bloco de áudio.
Evento touchstart, não click. O iOS só emite click no fim do toque, e com
atraso. Medido aqui: 0,2 ms entre o toque e o disparo.
latencyHint: 'interactive' pede ao sistema o menor buffer de saída possível.
Não forçamos navigator.audioSession.type. A tentação é usar 'playback' para
tocar com a chavinha de silencioso ligada. Medido no Safari do iOS, com dois contextos
novos e idênticos:
audioSession | estado | tocou |
|---|---|---|
auto (padrão) | running | sim, imediato |
playback | interrupted | não |
O buffer de saída é o mesmo nos dois (2,7 ms): 'playback' simplesmente derruba a
sessão. Preço de ficar no padrão: com o silencioso ligado o iOS pode calar o som —
se um pad não sair, confira a chavinha lateral.
O limitador é um WaveShaper, não um DynamicsCompressor. Medido nos dois motores, o compressor custa 6 ms de atraso na entrada e 6 ms de cauda depois que a fonte para — ele atrasa tudo para antecipar picos. O WaveShaper satura amostra a amostra: zero atraso, zero cauda, e ainda segura pico de 2,5 em 0,93.
O que sobra é a latência do próprio hardware de saída. Entre em Editar para ver o número
do seu aparelho — o app mostra saída ~N ms. Alto-falante ou fone com fio ficam na casa
de 10–40 ms; fone Bluetooth costuma somar 150–300 ms, e nenhuma linha de código muda isso.
index.html | estrutura da tela |
app.js | motor de áudio, pastas, pads, toque, persistência |
styles.css | visual |
sw.js + manifest.webmanifest | instalação na tela de início e uso offline |
serve.mjs | servidor local para testar no iPhone |
vercel.json | cabeçalhos do deploy na Vercel (ver abaixo) |
O app é estático: qualquer host serve. Hoje está no GitHub Pages, publicado a cada
git push na main.
O vercel.json existe para três coisas que quebram um PWA se ficarem no padrão:
sw.js sem cache. Se o service worker vier de cache, uma versão antiga do app fica
presa no aparelho e nenhum deploy novo chega.manifest.webmanifest com application/manifest+json. Sem o tipo certo o iOS
ignora o manifest e o "Adicionar à Tela de Início" perde nome e ícone.app.js / styles.css revalidando sempre. Os nomes não têm hash, então sem isso o
navegador segura a versão velha.Atenção ao trocar de endereço: cada domínio tem seu próprio armazenamento. Os sons
ficam no IndexedDB, que é por origem — o que você carregou em github.io não aparece em
vercel.app. Escolha um endereço e fique nele.