应用声音设计
做出关于何时、使用何种声音以及如何在应用程序中呈现声音的设计决策。这不是关于音频工程(那是sound-engineer)或复古音效(那是win31-audio-design)。本技能回答UI声音中最难的问题:这个交互应该发出声音吗? 如果是,该发出什么样的声音?
何时使用
用于以下场景:
- 决定Web或移动应用中的哪些交互应具有声音反馈
- 选择通知、确认、错误等声音的适当特性
- 使用ElevenLabs、AudioCraft或Stable Audio创建AI生成的UI声音
- 使用Web Audio API实现程序化声音
- 在iOS/Android上将触觉反馈与音频配对
- 为产品建立声音品牌标识
- 审核应用现有声音设计是否令人厌烦或是有帮助
请勿用于:
- 专业音频混音/母带处理 -> 使用sound-engineer
- Windows 3.1风格系统声音 -> 使用win31-audio-design
- 音乐创作或配乐 -> 使用sound-engineer
- 无障碍屏幕阅读器音频 -> 使用accessibility-expert
- 游戏音频系统(3D空间、动态音乐) -> 使用sound-engineer
核心问题:这应该发出声音吗?
交互是否需要反馈?
|
+-- 用户发起的操作(点按、点击、提交)?
| +-- 结果在屏幕上是否可见?
| | +-- 是且明显 --> 声音为可选(通常跳过)
| | +-- 是但微妙 --> 声音有帮助(轻柔确认)
| | +-- 否(后台进程) --> 建议添加声音
| +-- 操作是否具有破坏性?
| +-- 是 --> 建议添加声音(明显警告音)
| +-- 否 --> 声音为可选
|
+-- 系统发起的事件(通知、提醒)?
| +-- 用户是否在应用中?
| | +-- 是 --> 微妙的应用内声音
| | +-- 否 --> 通知声音(遵循操作系统惯例)
| +-- 是否对时间敏感?
| +-- 是 --> 更紧急/引起注意的声音
| +-- 否 --> 可以无声或环境音
|
+-- 环境/氛围(后台状态)?
+-- 应用是否需要存在感? --> 微妙的环境音
+-- 用户是否在工作/专注? --> 静音
UI声音词汇
每个应用都需要来自这些类别的声音。并非所有类别都是必需的——大多数应用需要3-5种。
第1层:基础(大多数应用需要)
| 声音类型 | 目的 | 特征 | 时长 | 示例 |
|---|---|---|---|---|
| 确认音 | 操作成功 | 明亮、上升的音调 | 100-300ms | Slack消息已发送 |
| 错误音 | 操作失败 | 低沉、平坦或下降 | 200-400ms | 表单验证失败 |
| 通知音 | 新信息到达 | 独特、中等能量 | 300-600ms | Slack收到新消息 |
第2层:优化(注重细节的应用)
| 声音类型 | 目的 | 特征 | 时长 | 示例 |
|---|---|---|---|---|
| 过渡音 | 视图间导航 | 微弱的嗖声或点击声 | 50-150ms | iOS页面翻转 |
| 开关音 | 二进制状态改变 | 清脆的咔哒声或啪声 | 50-100ms | 开关打开/关闭 |
| 悬停音 | 鼠标悬停反馈 | 几乎听不到的滴答声 | 20-50ms | 菜单项悬停 |
| 选择音 | 从列表中选择项目 | 轻柔的砰声或拨弦声 | 50-150ms | 下拉选择 |
| 删除音 | 破坏性移除 | 空洞的砰声或嗖声 | 150-300ms | 垃圾桶操作 |
第3层:愉悦(有性格的应用)
| 声音类型 | 目的 | 特征 | 时长 | 示例 |
|---|---|---|---|---|
| 成就音 | 达到里程碑 | 音乐性、庆祝性 | 500ms-2s | Duolingo课程完成 |
| 环境音 | 后台存在感 | 循环、氛围感 | 连续 | Noisli、Forest应用 |
| 品牌标志音 | 应用打开/关闭 | 标志性、难忘 | 500ms-1.5s | Netflix ta-dum |
| 彩蛋音 | 隐藏的愉悦 | 趣味、意外 | 可变 | 科乐美秘技奖励 |
平台惯例
Apple(iOS / macOS)
Apple HIG规定了特定的音频行为:
- 系统声音是神圣的:切勿覆盖或替换系统提醒声音
- 尊重静音开关:如果物理静音开关打开,你的应用声音会停止(媒体播放除外)
- 音频会话类别很重要:
ambient—— 与其他音频混合,尊重静音开关soloAmbient—— 静音其他音频,尊重静音开关playback—— 忽略静音开关(仅限媒体应用)
- 触觉与声音配对:将
UIImpactFeedbackGenerator与音频结合使用
Apple的声音特征:
- 干净的正弦波和滤波音调
- 最小泛音(纯净,不嗡嗡作响)
- 短衰减(声音不拖尾)
- 相对于系统声音音量的恒定值
// iOS:播放系统声音
import AudioToolbox
AudioServicesPlaySystemSound(1057) // 标准键盘点击
// iOS:触觉 + 自定义声音
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
// 同时播放自定义声音
Material Design(Android)
Google的声音设计原则:
- 功能优先:声音应提供信息,而非装饰
- 默认微妙:UI声音应比媒体音量低
- Android上触觉优先:优先使用触觉反馈;仅在触觉不足时添加声音
- 仅在有意义时开启声音:大多数交互的默认状态应为静音
Material声音特征:
- 短促、清脆的打击乐声音
- 微妙的谐波内容(非纯正弦波)
- 比Apple略微温暖(更多中频)
- 设计为在低质量手机扬声器上有效
Web应用
Web的声音惯例最弱,这意味着更多自由,但也更可能令人厌烦。
- 切勿自动播放声音 —— 浏览器会阻止,用户也讨厌
- 首先需要用户手势:Web Audio API需要用户交互后才能播放
- 提供音量控制:始终让用户静音或调整
- 考虑耳机用户:在笔记本扬声器上听起来不错的声音可能对耳机用户很刺耳
使用AI创建声音
ElevenLabs音效
最适合:具有特定特征描述的真实、高质量的UI声音。
# 生成确认音
curl -X POST "https://api.elevenlabs.io/v1/sound-generation" \
-H "xi-api-key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "短促明亮的上升音,数字音,干净的正弦波,200毫秒,UI确认声音",
"duration_seconds": 0.5
}'
ElevenLabs的有效提示:
- 确认音:"短促明亮上升的双音,干净的数字音,温暖,200毫秒"
- 错误音:"低沉空洞的砰声带轻微嗡嗡声,数字音,300毫秒,不刺耳"
- 通知音:"轻柔的三音木琴旋律,友好,400毫秒"
- 开关音:"清脆的机械点击声,塑料开关,50毫秒"
- 删除音:"柔和空洞的下降嗖声,200毫秒"
- 成就音:"明亮上升的琶音,闪闪发光,庆祝性,1秒"
Meta AudioCraft(开源)
最适合:希望完全控制且无需API费用的开发者。
from audiocraft.models import AudioGen
from audiocraft.data.audio import audio_write
model = AudioGen.get_pretrained('facebook/audiogen-medium')
model.set_generation_params(duration=0.5)
# 生成UI声音
descriptions = [
"短促数字确认蜂鸣音,上升音高,干净",
"低沉错误嗡嗡声,简短,不刺耳,数字音",
"轻柔通知音,两个音符,令人愉悦",
]
wav = model.generate(descriptions)
for i, w in enumerate(wav):
audio_write(f'ui_sound_{i}', w.cpu(), model.sample_rate)
Stable Audio Open
最适合:较长的环境音、背景纹理、品牌标志音。
import torch
from stable_audio_tools import get_pretrained_model
from stable_audio_tools.inference.generation import generate_diffusion_cond
model, model_config = get_pretrained_model("stabilityai/stable-audio-open-1.0")
# 生成环境背景
conditioning = [{
"prompt": "平静的环境数字纹理,温暖的低沉嗡嗡声,适合生产力应用背景,可循环",
"seconds_total": 10
}]
后期处理清单
AI生成的声音在发布前需要清理:
- 修剪静音 —— 移除开头/结尾的空隙
- 标准化音量 —— 所有UI声音应具有一致的可感知响度
- 淡入/淡出 —— 5-10毫秒的淡入淡出可防止咔哒声伪影
- 压缩动态范围 —— UI声音应具有一致的音量
- 导出为WAV(适用于原生)或OGG/MP3(适用于Web,每个声音低于50KB)
- 在目标扬声器上测试 —— 手机扬声器会切断200Hz以下的频率
Web Audio API:程序化声音
对于Web应用,程序化生成声音完全消除了对音频文件的需求。更小的包大小,即时可用。
class UISound {
private ctx: AudioContext | null = null;
private getContext(): AudioContext {
if (!this.ctx) {
this.ctx = new AudioContext();
}
return this.ctx;
}
/** 明亮上升确认音 */
confirm(): void {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(600, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(900, ctx.currentTime + 0.15);
gain.gain.setValueAtTime(0.15, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
}
/** 低沉下降错误音 */
error(): void {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'triangle';
osc.frequency.setValueAtTime(400, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(200, ctx.currentTime + 0.3);
gain.gain.setValueAtTime(0.12, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.35);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.35);
}
/** 用于开关和选择的微妙咔哒声 */
click(): void {
const ctx = this.getContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'square';
osc.frequency.setValueAtTime(800, ctx.currentTime);
gain.gain.setValueAtTime(0.08, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.05);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.05);
}
/** 三音通知旋律 */
notify(): void {
const ctx = this.getContext();
const notes = [523, 659, 784]; // C5, E5, G5
notes.forEach((freq, i) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, ctx.currentTime);
const start = ctx.currentTime + i * 0.12;
gain.gain.setValueAtTime(0, start);
gain.gain.linearRampToValueAtTime(0.12, start + 0.02);
gain.gain.exponentialRampToValueAtTime(0.001, start + 0.15);
osc.start(start);
osc.stop(start + 0.15);
});
}
}
// 使用示例
const sound = new UISound();
document.querySelector('#submit')?.addEventListener('click', () => sound.confirm());
触觉-音频配对(移动端)
在移动端,触觉和音频应作为整体设计。触觉先到达(即时物理反馈),声音后到达(确认和特征)。
配对指南
| 交互 | 触觉 | 声音 | 时机 |
|---|---|---|---|
| 点按按钮 | 轻触 | 短促点击声 | 同时 |
| 拨动开关 | 中触 | 清脆啪声 | 触觉比声音早10毫秒 |
| 下拉刷新 | 拉动时选择滴答声;完成时通知 | 微弱的拉伸声;确认音 | 触觉领先20毫秒 |
| 滑动删除 | 阈值处重触 | 空洞嗖声 | 触觉在提交点 |
| 长按 | 持续刚性 | 低沉上升嗡嗡声 | 触觉与声音同时开始 |
| 成功 | 成功模式(三次敲击) | 上升琶音 | 与节拍对齐 |
iOS实现
import UIKit
import AVFoundation
class FeedbackManager {
static let shared = FeedbackManager()
private let lightImpact = UIImpactFeedbackGenerator(style: .light)
private let mediumImpact = UIImpactFeedbackGenerator(style: .medium)
private let heavyImpact = UIImpactFeedbackGenerator(style: .heavy)
private let notificationFeedback = UINotificationFeedbackGenerator()
func tapConfirm() {
lightImpact.impactOccurred()
SoundPlayer.play(.confirm)
}
func errorShake() {
notificationFeedback.notificationOccurred(.error)
SoundPlayer.play(.error)
}
func deleteCommit() {
heavyImpact.impactOccurred()
SoundPlayer.play(.delete)
}
}
令人满意的声音的心理学
为什么有些UI声音感觉“正确”,而其他感觉“廉价”?
令人满意的声音属性
| 属性 | 为何有效 | 示例 |
|---|---|---|
| 音高上升 | 表示进度、乐观、完成 | 确认音上升 |
| 短衰减 | 感觉响应迅速且可控 | Nintendo菜单声音 |
| 谐波丰富 | 感觉有机且温暖 | Slack的“敲门”声 |
| 音量一致 | 感觉专业且可预测 | Apple系统声音 |
| 节奏对齐 | 感觉有意为之且经过设计 | Duolingo的连续天数计数 |
令人厌烦的声音属性
| 属性 | 为何令人烦躁 | 避免 |
|---|---|---|
| 重复无变化 | 人类习惯后会产生烦躁 | 每天100次完全相同的音效 |
| 相比环境太响 | 打断注意力 |