最近,我给一个自己使用的网页加了短提示音。声音能响,页面也有音量滑块,但滑块推到上限,听起来仍像轻轻碰了一下。
最省事的改法,是把增益再乘一个更大的数。真正排查后,我没有先这么做:问题不只在“音量多大”,还在“声音怎样从出现走到消失”。后来调整了提示音的包络和音色,保留原有音量控制,也没有改变系统与浏览器的混音器设置。反馈变得合适,我就停止继续加码。
这个小问题让我重新区分了三个东西:界面的百分比、实际输出的数字信号,以及人听到的感觉。它们不能互相代替。
音量滑块的上限,不是所有层的上限
一个显示为满格的滑块,只说明页面按自己的规则走到了上限。它究竟映射到哪个增益、音源本身有多强、浏览器有没有被单独调低、输出设备是否正确,仍要分别检查。
这次只读检查发现,浏览器的独立音量没有被压低,也没有静音。系统主音量并不是满格,所以我不能宣称“系统所有层都已排除”。但我也不想为了一个网页提示音,把整台机器的其他声音一起放大。
接着回到页面的发声逻辑,看到的是一个很快衰减的短音:开头出现,随即退下去。这提示我,应该先观察声音的时间形状,再决定是否提高峰值。
在 Web Audio 中,GainNode 对输入音频采样施加增益;它不是替你判断“这个声音听起来够不够清楚”的感知仪表。[1]
声音包络:不是只有最高点,还要看最高点后面发生什么
可以把包络理解为声音强度随时间变化的轮廓。对短提示音来说,至少要分清三个阶段:怎样开始、维持多久、怎样结束。
旧提示音的主要问题,是很快收掉,声音显得单薄。我采用的方向不是无限延长,而是让它在短时间内有一点持续的“主体”,再平滑结束,同时调整音色。因为实际修改同时改变了包络、音色和峰值范围,我不会把最终听感改善全部归功于某一个变量。
AudioParam 的 linearRampToValueAtTime() 可以按时间安排参数的渐变,适合构造这类平滑的起落。[2] 直接跳变增益可能产生点击声;MDN 的 GainNode 文档也特别提醒了这个风险。[1]
我的判断是:当一个提示音薄到难以辨识时,先检查它是否“刚开始就结束”,往往比先扩大一个乘数更有诊断价值。这是排查顺序,不是所有提示音的通用处方。
做一个不发声的对照,单独验证包络
为了避免把音色变化也算成包络的功劳,我又做了一个独立实验:同一个纯音、相同的目标峰值、相同的采样率与统计窗口,只改增益的时间曲线。一个快速衰减,另一个短暂维持后退出。
实验使用 OfflineAudioContext。它把音频图渲染成 AudioBuffer,而不是送到设备扬声器,所以可以比较数字采样,不必播放,也不需要录制系统声音。[3]
下面是实际运行过的独立示例。它不是网站生产参数,也不是建议直接复制进通知系统的音效配方。
async function measureEnvelope(mode) {
const rate = 48000;
const ctx = new OfflineAudioContext(1, rate * 0.4, rate);
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = 800;
osc.connect(gain);
gain.connect(ctx.destination);
gain.gain.setValueAtTime(0, 0);
gain.gain.linearRampToValueAtTime(0.4, 0.006);
if (mode === 'brief') {
gain.gain.exponentialRampToValueAtTime(0.0001, 0.08);
gain.gain.setValueAtTime(0, 0.081);
} else {
gain.gain.setValueAtTime(0.4, 0.12);
gain.gain.linearRampToValueAtTime(0, 0.24);
}
osc.start(0);
osc.stop(0.3);
const samples = (await ctx.startRendering()).getChannelData(0);
let peak = 0, sum = 0;
for (const x of samples) {
peak = Math.max(peak, Math.abs(x));
sum += x * x;
}
return { peak, rms: Math.sqrt(sum / samples.length) };
}
console.table({
brief: await measureEnvelope('brief'),
sustain: await measureEnvelope('sustain')
});
在本次真实浏览器实验中,两段信号的峰值都接近 0.4;快速衰减的 RMS 约为 0.036,短持续的约为 0.177。这里的 RMS 是固定窗口内数字采样的均方根,按代码中的平方、平均、开方计算。两段峰值相近,整体采样幅度却明显不同,这正是单看最高点容易漏掉的部分。
这些数值只属于这个合成实验。它们不代表扬声器的声压,不代表听力安全,也不能换算成“人听起来大了多少倍”。窗口如果变化,连同静音部分一起统计的 RMS 也会变化;比较时必须保持一致。
我怎样确认修改有效,又没有夸大验证
原排查留下了两个不同层级的证据。一层是在离线音频图里比较修改前后的输出,另一层是在实际页面的音频图里读取数字采样,检查音量变化和静音是否真的作用到输出。它们都不是麦克风实录。
测量片段没有出现幅度达到或越过正负 1 的数字采样。这只是该测量位置、该片段内没有观察到越界,不保证声卡、扬声器和系统后处理一定不失真,也不是证明没有发生任何上游限幅的完整检测。
此外,使用反馈说明修改后的声音合适。到这里,我把“信号确实改变了”和“当前使用者觉得合适”分别记录,而不是拿测量代替听感。今天复核当前实现,仍保留了短持续和平滑退出,没有把后来的页面调整误当成新的音效修复。
一个可以复用的排查顺序
先确认声音确实能触发,以及音频上下文的实际状态。如果根本没有发声,不要把它当成“小声”问题。
再检查页面音量映射、浏览器独立音量、静音和输出设备。发现某一层被调低时,先修那一层,不必改音效设计。
音量链没有解释全部现象,再看包络:开始是否太突兀、主体是否过短、结束是否过快。用相同窗口比较数字峰值与 RMS,必要时做一个只改包络的离线对照。
最后在目标设备上低音量试用,再逐步调整。短提示音的目标是可辨识、不过分打扰,不是尽可能响。如果需要反复播报,还应单独检查频率和使用场景;一次短音适合,不代表连续通知也适合。
这次我留下的结论很简单:网页提示音小,不一定要靠更大的增益解决。先查它在时间上怎样发声,再把数字信号和真实听感分别验收。
本文由真实排查记录与独立实验整理,AI 辅助成文。案例已匿名化;示例为独立合成测试,不披露实际系统参数。
资料与适用边界
以下开发者文档于 2026 年 10 月 4 日核验。GainNode 页面标注最后修改于 2023 年 4 月 6 日,线性渐变页面为 2024 年 5 月 6 日,OfflineAudioContext 页面为 2024 年 8 月 4 日。文档用于核对 API 机制,不代表所有浏览器、设备和输出链都经过本文测试;本文对照实验只在本次 Chromium 浏览器中执行。