feat(voz): timbre mais robotico e metalico

Modulacao em anel (72 Hz), ressonancia de lata (atraso de 4,3 ms
realimentado), degraus digitais com saturacao suave e FM com razao 1,41
nas silabas. Volume geral ajustado para compensar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 02:59:25 -03:00
co-authored by Claude Opus 5.5
parent f7c4793dc2
commit 74ae2863a7
2 changed files with 86 additions and 8 deletions
+43 -4
View File
@@ -11,15 +11,45 @@
let ruido = null; // buffer de ruído reaproveitado
const saidas = new WeakMap(); // AudioContext → nó de saída (compressor)
/*
* Cadeia de "garganta metálica" (criada uma vez por AudioContext):
* voz → [seca + modulação em anel] → ressonância de lata → degraus digitais → compressor → saída
*/
function saida(c) {
if (saidas.has(c)) return saidas.get(c);
const entrada = c.createGain();
// modulação em anel: multiplica a voz por um zumbido grave → timbre mecânico
const seca = c.createGain(); seca.gain.value = 0.55;
const anel = c.createGain(); anel.gain.value = 0;
const portadora = c.createOscillator(); portadora.frequency.value = 72;
const profundidade = c.createGain(); profundidade.gain.value = 0.9;
portadora.connect(profundidade); profundidade.connect(anel.gain); portadora.start();
const mixAnel = c.createGain(); mixAnel.gain.value = 0.75;
entrada.connect(seca); entrada.connect(anel); anel.connect(mixAnel);
// ressonância de lata: atraso curtíssimo realimentado (pente de harmônicos)
const lata = c.createGain();
const atraso = c.createDelay(0.05); atraso.delayTime.value = 0.0043;
const volta = c.createGain(); volta.gain.value = 0.58;
const molhada = c.createGain(); molhada.gain.value = 0.55;
seca.connect(lata); mixAnel.connect(lata);
lata.connect(atraso); atraso.connect(volta); volta.connect(atraso); atraso.connect(molhada);
// degraus digitais leves + saturação suave
const degraus = c.createWaveShaper();
const n = 2048, curva = new Float32Array(n), niveis = 28;
for (let i = 0; i < n; i++) {
const x = (i / (n - 1)) * 2 - 1;
curva[i] = Math.tanh(Math.round(x * niveis) / niveis * 1.6);
}
degraus.curve = curva;
lata.connect(degraus); molhada.connect(degraus);
const comp = c.createDynamicsCompressor();
comp.threshold.value = -16; comp.ratio.value = 4; comp.attack.value = 0.003; comp.release.value = 0.15;
const passaBaixa = c.createBiquadFilter();
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 7000; // tira o "chiado" de osciladores crus
comp.connect(passaBaixa); passaBaixa.connect(c.destination);
saidas.set(c, comp);
return comp;
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 6500;
const volume = c.createGain(); volume.gain.value = 0.7; // a cadeia metálica soma energia
degraus.connect(comp); comp.connect(passaBaixa); passaBaixa.connect(volume); volume.connect(c.destination);
saidas.set(c, entrada);
return entrada;
}
/**
@@ -39,6 +69,15 @@
lfo.connect(prof); prof.connect(osc.frequency);
lfo.start(t0); lfo.stop(t0 + dur + 0.05);
}
// FM com razão não inteira: harmônicos desalinhados soam como metal
const mod = c.createOscillator(), modProf = c.createGain();
mod.type = 'sine';
mod.frequency.setValueAtTime(de * 1.41, t0);
mod.frequency.exponentialRampToValueAtTime(Math.max(20, para * 1.41), t0 + dur);
modProf.gain.setValueAtTime(de * (op.metal ?? 0.55), t0);
modProf.gain.exponentialRampToValueAtTime(Math.max(1, para * (op.metal ?? 0.55)), t0 + dur);
mod.connect(modProf); modProf.connect(osc.frequency);
mod.start(t0); mod.stop(t0 + dur + 0.05);
// corpo: uma senoide uma oitava abaixo deixa a voz menos "apito"
const sub = c.createOscillator();
sub.type = 'sine';
+43 -4
View File
@@ -11,15 +11,45 @@
let ruido = null; // buffer de ruído reaproveitado
const saidas = new WeakMap(); // AudioContext → nó de saída (compressor)
/*
* Cadeia de "garganta metálica" (criada uma vez por AudioContext):
* voz → [seca + modulação em anel] → ressonância de lata → degraus digitais → compressor → saída
*/
function saida(c) {
if (saidas.has(c)) return saidas.get(c);
const entrada = c.createGain();
// modulação em anel: multiplica a voz por um zumbido grave → timbre mecânico
const seca = c.createGain(); seca.gain.value = 0.55;
const anel = c.createGain(); anel.gain.value = 0;
const portadora = c.createOscillator(); portadora.frequency.value = 72;
const profundidade = c.createGain(); profundidade.gain.value = 0.9;
portadora.connect(profundidade); profundidade.connect(anel.gain); portadora.start();
const mixAnel = c.createGain(); mixAnel.gain.value = 0.75;
entrada.connect(seca); entrada.connect(anel); anel.connect(mixAnel);
// ressonância de lata: atraso curtíssimo realimentado (pente de harmônicos)
const lata = c.createGain();
const atraso = c.createDelay(0.05); atraso.delayTime.value = 0.0043;
const volta = c.createGain(); volta.gain.value = 0.58;
const molhada = c.createGain(); molhada.gain.value = 0.55;
seca.connect(lata); mixAnel.connect(lata);
lata.connect(atraso); atraso.connect(volta); volta.connect(atraso); atraso.connect(molhada);
// degraus digitais leves + saturação suave
const degraus = c.createWaveShaper();
const n = 2048, curva = new Float32Array(n), niveis = 28;
for (let i = 0; i < n; i++) {
const x = (i / (n - 1)) * 2 - 1;
curva[i] = Math.tanh(Math.round(x * niveis) / niveis * 1.6);
}
degraus.curve = curva;
lata.connect(degraus); molhada.connect(degraus);
const comp = c.createDynamicsCompressor();
comp.threshold.value = -16; comp.ratio.value = 4; comp.attack.value = 0.003; comp.release.value = 0.15;
const passaBaixa = c.createBiquadFilter();
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 7000; // tira o "chiado" de osciladores crus
comp.connect(passaBaixa); passaBaixa.connect(c.destination);
saidas.set(c, comp);
return comp;
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 6500;
const volume = c.createGain(); volume.gain.value = 0.7; // a cadeia metálica soma energia
degraus.connect(comp); comp.connect(passaBaixa); passaBaixa.connect(volume); volume.connect(c.destination);
saidas.set(c, entrada);
return entrada;
}
/**
@@ -39,6 +69,15 @@
lfo.connect(prof); prof.connect(osc.frequency);
lfo.start(t0); lfo.stop(t0 + dur + 0.05);
}
// FM com razão não inteira: harmônicos desalinhados soam como metal
const mod = c.createOscillator(), modProf = c.createGain();
mod.type = 'sine';
mod.frequency.setValueAtTime(de * 1.41, t0);
mod.frequency.exponentialRampToValueAtTime(Math.max(20, para * 1.41), t0 + dur);
modProf.gain.setValueAtTime(de * (op.metal ?? 0.55), t0);
modProf.gain.exponentialRampToValueAtTime(Math.max(1, para * (op.metal ?? 0.55)), t0 + dur);
mod.connect(modProf); modProf.connect(osc.frequency);
mod.start(t0); mod.stop(t0 + dur + 0.05);
// corpo: uma senoide uma oitava abaixo deixa a voz menos "apito"
const sub = c.createOscillator();
sub.type = 'sine';