mcpField
C
create-sound

create-sound

github.com/raphaelsalaja创意与媒体60071 次安装4 天前更新
代码仓库

关于 create-sound

Create Sound 是指导 Agent 按提示生成声音定义,或分析现有音频并提取参数的技能。它依据事件类别和情绪词构造分层声音配置,并可选渲染 WAV 与回环验证。

主要功能

  1. 01按输入类型选择生成或分析流程
  2. 02根据事件与情绪词构造声音定义
  3. 03分析频率、包络、波形与滤波器
  4. 04支持单层及多层声音配置
  5. 05可选渲染 WAV 并回环验证

适用场景

  1. 01为界面点击、通知或错误事件设计音效
  2. 02从已有音频提取可复用的声音参数
  3. 03渲染并检查生成音效与预期的差异

安装

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

$npx skills add https://github.com/raphaelsalaja/web-kits --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