Áudio

Áudio procedural com Web Audio: motor, chuva e trilha sem arquivos

Por Fernando Capanema, SpiderWorks · Publicado em

Os jogos SpiderWorks quase não usam arquivos de áudio. Efeitos e músicas são gerados em tempo real com a Web Audio API do navegador. A decisão nasceu de uma preocupação com licenças e tamanho do pacote e acabou trazendo vantagens de design: o som reage ao estado do jogo em vez de tocar uma gravação fixa.

Por que sintetizar

Bancos de sons gratuitos têm licenças variadas, algumas exigem atribuição e outras proíbem redistribuição dentro de aplicativos. Em setembro de 2026, a revisão de conformidade do catálogo removeu recursos de terceiros cuja licença não estava documentada. Sintetizar elimina essa categoria de risco. Também mantém o pacote pequeno e permite que o som acompanhe parâmetros contínuos, como velocidade e marcha, o que uma gravação não faz bem.

O Super Pinguin GP é o caso mais completo: não há nenhum arquivo de áudio no projeto.

A estrutura do mixer

Todos os sons passam por um ganho mestre (0,45) e por um compressor configurado como limitador (limiar de −16 dB, razão 8:1). O limitador evita distorção quando motor, chuva, batida e música coincidem. Um único buffer de ruído branco de um segundo é gerado no início com um gerador pseudoaleatório de Park–Miller e reutilizado por vento, chuva, derrapagem e percussão. Usar uma semente fixa torna o ruído idêntico entre sessões, o que facilita comparar gravações de teste.

seed = (seed * 16807) % 2147483647;

O motor

O motor é uma síntese FM simples: uma onda dente de serra (portadora) e uma triangular uma oitava abaixo passam por um filtro passa-baixas; uma senoide moduladora altera a frequência da portadora. O que dá vida ao som é a ligação com a física. A rotação é calculada a partir da velocidade e do limite da marcha atual:

const limit = [76, 132, 194, 257, 330][state.gear - 1];
const rpm = 2200 + clamp(state.speed / limit, 0, 1) * 10200;
const fundamental = rpm / 120;
engine.carrier.frequency.setTargetAtTime(fundamental, now, .055);
engine.depth.gain.setTargetAtTime(input.gas ? fundamental * .8 : 5, now, .07);
engine.filter.frequency.setTargetAtTime(400 + rpm * .12, now, .08);

Três escolhas merecem nota. A profundidade da modulação depende do acelerador: com o pedal pressionado o som fica áspero, e ao soltá-lo fica quase uma onda pura, como um motor em desaceleração. O filtro abre com a rotação, então o motor fica mais brilhante em alta. E todas as mudanças usam setTargetAtTime, que aproxima o valor de forma exponencial em vez de saltar. Isso evita estalos quando a marcha muda e a rotação cai de uma vez.

Vento, chuva e trovão

O vento é ruído passando por um filtro passa-banda em torno de 1.700 Hz, com volume proporcional à velocidade e um reforço quando o carro sai da pista. A chuva usa ruído com filtro passa-baixas e fica abafada dentro do túnel. O trovão combina uma rajada de ruído filtrada em 140 Hz com uma senoide de 48 Hz, acompanhando o clarão da tempestade, que ocorre a cada 18 segundos de corrida. Na versão 1.0.45, o som da chuva ganhou mais presença e passou a começar já na contagem regressiva.

Derrapagem

O chiado de pneu foi acrescentado na versão 1.0.43. Ele mistura ruído em passa-banda com um oscilador triangular agudo. A intensidade é calculada por uma função pura, testável fora do navegador, que considera curvatura do trecho, velocidade acima de 70 km/h, ângulo do volante e freio, com uma variante mais suave na chuva. Em reta, ou com o carro pausado, mudo ou após a chegada, o som se cala. Separar o “quanto” (função pura) do “como” (nós de áudio) foi o que permitiu testar o comportamento sem ouvir.

Trilha e agendamento

A trilha é um chiptune original em onda quadrada, com 32 passos de melodia sobre quatro fundamentais, baixo em onda triangular, bumbo senoidal e chimbal e caixa feitos de ruído. Notas não são disparadas no instante em que o código decide tocá-las; um agendador roda a cada 80 ms e programa as notas dos próximos instantes no relógio do AudioContext. Esse “olhar adiante” mantém o ritmo estável mesmo quando o quadro do jogo atrasa, porque o relógio de áudio é independente do relógio de desenho.

O sintetizador dos outros jogos

Os demais títulos compartilham um módulo de efeitos, o mesmo arquivo em todos os jogos: moeda, acerto, vitória, explosão, erro, clique e outros, cada um uma combinação curta de osciladores com envelopes. Um exemplo é o efeito de explosão, uma onda triangular que cai de 180 para 50 Hz em 0,25 s. O som de virar carta usa ruído filtrado (o papel) e uma senoide grave curta (a carta tocando a mesa). O mesmo módulo tem quatro laços musicais simples entre 104 e 148 batidas por minuto.

As exceções são poucas e estão diminuindo. A Batalha Naval 1.0.1 usa arquivos de som de água e explosão; na versão 1.0.2, em preparação, eles foram substituídos por sons sintetizados para água, acerto e navio afundado, porque a licença dos arquivos não estava documentada. A Paciência ainda usa arquivos de áudio para embaralhar, concluir a distribuição e o movimento automático, e a origem deles segue em revisão no inventário de ativos do projeto.

Pausa, mudo e ciclo de vida

Navegadores só permitem iniciar áudio após um gesto do usuário, então o contexto é criado no primeiro toque. Na pausa, o contexto de áudio é suspenso (ctx.suspend()) e o relógio de áudio para. O mudo leva o ganho mestre a zero com uma transição de cerca de 20 ms e desliga o motor, em vez de cortar, para evitar estalo. No Android, a camada Flutter avisa a página quando o aplicativo vai para segundo plano, e os sons param; os eventos visibilitychange e pagehide cobrem o mesmo caso no navegador.

Limites