Skip to content

Latest commit

 

History

History
327 lines (216 loc) · 17.7 KB

File metadata and controls

327 lines (216 loc) · 17.7 KB

合成方法论

都是实测出来的,每条后面都有当时的数字。跟直觉相反的那几条尤其别改回去。

一、让一击"更重",只能加低频层

不能靠压低别的层。 两条路的实测对照:

                              <250Hz
三层原版                       -50.0dB
三层但压低后两层让 chop 突出    -50.1dB   ← 低频一点没变
四层,加一层低频 body           -26.3dB   ← +23.6dB

压别的层是错觉式的重——听感上"干净了",但低频量纹丝不动,而且丢层次。"重"的物理本质是低频质量,只能新增能量,不能搬移已有能量

EQ 也不行:给瞬态加低频减高频得到的是"更闷",不是"更重"。实测木头撞击 light→heavy,高频涨 18.5dB、低频只涨 10dB——越重高频涨得越快,加低频减高频是逆着物理趋势走。

两次独立验证:piece-impact 的 lethal 档、投矛 pierce 的第一击。

二、"同类不同种" = 高频接近,低频和时长拉开

要让两个材质听起来是"两种金属"而不是"两个物种":

              时长     >2k     <500     判断
铁           0.496s   -32.9   -36.6
青铜(对)     0.642s   -33.3   -32.3    高频差 0.4dB → 同类;低频+4.3、时长+146ms → 不同种 ✓
铁盒(错)     0.375s   -45.4   -26.8    高频差 12dB → 直接变成两个物种 ✗

高频决定"是什么材料类别",低频和时长决定"是这个类别里的哪一种"。

三、共享一层来建立同一性

一组相关音效要听起来属于同一个东西,让它们共享一个层

  • 五门棋盘:contact 层全用同一批木棋子,只换盘面 body → "同一副子落五种盘"
  • 护盾三件套:共享 ring 层 → "同一面盾的三种遭遇"

护盾破碎时把共享的 ring 硬切掉(140ms 截断),比加任何碎裂声都更能表达"它没了"——因为前两个音里它一直在。

四、倒放 = 零成本的"聚拢"

衰减音倒过来放就是渐强。所有"充能 / 凝聚 / 结界升起"类音效的起手层都能这么来,不用新录:

metal_board2 衰减音 → areverse → atrim 280ms → afade in

"生成"类动作的通用结构是两段式:聚拢(渐强,无明确起点)+ 成型(明确瞬态)。收尾那个瞬态是关键——只有渐强没有收尾,听感是"还在充能";有了收尾才是"已经立住"。

五、碎裂:按物理生成,不要随机撒点

碎片不是独立随机事件,它们来自同一个物体,尺寸分布、飞行距离、弹跳衰减全是关联的。随机撒听起来像"很多东西同时掉",不像"一个东西碎了"。

碎片数     3 大块(40-160ms 落)+ 4 小碎(260-700ms 落)
弹跳       每片 2-3 次,间隔 × 0.77 递减,音高不变(还是那一片)
音量       大块 -4~-8dB / 小碎 -17~-25dB,每弹 -7.5dB
时间分布   后段更密(碎片要飞行才落地,一片还会弹好几次)
音高       跨度 1.3-1.4 倍就够,再宽会听成上行音阶

时间分布是前疏后密,不是前密后疏——这条最反直觉。逆向现役音效实测:

                时长      onset   分布
block-break    1388ms      4      42/104/348/615ms  间隔越来越大
boss-shatter   6475ms    236      500-1000ms:14 → 1000-2000ms:41  越往后越密

物理上:初裂 → 大块分离 → 碎片飞行 → 落地 → 弹跳。飞行需要时间,弹跳更晚,一片会弹好几次,所以小事件天然在后面且密度增加。

素材本身的限制:用"敲击玻璃杯"这种有明确基频的音去堆,堆多少都像一堆铃铛。真实碎片落地是宽带噪声脉冲,几乎无音高。缩短到 30ms 以下能削弱音高感(人耳要几个周期才辨得出音高),但根本解是录真正的碎片落地音。

0904 已按这条重做:tools/recipes/block-break.mjs 用 Sonniss 的真碎玻璃(玻璃板 / 花瓶 / 碎片 / 落地各一类)替掉敲杯堆叠,打碎它的那一击仍是自录 glass_slab__knuckle(碎玻璃素材几乎没有 <300Hz,「被撞」的身体得自己给)。上面那组 42/104/348/615ms 的间隔当年没素材填、出来是 100ms 的死洞,这次各层起点终点都错开、任何时刻至少两层在响。

六、轴映射到什么物理量,每个 source 各不相同

不能一套参数走天下。实测:

source 体现在
wood_block 力度 响度 + 时长同步
grill_rack 速度 只有时长(620→365ms),响度几乎不变
cardboard pull_out 速度 attack + 时长 + 响度三者同步
flamethrower 火力 频谱倾斜(低频 +1.4dB,高频 −3.3dB),不是音量
long_rod 长度 × 力度 短杆的中/大力度只差 0.3dB(刚性饱和)

所以插值/外推前要先测这个 source 的轴到底动的是哪个量。火焰那条尤其典型:纯拉音量得到的是"更响的小火",耳朵分得出

七、逆向现役音效比凭感觉调快

tools/ 里那个 onset 分布分析能数出任何音频有几个事件、怎么分布、音量怎么递减。碎裂那次凭"密集瞬态"的直觉做出来的东西,时间结构跟真实碎裂是反的,光靠听定位不到"前密后疏错了"这么具体。

要对齐一个已有音效的质感时,先量它,再照着曲线生成。

八、录不了的用生成补,但只补"单点"不补"轴"

生成模型(Suno 这类)能填自录填不了的缺口:火、风、水、兽吼、金属长嘶鸣。但它给的东西结构上不是族。实测同一个 prompt 生 N 次:

                    族内亮度σ
Suno(默认 prompt)   3.9 - 8.4dB
Suno(切成原子后)    1.6 - 5.3dB
自录                 0.1 - 1.7dB

差 3-80 倍。同一个 prompt 生 6 次不是"同一个东西的 6 次",是"6 个相似但不同的东西"。所以别拿它补已有的族——一个族里混两种来源,轴的插值就不成立了。

它还给不了干素材。瞬态→250ms 后的衰减,Suno 只有 7-17dB,自录是 33-48dB —— 那 250ms 后还在响的就是它自带的房间和设计过的余韵。

三个能用的位置

用法 行不行
自录不了的单点素材(火/风/水/兽吼)
截取瞬态当原子(扔掉它的房间和余韵) ✅ 越短一致性越好
用(要力度/位置轴) ❌ 结构性做不到

第二条有实测支撑:air_whoosh(短促单一成分)σ 只有 1.6dB,已进自录区间;air_slit(持续过程)σ 5.3dB。越接近"一个瞬间",一致性越好;越是"一段过程",越各是各的。

prompt 要描述成分,不是事件

这条跟判据二是同一件事,换了个执行面:

✗  a sheet of paper catching fire, quick whoosh into crackle
     —— 这是"一张纸被点燃"这个事件,两个成分焊死在一起

✓  paper crackling as it burns
✓  a short sharp air whoosh
     —— 拆成两条,点燃时都播,持续燃烧只播第一条

拆开之后 whoosh 那条的一致性是整批最好的(σ 1.6dB)。录的时候要忍住别做完整的一刀,写 prompt 时要忍住别描述完整的事件。

模板

<一句大白话说是什么> + <1-2 个形容词> + dry recording, close mic, no music
  • no music 必写(不写会往音乐化跑)
  • dry recording / close mic 是录音术语不是规格,模型认
  • 别加时长、别加 one-shot、别堆频率描述 —— 复杂规格反而让它出错
  • 词不对时换物理描述:"wind whistling" 出不来,改 "air rushing through a narrow slit" 就有了(前者太靠近音乐化的氛围垫子)

入库前照常量一遍族内σ 和衰减,不合格的就别放进 atoms/

九、per-family 归一,绝不 per-file

每个文件各自归一到同一响度,力度轴直接废掉——轻放和重击一样响。整族用同一个系数缩放,族内相对关系原样保留,只把最响的那个对齐到目标峰值。

配套:短瞬态用 peak/RMS 不用 LUFS(ebur128 需要 ≥400ms 分析块,撞击音 300-400ms 正好在边界上,读数不可信)。

十、渲染完必须做响度归一,否则游戏里"只有 hit 那一下够响"

合成时按峰值归一(limiter 顶到 −3dBFS),峰值齐了,但平均响度可能差 25dB。耳朵听的是平均响度,而且游戏 SFX 要在 BGM + 其他音效垫底的环境里全程听得见,所以动态范围必须比"物理真实"窄。

实测本库归一前:

             LUFS      动态差
spear-stick  -33.3     27.5dB   ← 游戏里基本听不见
block        -25.2     24.1dB
block-break  -14.7     15.2dB
(现役参照)  -10 ~ -28   9 ~ 24dB

spear-stick 比现役最轻的还轻 5dB。归一到 −14 LUFS 后全库收在 RMS −15 ~ −20。

三个坑,一个比一个隐蔽

1. loudnorm 单遍对短音频会给出错的增益。 它是动态模式,2 秒以内收敛不了 —— 实测把 −14.7 LUFS 的素材"归一"到了 −21.2,反而更轻。正确做法是三步:只压缩 → 实测 → 用 volume 精确补loudnorm 只当尺子不当手术刀。

2. crest 大的素材补增益补不上去。 峰值比平均高 25dB 时,补 15dB 后多出来的全被 limiter 削掉,卡在目标以下 3dB 死活上不去。得按 crest 先选压缩比(>22dB 用 6:1 / 18-22 用 4:1 / 14-18 用 3:1),把峰值收下来才补得进去。再加一轮迭代补偿兜底。

3. <400ms 的素材 ebur128 根本测不出,会被静默跳过(补 0dB,等于没处理)。必须退到 RMS。经验偏移:归一到 −14 LUFS 的成品,RMS 落在 −17 左右。

工具:tools/normalize-loudness.mjs,ratio 不给就按 crest 自动选,LUFS 测不出自动退 RMS。

归到哪个值,取决于这个音在听感层级里的位置

统一 LUFS 只对同层级的事件成立。把过程音归到跟结果音一样响,重音关系就反了:

                        RMS      层级
命中链(impact + hit)  -18.6    结果 ← 重音
挥击起手               -15~-17   过程 ← 比结果还响 4dB,错
挥击起手(修正后)      -24~-26   跟 hit-melee 同档

实测这批攻击音照搬上一批命中音的 -14 LUFS,做出来的挥击声比整条命中链还响,听感是"发力在挥、落点没劲"。过程音(挥动、蓄力、脚步)要比它导致的结果音低 6-10dB,不然玩家的注意力会钉在错误的那一帧上。

修正时整族平移(volume -9dB),不要各自重新归一 —— 那会把族内已经调好的相对关系(钝击本就比戳刺重)一起抹平。

十一、戏剧层:判据跟物理层是反的

有一类层不对应任何物理事件 —— 锣、鼓、弦。它给的是"这一下很重要"的标记和文化质感,不是"发生了什么"。别拿物理层那套规矩套它

物理层(atoms/ 戏剧层(library/
干湿 必须干,截瞬态去房间 可以带余韵和房间
来源 自录,同一条信号链 外部 CC0 采样库
判据 受控变量、族内 σ < 2dB 许可证 + velocity 分层

戏剧层可以带房间,是因为它不在场景的物理空间里,在叙事空间里 —— 跟电影配乐不需要匹配画面混响是一回事。这条放宽了选材:不用挑干采样,也不用像对付生成素材那样只截瞬态。

层级要靠材质递进,不是音量递进

light/medium   筝    丝弦     有音高、短
heavy          钹    薄金属   炸开、无明确音高
lethal         锣    厚金属   轰鸣、长余韵

材质从丝到薄金属到厚金属,跟伤害递进天然对应。锣只给 lethal —— 它是"终结"的语义,响一次宣告一个段落结束,用在普通命中会语义通胀。

同一件事的反面:heavy 一开始用锣的 mf 档觉得弱,正确修法是换 f 档而不是给 mf 加增益。锣的力度轴上,从 mffff 高频涨 19.5dB 而低频几乎不动 —— 差别在音色不在音量,加 dB 补不出来。

要不要时间错开,取决于频段撞不撞

这条最容易一刀切错。同样是叠在打击上的乐器层:

              300-600Hz      >5k        处理
木头(hit)      -28.3        -36.7
筝             -31.4  ←撞    -47.3      延后 100ms
钹             -51.0        -42.2 ←不撞  必须对齐

筝跟木头在 300-600 只差 3dB,木头的辨识度恰恰来自这段木质共鸣,不错开就被糊住。而且不能靠 EQ 削 —— B4 的基频就坐在那,削了就没筝了,只能靠时间。

钹在 >5k,跟木头压根不抢,这时候错开反而是错的:两个瞬态挨得够近(5ms 和 20ms)才会融合成"一下",延后 100ms 直接听成两下。

有音高的乐器,先查 BGM 的调

battle / boss     D 调
battle-2 / -3     G 调
Dan Tranh 原调弦  B 大调五声(B C# D# F# G#)—— 一个音都不重合

不协和跟触发频次无关,响一次就是一次。移调后取 D 大调五声(D E F# A B) —— 在 D 调是本调,在 G 调也全落在音阶内,两首都不打架。移动量最多 1 个半音,asetrate 对音色的改变可以忽略。

查调用 Goertzel 扫 12 个半音求 chroma 就够,不必上 librosa。但音高别信文件名 —— 实测 Dan Tranh 的 C#3 基频是 D4,八度标记整体差 1;拨弦乐器基频弱、二次泛音强,检测也容易报高八度。

协和度可以编码信息

这是戏剧层能做而物理层做不到的事:玩家打人用协和音,挨打用不协和音,玩家不看数字就知道刚才发生了什么。

单音编码不了协和度 —— 不协和是音程产生的。单个 D 在 D 调是主音、在 G 调是属音,两边都稳,当"挨打"根本不成立。要难听得靠音程:D + E 同时响是大二度(最刺的音程之一),或者三全音。

三全音归到哪一边不是理论能定的:它在金属乐里是力量和侵略性的标志,所以给"攻击"比给"受击"更对。理论只能告诉你音程是什么,选哪个还得靠耳朵。

采样库的 velocity layer 就是现成的力度轴

p / mp / mf / f / fffv2/v3/v5/v7 这类分层跟自录的 f1/f2/f3 是同一个东西,可以插值、可以按伤害档位映射。这跟生成模型有本质区别(第八条:那边每次生成都是不同的东西)。rr1/rr2 这类 round robin 是同力度的不同次演奏,直接当变体池。

选库时优先看这两样,比听 demo 有用。

十二、多步手工调出来的东西压成脚本,先补回步骤边界的隐式保护

手工做一个音效通常是一步一个中间 wav:切素材 → 加效果 → 混 → 压 → 编码。 压成一条 filter_complex 之后听起来该一样,但 0820 校准劫线时实测差了 10 多 dB,追下去是两件跟音色无关的事被顺手带掉了。

中间 wav 每一次落盘都在做格式归一

手工版每步都写 -ar 48000 -ac 1,采样率在步骤之间被悄悄统一了。压成单链后 源文件的原始采样率直接透到滤镜里 —— 而有些滤镜的参数含义依赖输入采样率

elec_hum.wav 是 96kHz。asetrate=46800 的本意是「差不到半音」的微失谐
(46800/48000 → 0.44 半音),喂 96k 素材时它变成了降八度。
实测频带能量整体下移一个八度:40Hz 档 +2.4dB、320Hz 档 -7.9dB。

拍频、共振峰、任何按 48k 算出来的常数全部作废。所有声源进链之前先统一转 48k 单声道,一次转好复用,别指望链里的 aresample 兜底 —— 它在 asetrate 之后才生效,那时候已经晚了。

Sonniss 的包大量是 96k(沙类十条全是),这不是个例。

落盘也在顺手 flush 掉 ffmpeg 的竞态

vibratoamix 在同一条 filter_complex 里会吐 NaN 采样(实测 ffmpeg 8.1.1)。而且它非确定性:

  • 三路 [snap][hum][buzz]amix 出 37760 个 NaN(几乎整段),换成 [buzz][snap][hum] 就是 0 个
  • 同一条支路单独跑 48 次一次都不复现,整条配方跑起来偶发
  • 中招时是整段全 NaN,说明是启动期延时缓冲没填满就被下游拉走了

NaN 一路穿到 limiter,RMS level dB: nan,测出来的 LUFS 全是垃圾 —— 表现出来就是「增益怎么调都不对」,很容易误判成配方参数问题。

两层防:一条支路渲一个 wav 再混(落盘强制 flush,故障面缩到最小), 外加每个中间产物验一次 NaN,中招重渲

const inspect = (f) => { /* astats 读 Number of NaNs / Number of Infs */ };
const render = (args, p, what) => {
  for (let i = 0; i < 4; i++) { ff(args); if (!inspect(p).bad) return p; }
  throw new Error(`${what} 连续 4 次都带 NaN`);
};

这类 bug 不能靠事后听审发现 —— NaN 在 ogg 里可能就是一段静音或杂音, 但它先把你的测量污染了。凡是渲染中间产物,都验一次。

电平别写死常数,写测量

手工版的 volume=8.3dB 只在当时那条链上成立,改任何一段都得重调, 而且改完没人知道要重调。改成两次测量:

  1. 裸信号峰值 → 对齐到固定值再进压缩器。压缩器阈值是绝对的, 不定死喂进去的电平,压缩量就不可复现(这也是早期阴阳劫 LUFS 差 8-12dB 的根因之一:compressor 在 volume 之前)
  2. 在成品上测 LUFS 补差值。limiter 削峰和 vorbis 编码都会再改一点响度, 高 crest 的素材(湮灭)能差 0.7dB —— 只有量成品才是真的

劫线四条改成测量驱动后,跨轮次的输出逐位稳定,跟手工交付物的频带能量 偏差 ≤1dB。