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:
@@ -11,15 +11,45 @@
|
|||||||
let ruido = null; // buffer de ruído reaproveitado
|
let ruido = null; // buffer de ruído reaproveitado
|
||||||
const saidas = new WeakMap(); // AudioContext → nó de saída (compressor)
|
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) {
|
function saida(c) {
|
||||||
if (saidas.has(c)) return saidas.get(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();
|
const comp = c.createDynamicsCompressor();
|
||||||
comp.threshold.value = -16; comp.ratio.value = 4; comp.attack.value = 0.003; comp.release.value = 0.15;
|
comp.threshold.value = -16; comp.ratio.value = 4; comp.attack.value = 0.003; comp.release.value = 0.15;
|
||||||
const passaBaixa = c.createBiquadFilter();
|
const passaBaixa = c.createBiquadFilter();
|
||||||
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 7000; // tira o "chiado" de osciladores crus
|
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 6500;
|
||||||
comp.connect(passaBaixa); passaBaixa.connect(c.destination);
|
const volume = c.createGain(); volume.gain.value = 0.7; // a cadeia metálica soma energia
|
||||||
saidas.set(c, comp);
|
degraus.connect(comp); comp.connect(passaBaixa); passaBaixa.connect(volume); volume.connect(c.destination);
|
||||||
return comp;
|
saidas.set(c, entrada);
|
||||||
|
return entrada;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +69,15 @@
|
|||||||
lfo.connect(prof); prof.connect(osc.frequency);
|
lfo.connect(prof); prof.connect(osc.frequency);
|
||||||
lfo.start(t0); lfo.stop(t0 + dur + 0.05);
|
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"
|
// corpo: uma senoide uma oitava abaixo deixa a voz menos "apito"
|
||||||
const sub = c.createOscillator();
|
const sub = c.createOscillator();
|
||||||
sub.type = 'sine';
|
sub.type = 'sine';
|
||||||
|
|||||||
+43
-4
@@ -11,15 +11,45 @@
|
|||||||
let ruido = null; // buffer de ruído reaproveitado
|
let ruido = null; // buffer de ruído reaproveitado
|
||||||
const saidas = new WeakMap(); // AudioContext → nó de saída (compressor)
|
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) {
|
function saida(c) {
|
||||||
if (saidas.has(c)) return saidas.get(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();
|
const comp = c.createDynamicsCompressor();
|
||||||
comp.threshold.value = -16; comp.ratio.value = 4; comp.attack.value = 0.003; comp.release.value = 0.15;
|
comp.threshold.value = -16; comp.ratio.value = 4; comp.attack.value = 0.003; comp.release.value = 0.15;
|
||||||
const passaBaixa = c.createBiquadFilter();
|
const passaBaixa = c.createBiquadFilter();
|
||||||
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 7000; // tira o "chiado" de osciladores crus
|
passaBaixa.type = 'lowpass'; passaBaixa.frequency.value = 6500;
|
||||||
comp.connect(passaBaixa); passaBaixa.connect(c.destination);
|
const volume = c.createGain(); volume.gain.value = 0.7; // a cadeia metálica soma energia
|
||||||
saidas.set(c, comp);
|
degraus.connect(comp); comp.connect(passaBaixa); passaBaixa.connect(volume); volume.connect(c.destination);
|
||||||
return comp;
|
saidas.set(c, entrada);
|
||||||
|
return entrada;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +69,15 @@
|
|||||||
lfo.connect(prof); prof.connect(osc.frequency);
|
lfo.connect(prof); prof.connect(osc.frequency);
|
||||||
lfo.start(t0); lfo.stop(t0 + dur + 0.05);
|
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"
|
// corpo: uma senoide uma oitava abaixo deixa a voz menos "apito"
|
||||||
const sub = c.createOscillator();
|
const sub = c.createOscillator();
|
||||||
sub.type = 'sine';
|
sub.type = 'sine';
|
||||||
|
|||||||
Reference in New Issue
Block a user