关于 create-sound
面向 Agent 的声音设计技能,可将文字提示或提供的音频转为 TypeScript SoundDefinition 片段。它按声音事件选择生成路径,并应用情绪和分层规则,也可指导音频分析与预览渲染。
主要功能
- 01按提示词和音频选择生成路径
- 02依据事件选择声音层
- 03应用情绪与细化规则
- 04分析音高、包络和波形
- 05可选渲染并验证 WAV 输出
适用场景
- 01根据事件提示创建 UI 音效
- 02按情绪要求调整从音频分析得到的声音
- 03分析音效精灵并重建其中的声音
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ raphaelsalaja/ audio --skill create-sound安装后在 Claude Code 中输入/create-sound确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.jsonpackage.jsonREADME.mdrules/_sections.md_template.mdeffect-bandpass-noise-swoosh.mdeffect-bitcrush-lofi.mdeffect-fm-bell.mdeffect-lowpass-warmth.mdeffect-reverb-tail.mdevent-click.mdevent-complete.mdevent-error.mdevent-modal-close.mdevent-modal-open.mdevent-notification.mdevent-success.mdevent-swoosh.mdevent-tap.mdevent-tick.mdevent-toggle.mdevent-whoosh.mdinterpret-classify-waveform.mdinterpret-detect-effects.mdinterpret-detect-filter.mdinterpret-detect-lfo.mdinterpret-extract-envelope.mdinterpret-extract-fundamental.mdinterpret-extract-source.mdinterpret-multi-layer.md详细介绍
创建声音
由
rules/*.md通过src/build.mjs生成。请勿手动编辑。
使用 pipeline-detect-input 选择生成路径,然后按照对应章节操作。
1. 生成流程
Agent 端到端执行的步骤。处理任何创建声音的请求时,请从这里开始。
1.1 检测输入模式并路由请求 (关键)
根据用户提供的内容决定执行哪条路径。
| 输入 | 路径 |
|---|---|
| 仅有提示词(未附加音频) | 跳过 interpret-*。前往 pipeline-pick-base-layer。 |
| 仅有音频文件 | 执行所有 interpret-* 规则。跳过 event-* / mood-*。 |
| 同时有提示词和音频 | 先执行 interpret-*,然后将提示词作为对测量所得 SoundDefinition 的细化层。 |
检测音频
查找符合 *.wav、*.mp3、*.flac 或 *.ogg 的附件,或用户引用的且指向音频文件的任何路径。与精灵图同目录的 JSON 清单(*.json)也是音频路径信号。
细化示例(提示词 + 音频)
| 提示词修饰语 | 对测量所得定义的细化 |
|---|---|
| "更温暖" | 添加 filter: { type: "lowpass", frequency: 2500 } |
| "更短" / "更有冲击力" | 将 envelope.decay 限制为 <= 0.06 |
| "更明亮" | 移除或提高任何低通滤波器的截止频率 |
| "带混响" | 追加 effects: [{ type: "reverb", decay: 0.5, mix: 0.15 }] |
| "低一个八度" | 将 source.frequency(或 start / end)减半 |
此步骤的输出
生成一条内部备注,例如:
Input: prompt + audio
Plan: run interpret-* on out/click.wav, then refine with mood-warm.
然后继续下一步流程。
1.2 根据提示词的事件类别选择基础层 (关键)
对提示词进行分词,找出最强的事件类别信号。与 event-* 规则匹配。
词元映射
| 提示词中的词元 | 事件规则 |
|---|---|
| click、tap、key、press、button | event-click / event-tap |
| tick、scroll、snap、focus | event-tick |
| success、complete、win、achievement、level-up、confetti | event-success / event-complete |
| error、fail、wrong、invalid、delete、destroy | event-error |
| modal、dialog、popup、drawer、sheet、sidebar、dropdown、menu | event-modal-open / event-modal-close |
| swoosh、slide、transition、page、tab | event-swoosh / event-whoosh |
| notification、alert、ding、bell、mention、badge | event-notification |
| toggle、switch、on、off | event-toggle |
方向词元(打开与关闭)
- "open"、"appear"、"in"、"show"、"expand"、"confirm" -> 音高上升。
- "close"、"dismiss"、"out"、"hide"、"collapse"、"cancel" -> 音高下降。
输出
从所选事件规则的 example 复制一个初始 SoundDefinition 字面量。下一步(pipeline-apply-mood)将对其进行修改。
如果无法确定触发了哪种事件类别,则默认使用 event-click,并通过情绪形容词进行调整。
1.3 将情绪形容词应用到基础层 (高)
pipeline-pick-base-layer 生成初始 SoundDefinition 后,扫描提示词中的形容词词元,并按顺序应用各个 mood-* 规则的修改。
应用顺序
- 音源形态形容词(
warm、bright、glassy、metallic、lofi、retro、organic)——修改source.type、source.fm,或添加filter。 - 包络形容词(
punchy、airy)——修改envelope.attack/envelope.decay。 - 效果形容词(
reverby、delayed、crushed)——追加到effects。
冲突处理
warm+bright-> 后出现的词优先。lofi+glassy-> 两者都应用,但将effects限制为最多 2 项。punchy+airy-> 两者互不影响(一个作用于包络,一个作用于音源);两者都应用。
对现有定义进行润色(音频 + 提示词路径)
当输入模式为 prompt + audio 时,将每个形容词视为对测量所得定义的润色,而不是从头开始:
| 形容词 | 润色方式 |
|---|---|
| warmer | 添加或降低 filter.frequency(约 2500 Hz 的低通滤波) |
| brighter | 移除低通滤波,或将其截止频率提高到 6 kHz 以上 |
| punchier | 将 envelope.decay 限制为不超过 0.06,并设置 envelope.attack: 0 |
| longer | 延长 envelope.decay,若缺少 release 则添加 |
| crisper | 略微提高 gain,并添加 fm: { ratio: 0.5, depth: 50 } |
输出
修改后的 SoundDefinition。交由 pipeline-decide-layering 处理。
1.4 决定使用单层还是多层 (MEDIUM-HIGH)
| 事件类别 | 默认设置 |
|---|---|
| click、tap、tick、hover、focus、swoosh | 1 层(Layer) |
| toggle、copy、send、sync | 2 层(带 delay 的配对音高) |
| success、complete、level-up、confetti | 3 层或更多(带级联 delay 的和弦) |
| error、delete | 2 层(sawtooth + square) |
具体结构请参见 layer-single、layer-octave-pair、layer-ascending-chord、layer-click-plus-body。
将单个 Layer 升级为 MultiLayerSound
如果提示词或润色要求多个层,则进行封装:
{
layers: [<existing layer>, <new layer>],
// optional global effects, e.g. sidechain compressor, master EQ
}
每层的 gain 值总和应不超过约 0.6(参见 validate-gain-budget)。
将 MultiLayerSound 降级为单个 Layer
如果应用情绪效果后只剩一层,则直接输出内部的 Layer,而不是只包含一个元素的 MultiLayerSound。两种形式都能通过验证,但单层形式是规范的紧凑写法。
1.5 输出、可选渲染、可选往返验证 (HIGH)
1. 输出
始终返回可直接粘贴到 .web-kits/<patch>.ts 文件中的 TypeScript 代码片段:
import type { SoundDefinition } from "@web-kits/audio";
export const myClick: SoundDefinition = {
source: { type: "sine", frequency: 1300, fm: { ratio: 0.5, depth: 60 } },
envelope: { decay: 0.012, release: 0.004 },
gain: 0.18,
};
并附上一行理由,指出你处理了哪些提示词:
“click” -> 以
event-click为基础;“warm” -> 保留默认正弦波;在 1.3 kHz 下无需额外滤波器。
2. 可选预览渲染
如果用户要求 WAV(或你想为自己的输出评分),请使用 packages/audio/src/offline.ts:
import { renderToWav } from "@web-kits/audio";
import { writeFile } from "node:fs/promises";
const blob = await renderToWav(myClick, { duration: 0.3 });
await writeFile("preview.wav", Buffer.from(await blob.arrayBuffer()));
duration 应为 attack + decay + release + 0.05(短尾音);如果存在混响,则应更长。
3. 可选的往返验证
如果你根据提示词生成了声音,并想确认结果符合预期,可对渲染后的 WAV 运行 interpret-* 规则,并对比测量值与预期值:
| 字段 | 可接受的偏差 |
|---|---|
| 基频 Hz | ±5% |
| 起音 | ±2 ms |
| 衰减 | ±10% |
| 频谱质心 | 所选波形预期值的 ±20% |
如果偏差超出容差范围,请完善定义(通常可通过调高或调低 gain、收紧 envelope 或调整 filter.frequency),然后重新渲染。
2. 音频解析
用户分享音频文件时触发的 FFT 分析子步骤。
2.1 获取并拆分源音频 (HIGH)
用户分享的是单个文件或音频精灵图(一个包含多种声音的文件)。进行任何 FFT 分析前,先将每种声音保存为磁盘上的一个单声道 WAV 文件。
来自 npm 包的音频精灵图
npm pack <package-name> --pack-destination /tmp
tar -xzf /tmp/<package-name>-*.tgz -C /tmp
查找 MP3/WAV,以及用于将声音名称映射到时间偏移量的任何 JSON 清单。
根据清单切片
ffmpeg -i sprite.mp3 \
-ss <start_seconds> -t <duration_seconds> \
-acodec pcm_s16le -ar 44100 \
output/<name>.wav
静音检测切片(无清单)
ffmpeg -i sprite.mp3 -af silencedetect=noise=-40dB:d=0.05 -f null -
读取 silence_start/silence_end 行,并在间隙之间切片。
输出约定
每个声音的 WAV 文件保存到 out/<name>.wav(单声道、44.1 kHz、16-bit PCM)。下游解析规则会从 src/analyze.py 调用 analyze.load_mono(path)。
2.2 提取基频和音高扫频 (HIGH)
在多个时间切片上采样频谱,以检测静态音高和任何扫频。
from analyze import load_mono, analyze_slice
sample_rate, data = load_mono("out/click.wav")
slices = [0, 5, 10, 20, 50] # ms
freqs_over_time = [analyze_slice(data, sample_rate, t) for t in slices]
映射规则
| 观测结果 | 输出 |
|---|---|
| 所有切片均在 ±5% 范围内 | source.frequency: <Hz>(静态) |
| 随切片递减 | source.frequency: { start: <high>, end: <low> } |
| 随切片递增 | source.frequency: { start: <low>, end: <high> } |
提示
- 如果起音是点击瞬态,可跳过最初 1-2 ms,以免污染 FFT。
- 对于非常短的声音(< 20 ms),减少切片数量并使用较小的窗口。
- FFT 前使用 Hanning 窗(已在
analyze_slice中应用)以减少频谱泄漏。
2.3 从振幅提取 ADSR 包络 (HIGH)
平滑时域振幅,找到起音、峰值、延音和结束点,并推导出各个 ADSR 阶段。
from analyze import load_mono, extract_envelope
sample_rate, data = load_mono("out/click.wav")
env = extract_envelope(data, sample_rate)
# -> { "attack": 0.0008, "decay": 0.012, "sustain": 0.0, "release": 0.005 }
输出格式
该字典与 Envelope 类型一一对应:
envelope: {
attack: env.attack, // 0 if percussive
decay: env.decay,
sustain: env.sustain, // 0 for transient sounds, 0-1 for sustained
release: env.release,
}
启发式规则
sustain < 0.01-> 删除该字段;该声音具有打击乐特性。attack < 0.001-> 设置attack: 0。release < 0.005-> 限制为0.005,以避免结尾出现咔嗒声。
2.4 根据谐波分类振荡器波形 (HIGH)
将前 8 个谐波的振幅与基频进行比较。
import numpy as np
from scipy.fft import rfft, rfftfreq
from analyze import classify_waveform
segment = data[:int(sample_rate * 0.02)].astype(float)
segment *= np.hanning(len(segment))
spectrum = np.abs(rfft(segment))
freqs = rfftfreq(len(segment), 1 / sample_rate)
waveform = classify_waveform(spectrum, freqs, fundamental_freq)
# -> "sine" | "triangle" | "square" | "sawtooth" | "wavetable"
映射规则
| 模式 | source.type |
|---|---|
| 仅有基频,谐波 < -40 dB | sine |
| 奇次谐波按 1/n 逐渐衰减 | triangle |
| 奇次谐波振幅大致相等 | square |
| 所有谐波按 1/n 逐渐衰减 | sawtooth |
| 自定义谐波轮廓(均不符合上述情况) | wavetable |
| 无明显谐波结构,且能量宽带分布 | noise |
何时回退到 wavetable
如果谐波轮廓与任何常见振荡器都不匹配,则改为提取谐波序列:
from analyze import extract_harmonics
harmonics = extract_harmonics(spectrum, freqs, fundamental_freq, num_harmonics=16)
# -> { source: { type: "wavetable", harmonics, frequency: fundamental_freq } }
噪声颜色
对于没有基频的宽带信号,根据频谱斜率进行分类:
from analyze import classify_noise_color
color = classify_noise_color(spectrum, freqs) # "white" | "pink" | "brown"
# -> { source: { type: "noise", color } }
2.5 检测滤波器类型、截止频率和共振 (MEDIUM-HIGH)
将测量频谱与已识别振荡器的预期频谱进行比较。
通过频谱质心检测截止频率
from analyze import spectral_centroid
centroid = spectral_centroid(spectrum, freqs)
基频为 440 Hz 时的预期质心:正弦波约为 440,三角波约为 880,锯齿波约为 2200,方波约为 1760。如果测得的质心明显低于预期,则存在 lowpass;请在 -3 dB 点估算截止频率。
根据滚降判断滤波器类型
| 观察结果 | filter.type |
|---|---|
| 高频滚降比信号源本身产生的滚降更陡 | lowpass |
| 低频滚降 | highpass |
| 仅有一段窄频带通过 | bandpass |
| 移除一个窄陷波 | notch |
| 截止频率附近出现谐振峰 | 高 resonance |
谐振(Q 值)
from analyze import estimate_resonance
q = estimate_resonance(spectrum, freqs, cutoff_hz)
# Returns 0.1 - 20.0
滤波器包络
如果明亮度随时间变化(明亮的起音逐渐变暗),则存在滤波器包络:
from analyze import detect_filter_envelope
env = detect_filter_envelope(data, sample_rate)
# -> { "peak": 4000, "resting": 800, "decay_ms": 50 } or None
映射到:
filter: {
type: "lowpass",
frequency: env.resting,
envelope: { attack: 0, peak: env.peak, decay: env.decay_ms / 1000 },
}
2.6 检测信号源之后的效果 (中)
每个检测器都会返回带有置信度提示的结果,而非保证准确。与信号源和包络相比,效果更难提取——存在歧义时请报告低置信度。
混响
from analyze import detect_reverb
result = detect_reverb(data, sample_rate, envelope_end_ms=120)
# -> { "type": "reverb", "decay": 0.6 } or None
延迟(自相关)
from analyze import detect_delay
result = detect_delay(data, sample_rate)
# -> { "type": "delay", "time": 0.25, "feedback": 0.3 } or None
FM 合成
基频的非整数倍处出现频谱边带,表明存在 FM:
from analyze import detect_fm
fm = detect_fm(spectrum, freqs, fundamental_freq)
# -> { "fm": { "ratio": 0.5, "depth": 80 } } or None
映射到 source.fm: { ratio, depth }(而非单独的效果)。
颤音和振音
1–20 Hz 频带内的周期性振幅或频率调制表明存在颤音/振音。跟踪振幅或音高随时间的变化,并调用 detect_lfo(参见 interpret-detect-lfo)。
位压碎/失真
| 时域特征 | 效果 |
|---|---|
| 阶梯状/量化波形,带有混叠伪影 | bitcrusher |
| 波形顶部平坦,并增加了谐波 | distortion |
合唱/镶边/移相
随时间扫动的梳状滤波器模式会在频谱中产生移动的陷波。难以通过算法区分;请标记以供人工审核。
2.7 检测 LFO 调制 (低至中)
LFO 是对参数进行的亚音频(0.1–20 Hz)周期性调制。跟踪参数随时间的变化,然后运行 detect_lfo。
from analyze import detect_lfo
# 1. Track amplitude (or pitch, or spectral centroid) at regular intervals
window_ms = 10
samples_per_window = int(sample_rate * window_ms / 1000)
amp_over_time = [
float(np.max(np.abs(data[i:i + samples_per_window])))
for i in range(0, len(data) - samples_per_window, samples_per_window)
]
# 2. Detect periodicity
lfo = detect_lfo(np.array(amp_over_time), 1000 / window_ms)
# -> { "frequency": 5.0, "depth": 0.12 } or None
按跟踪参数映射
| 跟踪的参数 | LFO 目标 |
|---|---|
| 振幅 | gain |
| 音高 | frequency 或 detune |
| 频谱质心 | filter.frequency |
| 声像位置 | pan |
输出
lfo: { type: "sine", frequency: lfo.frequency, depth: lfo.depth, target: "gain" }
根据调制波形选择 type:平滑正弦波 -> sine,陡峭斜坡 -> sawtooth,硬切换 -> square。
2.8 检测多层声音和立体声定位 (中)
多个基频 -> MultiLayerSound
检查频谱中的峰值。如果两个或更多强峰不是同一基频的整数倍,则说明声音由多个层叠加而成。
from scipy.signal import find_peaks
peaks, props = find_peaks(spectrum, height=float(np.max(spectrum)) * 0.2)
peak_freqs = sorted(freqs[peaks])
# Check pairwise ratios. If no shared fundamental explains all peaks, treat as layered.
对每个检测到的基频运行完整流程(频率、包络、波形、滤波器、效果),并为每个基频生成一个 Layer:
{
layers: [
{ source: { ... }, envelope: { ... }, gain: 0.2 },
{ source: { ... }, envelope: { ... }, gain: 0.15, delay: 0.04 },
]
}
较早的层通常使用 delay: 0(省略);后续层则调整其 delay,以匹配测得的起音间隔。
立体声和声像
from analyze import analyze_stereo
stereo = analyze_stereo(data)
# -> { "pan": 0.3, "stereo_width": 0.7 }
pan 幅度 | 输出 |
|---|---|
< 0.05 | 省略(pan: 0 为默认值) |
0.05 - 1 | pan: <value> |
stereo_width > 0.5 且 |pan| < 0.05 表明存在立体声效果(合唱、双层)。可考虑拆分为两个声像分别位于 -0.5/+0.5 的层。
备用方案
如果某种声音无法合成(复杂的瞬态、录制素材、不可约的纹理),请改用:
{ source: { type: "sample", url: "..." } }
并注明应直接使用原始音频文件,而不是重新合成。
3. UI 事件配方
针对各类 UI 事件的具体 SoundDefinition 模板。提示词路径会将其用作基础层。
3.1 Click - 正弦波 + 低频 FM,极短衰减 (HIGH)
短促的上行正弦扫描,带有轻微 FM。扫描赋予 Click “清脆感”;FM 则增加谐波厚度,同时避免听起来像金属声。
错误(衰减过长,听起来像钟声):
{ source: { type: "sine", frequency: 1300 }, envelope: { decay: 0.5 }, gain: 0.18 }
正确:
{
source: { type: "sine", frequency: { start: 200, end: 700 }, fm: { ratio: 0.5, depth: 80 } },
envelope: { attack: 0, decay: 0.06, sustain: 0, release: 0.02 },
gain: 0.25,
}
参考:.web-kits/core.ts click。
3.2 Complete - 四音上行琶音 (MEDIUM-HIGH)
与 success 使用相同的 C 大调三和弦,但在顶部加入 C6,并采用更紧凑的 15 ms delay 间隔,让音符融合成一个连贯的动作,而不是听起来像独立的音高。
参考:.web-kits/core.ts complete。
3.3 Error - 分层锯齿波 + 方波,带下行扫描 (HIGH)
两个相差一个八度的下行扫描叠加。低通滤波器可避免声音刺耳。同样的音形也适用于 delete(衰减稍长)。
错误(没有滤波器,听起来像蜂鸣器):
{ source: { type: "sawtooth", frequency: { start: 320, end: 140 } }, envelope: { decay: 0.25 }, gain: 0.22 }
参考:.web-kits/core.ts error、_delete。
3.4 Modal-close - 下行正弦扫描 (MEDIUM)
与 modalOpen 相反。由于关闭操作应比打开时更克制,因此使用更窄的音域。出于同样的原因,gain 略低。
对于 drawer-close,使用 800 -> 350。对于 dropdown-close,使用 900 -> 500。
参考:.web-kits/core.ts modalClose、drawerClose、dropdownClose。
3.5 Modal-open - 上行正弦扫描 (MEDIUM)
单个正弦波在 80 ms 内从约 430 Hz 扫描至约 1400 Hz。无 FM、无滤波器;清澈的音色传达出“出现”的感觉。
对于 drawer-open,使用稍低的起始频率(约 350 Hz)和更低的增益(约 0.08)。
对于 dropdown-open,使用更小的音域(500 -> 1200),衰减约为 60 ms。
参考:.web-kits/core.ts modalOpen、drawerOpen、dropdownOpen。
3.6 Notification - 富含 FM 的正弦波,带轻微混响 (HIGH)
两个相隔五度的 FM 铃声,其间隔为 100 ms delay。fm.ratio: 1.5 带来不谐和的闪烁感;每层匹配的混响则让它们融为一体。
对于 ding:使用单层、fm.ratio: 3.5、混响 decay: 0.8。
对于 mention:使用较低的基频(660 Hz)、fm.ratio: 2.5,并稍微增强起音。
参考:.web-kits/core.ts notification、ding、mention、badge。
3.7 Success - 上行三音正弦和弦 (HIGH)
三个正弦层分别为 C5 / E5 / G5,彼此间隔 0.07 s 逐层加入 delay。最高音带有轻微上行扫描(G5 -> A5),使和弦以“向上”的感觉解决,而不是戛然而止。
各层增益总和为 0.45,远低于 0.6 的上限。
参考:.web-kits/core.ts success。
3.8 Swoosh - 白噪声经过扫描式带通滤波器 (MEDIUM)
白噪声由带通滤波器塑形,滤波器的中心频率从 300 Hz 扫描至 4 kHz。扫描方向构成动作感:峰值高于静止值 = 上行 Swoosh;峰值低于静止值(例如静止值 2500、峰值 400)= 下行。
对于 slide-up,使用类似的音形,峰值设为 3500。对于 slide-down,改用粉红噪声,并设置 envelope: { decay: 0.12, peak: 500 }(滤波器包络不起音)。
参考:.web-kits/core.ts swoosh、slide、slideUp、slideDown。
3.9 Tap - 静态高频正弦波 + FM,极短 (HIGH)
单一高音(无扫描),强烈 FM,衰减短于 20 ms。这是“按键”原型。
错误(频率过低,听起来像重击声):
{ source: { type: "sine", frequency: 200 }, envelope: { decay: 0.015 }, gain: 0.2 }
正确:
{
source: { type: "sine", frequency: 1300, fm: { ratio: 0.5, depth: 100 } },
envelope: { attack: 0, decay: 0.015, sustain: 0, release: 0.005 },
gain: 0.2,
}
参考:.web-kits/core.ts tap、keyPress。
3.10 Tick - 尽可能轻柔的正弦音 (MEDIUM)
敲击音家族中频率最高的音效。衰减时间短于 15 ms。gain 上限约为 0.15,因为 tick 触发频繁,不能喧宾夺主。
对于 scroll-snap,将 gain 降至 0.08;对于 focus/blur,将其降至 0.04-0.06。
参考:.web-kits/core.ts 中的 tick、scrollSnap、focus、blur。
3.11 Toggle - 带延迟的成对正弦音(方向很重要)(MEDIUM)
两个短促的正弦音:C7 (2093 Hz) 和 G7 (3136 Hz),间隔 25 ms。
toggle-on:先播放低音,再播放高音(上行 = 启用)。toggle-off:先播放高音,再播放低音(下行 = 停用)。
相同的结构也适用于 copy(1200 Hz,然后 1400 Hz,间隔 40 ms)和 sync(C5,然后 G5)。
参考:.web-kits/core.ts 中的 toggleOn、toggleOff、copy、sync。
3.12 Whoosh - 用于整页过渡的更长、更缓慢的呼啸音 (LOW-MEDIUM)
结构与 swoosh 相同,但所有参数都更舒展。滤波器的起音时间延长 4 倍(0.04 s,而非 0.01 s),让音效轻柔地开始。由于音效持续时间更长,gain 略高。
pageEnter 使用峰值为 3000 的带通滤波器和白噪声;pageExit 使用粉红噪声,并反转带通包络(仅衰减,峰值为 400)。
参考:.web-kits/core.ts 中的 whoosh、pageEnter、pageExit。
4. 情绪词汇
叠加在基础配方上的形容词到旋钮参数的映射。
4.1 Airy - 噪声源 + 峰值较高的带通滤波器 (LOW-MEDIUM)
修改:
- 将
source替换为{ type: "noise", color: "white" }。 - 将
filter替换为带通包络,并将峰值设为较高频率(4-6 kHz)。 - 将
envelope.attack延长至 0.02-0.04 s,让声音渐入,而非突然出现。 - 将
gain降至 0.08-0.12。
如果基础音色为正弦波、三角波等有音高的音色,此情绪会完全替换音源——这是结构性改动。
4.2 Bright - 不使用低通滤波器,可选加入 FM 闪烁感 (MEDIUM)
修改:
- 移除任何
type为lowpass的filter,或将其截止频率提高至 6 kHz 以上。 - 如果基础音色使用了
triangle,则改为sine,并设置fm: { ratio: 2.5, depth: 50 },营造闪烁感。 - 可以略微提高
gain(+0.02),但不要超出预算。
4.3 Glassy - 高 FM 比率 + 混响 (MEDIUM)
修改:
source.type: "sine"。source.fm: { ratio: 3.5, depth: 200-300 }。- 在
effects: [{ type: "reverb", decay: 0.7, damping: 0.5, mix: 0.15 }]后追加。 - 将
envelope.decay延长至至少 0.3 s,让钟声得以延续。
参考:.web-kits/core.ts 中的 ding、sparkle、star。
4.4 Lo-fi - 位压碎失真 + 低通滤波器 (MEDIUM)
修改:
- 添加
filter: { type: "lowpass", frequency: 1500 }。 - 在
effects: [{ type: "bitcrusher", bits: 6-8, mix: 0.7-1 }]后追加。 - 可以将
gain降低 0.02,因为位压碎会让声音听起来更响。
与 mood-retro 搭配效果很好。
4.5 Metallic - 非谐波 FM 比率 (MEDIUM)
修改:
source.type: "sine"(或使用square获得更刺耳的效果)。source.fm: { ratio: 2.76, depth: 300-400 }——.web-kits/core.ts中badge使用的非谐波比率 2.76,听起来像金属钟声。- 缩短释音时间;金属音不应持续太久。
避免与 mood-warm 叠加使用——两者会相互抵消。
参考:.web-kits/core.ts 中的 badge。
4.6 Organic - 三角波 + 轻微失谐 + 少量混响 (LOW-MEDIUM)
修改:
source.type: "triangle"。- 添加
source.detune: 5-10,带来极轻微的音高摆动。 - 将
envelope.attack从 0 提高至 0.003-0.008 s,避免起音像硬点击声。 - 追加少量混响(
mix: 0.05-0.1)。
与 mood-warm 搭配效果很好。避免与 mood-metallic 或 mood-lofi 搭配——它们会破坏自然感。
4.7 强劲 - 零起音、极短衰减 (MEDIUM)
修改:
envelope.attack: 0。envelope.decay: <= 0.06。envelope.sustain: 0。envelope.release: <= 0.015。gain增加 +0.05 没问题,因为能量集中在更短的时间范围内。
与源波形风格相互独立——可叠加在温暖/明亮/玻璃质感/金属质感效果之上。
4.8 复古 - 方波或锯齿波 + 低通 + 位压缩 (MEDIUM)
修改:
source.type: "square"(或"sawtooth")。- 添加
filter: { type: "lowpass", frequency: 3000 }以柔化混叠。 - 追加
effects: [{ type: "bitcrusher", bits: 8, sampleRateReduction: 2-4, mix: 1 }]。
与上升或阶梯式音高滑变(金币、强化道具)自然搭配。
4.9 温暖 - 低通 + 轻微混响 (MEDIUM)
在基础配方之上进行修改:
- 添加
filter: { type: "lowpass", frequency: 2500 }(或 2-3 kHz)。 - 可选添加
effects: [{ type: "reverb", decay: 0.4, mix: 0.1 }]。 - 如果基础配方使用了
sawtooth或square,则改用triangle,让声源本身听起来更圆润。
如果基础配方已有低通滤波器,将其截止频率降低约 30%。
5. 分层模式
何时使用单层、双层或和弦堆叠。
5.1 上行和弦 - 3-4 层,带递进延迟 (MEDIUM)
使用 3-4 个正弦波层奏出大三和弦(C-E-G 或 C-E-G-C)。将 delay 逐层增加约 70 ms,营造“听起来像音符”的效果;增加约 15 ms,则营造“听起来像一个连贯动作”的效果。
最上层加入轻微的向上滑变,让和弦以解决感收尾,而不是戛然而止。
层数最多为 4。各层的增益总和应 <= 0.6。如果某层的 sustain > 0,所有层都应采用相近的延音值,以避免出现错落的余响。
5.2 点击声 + 主体 - 在延音上叠加瞬态层 (MEDIUM)
同时触发两层(不设 delay):
- 高频瞬态(3-5 kHz),衰减时间短于 10 ms——即“敲击”声。
- 较低频的主体(80-300 Hz),衰减时间较长——即“鼓”声。
适用于:发送按钮、强确认、类似鼓点的 UI 反馈,以及任何需要听起来有分量的场景。两层使用相同的声源 type(通常为 sine),使其听起来像同一个事件。
增益应大致平衡(瞬态层略低于主体层)。
5.3 八度音程组合 - 两层相隔八度并带延迟 (MEDIUM)
使用相隔纯五度或八度的两层,并设置 20-50 ms 的 delay。音高顺序(低音先或高音先)可表示“开启”与“关闭”、“打开”与“关上”等含义。
各层的增益总和应小于 0.5。两个包络应匹配,以免第二拍听起来与第一拍脱节。
如果发现自己需要超过 2 层,请改用 layer-ascending-chord。
5.4 单层 - 直接输出 Layer (HIGH)
配方只需一个声源时,直接输出 Layer 结构(不要将其包装在 { layers: [...] } 中)。引擎接受这两种形式,但不加包装的 Layer 形式是规范的紧凑表示。
const sound: SoundDefinition = {
source: { type: "sine", frequency: 1300 },
envelope: { decay: 0.012, release: 0.004 },
gain: 0.18,
};
适用于:点击、轻触、滴答、悬停、聚焦、失焦、滚动吸附、单音通知、简单的呼啸声。
6. 效果配方
何时以及如何使用各类效果。
6.1 带通噪声呼啸声 - 滤波器包络就是动作本身 (MEDIUM)
配方设置在层的 filter 中,而不是其 effects 中:
filter: {
type: "bandpass",
frequency: <resting Hz>,
resonance: 1-3,
envelope: { attack: 0.01-0.04, peak: <target Hz>, decay: 0.08-0.2 },
}
- 峰值高于静止值 -> 向上的呼啸声。
- 峰值低于静止值 -> 向下的呼啸声。
resonance较高(>2)时会更像哨声;较低(<1.5)时则更宽泛。
声源应为 noise(白噪声适合尖锐效果,粉红噪声适合柔和效果)。声源的振幅包络只负责控制噪声的持续时间。
6.2 位压缩 - 复古 / 低保真润色 (LOW-MEDIUM)
bits:4-8。数值越低,颗粒感越强。低于 4 会变成噪声。sampleRateReduction:1(关闭)到 8(严重混叠)。与bits: 8搭配,可获得 8 位游戏机音效。mix:通常为 1。将位压缩声与干声混合会听起来浑浊。
最适合搭配 square 或 sawtooth 声源,并使用低通滤波器柔化混叠边缘。
避免与 effect-reverb-tail 叠加——量化噪声会被混响拖散。
6.3 FM 钟声——高比率、高深度 (中)
source.fm: { ratio, depth } 是结构参数,并非效果节点。要生成钟声:
ratio:谐波钟声使用 2.5-3.5;“徽章”非谐和锵鸣使用 2.76。depth:150-400。深度越高,声音越尖锐。envelope.decay:至少 0.3 s,让钟声得以延续。
要生成明亮的“叮”声,使用 ratio: 3.5、depth: 250,并添加混响(decay: 0.7, mix: 0.15)。
要生成厚实的沉闷“咚”声,使用 ratio: 0.5、depth: 200,并采用较短的包络。
可搭配 mood-glassy 或 mood-metallic。
6.4 低通暖音——最稳妥的滤波器 (中)
filter: { type: "lowpass", frequency: 2500, resonance: 0.7 }
frequency:1500-3000 Hz 可获得“温暖”的音色。低于 1000 Hz 会开始让声音变闷。resonance:省略或设为 0.7-1.5。高于 2 时,截止频率本身会开始发出啸声。
可安全地与混响、FM 和大多数情绪搭配。这是消除任何声源刺耳感最快的方法。
若要获得动态暖音(明亮起音 → 温暖延续),添加滤波器包络:
filter: {
type: "lowpass",
frequency: 2500,
envelope: { attack: 0, peak: 6000, decay: 0.08 },
}
6.5 混响尾音——空间小、混合比低 (中)
UI 默认混响:
decay:0.3-0.6 s。damping:0.4-0.6(削弱尾音中的高频;若不设置,混响听起来会很金属)。mix:0.08-0.15。高于 0.2 就会开始产生音乐制作效果的感觉。
对于钟声类声音(通知、叮声)的分层混响,将混响放在该层的 effects 数组中,这样每个音符都会独立延续。对于和弦/转场的共享混响,将其放在 MultiLayerSound 顶层的 effects 中。
避免将混响与延迟叠加——二选一。
7. 输出验证
返回给用户前,每个生成的 SoundDefinition 都必须通过检查。
7.1 时长上限——瞬态 1 s,绝对上限 3 s (中)
估算总时长:
estimated = (envelope.attack ?? 0)
+ envelope.decay
+ (envelope.release ?? 0)
+ max(0, longestEffectTail) // reverb decay, delay time * 4
目标:
- 点击 / 轻点 / 滴答 / 悬停 / 聚焦:<= 0.1 s。
- 切换 / 复制 / 同步:<= 0.2 s。
- 模态框 / 抽屉 / 下拉菜单的打开/关闭:<= 0.3 s。
- 成功 / 完成 / 通知:<= 0.8 s。
- 呼啸声 / 页面转场:<= 0.5 s。
硬性上限:3 s。超过此时长的声音不应作为 UI 音效。
validate 脚本会计算估算时长,并标记超过 3 s 的层。
7.2 包络检查——衰减不能为零,无释放时延音不能无限持续 (高)
要求:
envelope.decay > 0(始终如此)。最小设为 0.005。- 如果
envelope.sustain > 0,则必须设置envelope.release,且其值必须> 0。
建议:
envelope.attack:打击音设为 0,延续音调设为 0.003-0.05,环境音最高设为 0.1。- 任意 UI 音效的
envelope.decay + envelope.release:<= 2 s。超过此时长,你创作的就是音乐,而非界面反馈。 envelope.sustain:瞬态设为 0,“余音”音调设为 0.03-0.15,仅对按住播放的循环音设为 0.3-0.7。
validate 脚本会标记 decay <= 0、sustain > 0 但未设置 release 的情况,以及总时长超过 3 s 的情况。
7.3 频率范围——20 Hz 到 20 kHz,且两端都有意义 (高)
硬性范围:
source.frequency(或扫频的start/end):20 Hz <= f <= 20000 Hz。filter.frequency:20 Hz <= f <= 20000 Hz。filter.envelope.peak:范围与filter.frequency相同。
建议的 UI 范围:
- 音调类声源:80 Hz <= f <= 8000 Hz。
- 高频瞬态层(点击声、击棒声):最高 5 kHz。
- 次低频层(厚实感、鼓声):60-200 Hz。
超过 8 kHz 的声音在手机扬声器上可能听不见;低于 60 Hz 的声音在笔记本电脑扬声器上可能听不见。
validate 脚本会标记任何超出硬性范围的频率。
7.4 控制增益预算——将总层增益保持在 0.6 以下 (HIGH)
单层:
- 对于常见的 UI 事件,
gain设为 0.04 到 0.3。 - 背景节拍/滚动吸附:0.04-0.10。
- 中等重要性(点击、轻触、悬停):0.12-0.20。
- 高重要性(成功、通知):0.16-0.25。
多层:
- 所有
layer.gain值的总和必须 <= 0.6。 - 如果超出,请按比例缩放每一层,而不是只降低其中一层的值。
如果音效包含较强的混响(mix > 0.15)或失真,请将增益预算降低 20%。
validate 脚本会标记单层增益超过 0.4 和总增益超过 0.6 的情况。
7.5 符合 Schema——根据 patch.schema.json 验证 (CRITICAL)
每个生成的 SoundDefinition 都必须通过 packages/audio/schemas/patch.schema.json(#/$defs/SoundDefinition)的验证。
常见错误:
envelope中缺少必填的decay。lfo中缺少必填的target。- 将
pan设为[-1, 1]范围之外的值。 - 使用不属于
lowpass | highpass | bandpass | notch | allpass | peaking | lowshelf | highshelf | iir的filter.type。 - 在
Layer或MultiLayerSound中添加不支持的顶层字段(例如name、description)。Schema 设置了additionalProperties: false。 - 混淆
MultiLayerSound.effects(混音总线上的效果链)和Layer.effects(单层上的效果链)。
validate 脚本会对每条规则的 example 字段调用 JSON Schema 验证器。任何违反规则的情况都会导致构建中止。