mcpField
C
create-sound

create-sound

github.com/raphaelsalaja创意与媒体59066 次安装8 周前更新
代码仓库

关于 create-sound

面向 Agent 的声音设计技能,可将文字提示或提供的音频转为 TypeScript SoundDefinition 片段。它按声音事件选择生成路径,并应用情绪和分层规则,也可指导音频分析与预览渲染。

主要功能

  1. 01按提示词和音频选择生成路径
  2. 02依据事件选择声音层
  3. 03应用情绪与细化规则
  4. 04分析音高、包络和波形
  5. 05可选渲染并验证 WAV 输出

适用场景

  1. 01根据事件提示创建 UI 音效
  2. 02按情绪要求调整从音频分析得到的声音
  3. 03分析音效精灵并重建其中的声音

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/raphaelsalaja/audio --skill create-sound

安装后在 Claude Code 中输入/create-sound确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.json
package.json
README.md
rules/
_sections.md
_template.md
effect-bandpass-noise-swoosh.md
effect-bitcrush-lofi.md
effect-fm-bell.md
effect-lowpass-warmth.md
effect-reverb-tail.md
event-click.md
event-complete.md
event-error.md
event-modal-close.md
event-modal-open.md
event-notification.md
event-success.md
event-swoosh.md
event-tap.md
event-tick.md
event-toggle.md
event-whoosh.md
interpret-classify-waveform.md
interpret-detect-effects.md
interpret-detect-filter.md
interpret-detect-lfo.md
interpret-extract-envelope.md
interpret-extract-fundamental.md
interpret-extract-source.md
interpret-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、buttonevent-click / event-tap
tick、scroll、snap、focusevent-tick
success、complete、win、achievement、level-up、confettievent-success / event-complete
error、fail、wrong、invalid、delete、destroyevent-error
modal、dialog、popup、drawer、sheet、sidebar、dropdown、menuevent-modal-open / event-modal-close
swoosh、slide、transition、page、tabevent-swoosh / event-whoosh
notification、alert、ding、bell、mention、badgeevent-notification
toggle、switch、on、offevent-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-* 规则的修改。

应用顺序
  1. 音源形态形容词(warm、bright、glassy、metallic、lofi、retro、organic)——修改 source.type、source.fm,或添加 filter。
  2. 包络形容词(punchy、airy)——修改 envelope.attack / envelope.decay。
  3. 效果形容词(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、swoosh1 层(Layer)
toggle、copy、send、sync2 层(带 delay 的配对音高)
success、complete、level-up、confetti3 层或更多(带级联 delay 的和弦)
error、delete2 层(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 dBsine
奇次谐波按 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 - 1pan: <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):

  1. 高频瞬态(3-5 kHz),衰减时间短于 10 ms——即“敲击”声。
  2. 较低频的主体(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 验证器。任何违反规则的情况都会导致构建中止。

评论

支持 Markdown