欢迎光临
我们一直在努力

手写 Web Audio 环形调制器:打造科幻机器人声学变声特效

封面信息图

在经典科幻剧《神秘博士》里,标志性反派达雷克(Dalek)那刺耳、冰冷、毫无人类情感的金属机械嗓音,曾经是无数人的童年阴影;在无数赛博朋克风格的游戏和科幻动画中,机器人与外星异形变声的背后,也几乎都有同一个声学电路的身影——环形调制器(Ring Modulator)。

很多前端开发者在用 Web Audio API 尝试做语音变声特效时,往往停留在粗暴的音高变调(Pitch Shift)或简单的混响(Reverb)上。但音高变调只能把声音变细成“花栗鼠”或者变粗成“低音大叔”,它仍然保留着人类声带天然的谐波自然倍数关系。

要想彻底剥夺人声的血肉感,赋予其一种冰冷、扭曲、充满金属碰撞与电子杂音的纯正机器品质,必须进入时域乘法与非谐波频带的世界。

环形调制的声学物理与边带数学

环形调制的名称来源于早期模拟电路中由四个二极管构成的环形拓扑。在信号处理的数学本质上,它是两个连续时域信号的直接瞬时相乘。

假设输入的人声信号为 $m(t)$(调制波 Modulator),其包含的某个人声音频分量为 $\\cos(\\omega_m t)$;本地振荡器产生的高频载波信号为 $c(t) = \\cos(\\omega_c t)$(载波 Carrier)。

当两者在乘法器中相遇时,根据高中三角函数积化和差公式:

$$y(t) = c(t) \\cdot m(t) = \\cos(\\omega_c t) \\cdot \\cos(\\omega_m t) = \\frac{1}{2} [\\cos((\\omega_c + \\omega_m)t) + \\cos((\\omega_c – \\omega_m)t)]$$

这个简单的数学方程揭示了一个极其惊人的声学现象:输出的最终音频中,原始的人声音频 $f_m$ 和载波基频 $f_c$ 被完全抵消消除!取而代之的,只有两个全新的侧频带(Sidebands)——和频 $(f_c + f_m)$ 与差频 $|f_c – f_m|$。

由于人类自然发声产生的泛音列是整数倍谐波(如 100Hz, 200Hz, 300Hz),而加上载波(例如 400Hz)后生成的和差频(300Hz, 500Hz, 200Hz, 600Hz, 100Hz, 700Hz)打破了自然的音程比例,形成了密集的非谐波失真(Inharmonic Spectrum)。人耳听到这种破坏了自然泛音的声学结构时,会本能地将其感知为金属外壳、齿轮摩擦与无机冷酷的机器人嗓音。

Web Audio 原生乘法拓扑架构

在 Web Audio API 的标准规范中,并没有一个现成的名叫 RingModulatorNode 的单体节点。但我们可以利用 Web Audio 极其硬核的底层特性——**音频速率参数调制(a-rate AudioParam Modulation)**来手写一个完美的乘法器。

在 Web Audio 中,任何 AudioNode 的音频输出不仅可以连接到另一个节点的 input,还可以直接连接到另一个节点的 AudioParam(例如 GainNode.gain)!

当输入音频信号连接到 GainNode 的输入端,而载波振荡器连接到同一个 GainNode 的 gain 参数上时,GainNode 内部执行的正是每一个采样点音频样值的乘法计算:

$$Output[n] = Input[n] \\times Gain[n]$$

生产级科幻机器人变声器手写实现

下面的 TypeScript 实现封装了一个带有抗混叠滤波、载波波形切换与干湿比调谐的专业环形调制变声系统:

export type CarrierWaveform = 'sine' | 'triangle' | 'square' | 'sawtooth';

export interface RingModulatorOptions {
carrierFrequency: number; // 载波频率 (Hz, 常见机器人频段 30Hz ~ 1200Hz)
carrierType: CarrierWaveform;
dryWetMix: number; // 干湿比 (0.0 ~ 1.0)
cutoffHz: number; // 抗混叠后置低通滤波频率
}

export class SciFiRobotVoiceProcessor {
private ctx: AudioContext;
public input: GainNode;
public output: GainNode;

// 核心处理拓扑
private carrierOsc: OscillatorNode;
private ringMultiplier: GainNode;
private postFilter: BiquadFilterNode;
private dryGain: GainNode;
private wetGain: GainNode;

private isStarted: boolean = false;

constructor(ctx: AudioContext, options?: Partial<RingModulatorOptions>) {
this.ctx = ctx;

const opt: RingModulatorOptions = {
carrierFrequency: 85, // 85Hz: 极具威慑力的低频达雷克机械轰鸣
carrierType: 'sine',
dryWetMix: 0.9,
cutoffHz: 4500,
…options
};

this.input = ctx.createGain();
this.output = ctx.createGain();

// 1. 初始化干湿音混合节点
this.dryGain = ctx.createGain();
this.wetGain = ctx.createGain();
this.dryGain.gain.value = 1.0 – opt.dryWetMix;
this.wetGain.gain.value = opt.dryWetMix;

// 2. 初始化核心乘法节点 (GainNode)
// 关键点:必须将 gain.value 设为 0,让其完全受载波振荡器动态驱动
this.ringMultiplier = ctx.createGain();
this.ringMultiplier.gain.value = 0;

// 3. 初始化载波振荡器 (Carrier)
this.carrierOsc = ctx.createOscillator();
this.carrierOsc.type = opt.carrierType;
this.carrierOsc.frequency.value = opt.carrierFrequency;

// 4. 初始化抗混叠与修剪低通滤波器
this.postFilter = ctx.createBiquadFilter();
this.postFilter.type = 'lowpass';
this.postFilter.frequency.value = opt.cutoffHz;
this.postFilter.Q.value = 0.7071;

this.assembleGraph();
}

private assembleGraph(): void {
// A. 干音旁路
this.input.connect(this.dryGain);
this.dryGain.connect(this.output);

// B. 环形调制乘法链路
// 调制波 (人声) 输入乘法器信号端
this.input.connect(this.ringMultiplier);

// 载波振荡器直接注入乘法器的 gain 参数 (音频速率瞬时乘法)
this.carrierOsc.connect(this.ringMultiplier.gain);

// 乘法输出进入后置滤波器,去除高次谐波失真,再进入湿音增益
this.ringMultiplier.connect(this.postFilter);
this.postFilter.connect(this.wetGain);
this.wetGain.connect(this.output);
}

/**
* 启动载波振荡器
*/
public start(): void {
if (!this.isStarted) {
this.carrierOsc.start();
this.isStarted = true;
}
}

/**
* 动态平滑调节载波频率 (打造机器人变异升降调)
*/
public setCarrierFrequency(freqHz: number, rampTimeSec: number = 0.05): void {
const t = this.ctx.currentTime;
this.carrierOsc.frequency.cancelScheduledValues(t);
this.carrierOsc.frequency.setTargetAtTime(freqHz, t, rampTimeSec);
}

/**
* 切换载波波形
* 方波/锯齿波会带来极度粗糙刺耳的 8-bit 复古科幻感,正弦波则具有纯净的钟鸣金属感
*/
public setCarrierWaveform(type: CarrierWaveform): void {
this.carrierOsc.type = type;
}

/**
* 调节干湿比
*/
public setDryWet(mix: number): void {
const clamped = Math.max(0, Math.min(1, mix));
const t = this.ctx.currentTime;
this.dryGain.gain.setValueAtTime(1 – clamped, t);
this.wetGain.gain.setValueAtTime(clamped, t);
}

public dispose(): void {
if (this.isStarted) {
try {
this.carrierOsc.stop();
} catch {}
}
this.input.disconnect();
this.carrierOsc.disconnect();
this.ringMultiplier.disconnect();
this.postFilter.disconnect();
this.dryGain.disconnect();
this.wetGain.disconnect();
this.output.disconnect();
}
}

机器人音效的调音秘籍与避坑细节

在把麦克风输入接入这套环形调制器时,有几个非常实用的调音经验:

  • 载波频率的选择决定了机器人的“人格”:
    • 低频载波(30Hz ~ 90Hz):人声会产生极高频颤抖的颤音(Tremolo)与粗糙沙哑的低音共鸣,特别适合邪恶机械头目或巨型机甲战士;
    • 中高频载波(200Hz ~ 800Hz):和频与差频大量落入人声敏感区,语言清晰度降低,呈现出极具钟鸣金属感的电子声(Bell-like Inharmonic Tone),这是典型的飞船 AI 或轻量智能管家嗓音;
    • 超高频载波(1.5kHz 以上):声音会变得极度尖细、带有明显的异星电波感。
  • 话筒输入的直流阻断(DC Blocker):一些低端外接麦克风采集的音频可能带有几个毫伏的直流电平漂移(DC Offset)。如果直流偏移进入环形调制乘法器,载波信号本身就会作为残余泄露到最终音频中,导致即便没人说话,音箱里也能听到嗡嗡的载波蜂鸣。在 this.input 前面串联一个高通截止频率为 15Hz 的极低频高通滤波器,能彻底扫净直流偏移。
  • 载波波形的声学选择:绝大多数情况下优先选用纯正弦波(Sine)。因为正弦波只有一个单频 $f_c$,产生的侧频带干净纯粹;如果你切换到方波(Square),方波本身包含奇次高阶泛音($3f_c, 5f_c, 7f_c…$),它们会与人声的所有频段交叉相乘,生成极其密集的泛音风暴,稍不注意就会导致声音完全听不清字句。
  • 仅靠几行纯原生的 Web Audio 节点编织,就能在浏览器里以极低的 CPU 开销实时把一段普通人声重塑为令人毛骨悚然的科幻机甲声浪。这就是底层声学工程给前端带来的无限创造力。

    赞(0)
    未经允许不得转载:171主机测评 » 手写 Web Audio 环形调制器:打造科幻机器人声学变声特效
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址