给八个世界各写一首主题曲,顺便滚出了八十位虚构歌手
本文目录
上一篇讲了 Outland 的世界观是个缝合怪。
这篇讲它的音乐——因为这个项目做到现在,最完整的一块产出其实不是代码,是音频。
而且它开始的时候,根本不是冲着”写歌”去的。
一、我只是想搭个工具
当时的需求特别朴素:游戏需要一个能出声的环境和一个音效库,我想在本地跑一个音乐生成模型,这样以后要什么音乐自己生成,不用满网找素材。
所以我去部署了两套:
- ACE-Step 1.5 turbo(跑在 ComfyUI 里)——主要生成有歌词的歌
- Stable Audio 3——纯器乐和音效
两套都通了,实测下来 ACE-Step 生成 45 秒 48kHz 立体声大概花 15 秒,Stable Audio 出一段 4 秒音效大概 6 秒。工具能用了。
然后我顺手用它批量生成了一批环境音和音效。
这是我犯的第一个错。
我有一个挺明确的习惯:让工具跑通之后,总想”顺便”再往前一步,看看它到底能做到什么程度。结果就是我在没想清楚要什么的情况下,先产出了一批东西。
那批东西质量其实不差,我自己听也觉得还行。但方向不对——我要的不是环境音循环,是每个世界一条完整的主题曲。
这批次生成了十八来条,最后全部封存,留在硬盘里没删(说不定以后哪天用得上),但项目里一个都没用。白白烧掉的时间不算,还多花了不少算力钱。
教训记下来了:工具跑通就停,需求没想清楚之前不要批量生产。 这句话后来被我写进了项目的 AI 协作守则里。
二、八首主题曲
痛定思痛,把需求想清楚了再动手。
规格:八个世界各一首完整主题曲,用在主菜单”选择世界”和跨世界穿梭的选择界面上。
定了几条硬规矩:
| 项 | 要求 |
|---|---|
| 形式 | 完整曲子,不做循环切分 |
| 长度 | 至少 60 秒,不设上限(实际都在 145~200 秒) |
| 人声 | 纯器乐,无人声(蒸汽朋克例外,用圣咏当织体) |
| 独立性 | 八个世界各自独立,不共享任何动机 |
| 响度 | 峰值 −6dB,48kHz / 16bit / 立体声 |
关于”不共享动机”这条——我一开始犹豫过。八个世界如果有一小段共同的旋律碎片,穿梭的时候会有”回家”的感觉。
后来否了。因为八世界的定位是互不相干的独立位面,被大裂隙硬拽到一起的。它们之间没有血缘关系,共享动机反而会让它们显得像同一个世界的八个区域。各写各的,才符合”八个世界被强行缝合”这个设定。
八首的主题分别是:
| 世界 | 风格方向 | 时长 |
|---|---|---|
| 赛博朋克 | dark synthwave,冷酷霓虹,脉冲贝斯 | 196s |
| 现实 | 电影感暗氛围,孤独钢琴 + 低音大提琴持续音 | 180s |
| 蒸汽朋克 | 维多利亚管风琴 + 管弦乐 + 机械鼓轨 | 193s |
| 废土 | 芝加哥电布鲁斯,失真吉他 + 放大口琴 | 173s |
| 奇幻 | 魔法门风格,大键琴动机 + 全管弦 + 合唱 | 173s |
| 仙侠 | 中式器乐,古琴古筝 + 笛子二胡 + 编钟 | 176s |
| 太空歌剧 | 《星际牛仔》Tank! 风格的 Funk 硬波普大乐队 | 169s |
| 克苏鲁 | 暗黑管弦,低音持续音,大教堂混响 | 180s |
有一首我特别想说:蒸汽朋克那首。
它没法用常规方式做。我想要的是”蒸汽阀门喷气 + 金属碰撞”那种质感,但ACE-Step 只能用文字去描述,让模型”演奏”这些拟声,拿不到真正的采样音色。
所以走了条野路子:
ACE-Step 生成一个无人声底子(管风琴 + 管弦 + 圣咏织体)
↓
Stable Audio 3 生成一堆机械音色(阀门、铁砧、齿轮、活塞……)
↓
把每段音色切成单次打击,做成一套采样鼓
↓
按固定 BPM 铺鼓轨,再和底子混音
采样套鼓里每个音色都是从频谱上挑出来的:
| 音色 | 频谱质心 | 低频占比 | 用在什么位置 |
|---|---|---|---|
| 金属板共振 | 489 Hz | 74.5% | 重拍(最厚) |
| 厚重铁砧 | 1027 Hz | 65.0% | 弱拍 |
| 铸件铁块 | 1143 Hz | 41.0% | 中音撞击 |
| 活塞汽缸 | 2934 Hz | 33.9% | 切分点缀 |
| 齿轮咬合 | 3129 Hz | 29.2% | 十六分碎拍 |
| 铁链拖曳 | 2597 Hz | 22.3% | 段落过门 |
| 蒸汽阀门 | 11119 Hz | 0.4% | 每拍前导瞬态 |
这里有个挺有意思的原理:蒸汽声是宽带噪音,只有瞬态没有音高,单独用根本不成鼓点;而金属碰撞提供音高和实体感。两者叠加才是一个完整的 kick。
所以”脆”和”厚”这两个听感其实是可测量的——质心高就是脆(高频多),质心低就是厚(低频多)。
三、然后它失控了
八首主题曲做完了,按理说该收工了。
但游戏里不是只有主菜单。玩家在世界里探索的时候得有点背景音乐吧?一个世界走进去,总不能从头到尾听同一首主题曲循环。
所以有了第二个需求:每个世界需要一套游戏内电台/BGM。
这里我定了一条规则,是整件事后来滚起来的直接原因:
不同风格的音乐 = 不同歌手。
意思是,我不做”每个世界一套纯器乐 BGM”,而是给每个世界虚构一批歌手/乐队,每位歌手有自己的专辑。
因为不同风格本来就不该是一个人唱的——你不可能让同一位歌手既唱 1930 年代的三角洲布鲁斯又唱电音,那太假了。一个世界有几位甚至十几位歌手,各有各的风格,各有各的专辑。
结果数量是这样:
| 世界 | 歌手/乐团 | 曲目 | 语言 |
|---|---|---|---|
| 奇幻 | 20 位 | 100 首 | 英/拉丁/德/俄 + 无词 |
| 废土 | 10 位 | 42 首 | 英/西 + 无词 |
| 科幻 | 10 位 | 40 首 | 中/日/英/粤 + 无词 |
| 赛博朋克 | 10 位 | 40 首 | 英/日/中 + 无词 |
| 蒸汽朋克 | 10 位 | 40 首 | 英/拉丁 + 无词 |
| 现实 | 10 位 | 40 首 | 英/中/韩/西/阿/法/印地 七种 |
| 仙侠 | 10 位 | 40 首 | 中文 |
| 合计 | 80 位 | 342 首 | 14 种语言 |
(克苏鲁不做歌手专辑——那个世界我不想给它加”人味”的东西。)
三百四十二首歌。这个数字现在回头看有点离谱,但当时是一世界一世界推过去的,每一步都觉得”再来一位歌手不多吧”。
四、每个世界的电台,其实是它的社会切片
虚构歌手这件事,一旦认真做,就会变成一个用音乐写世界观的过程。
举废土世界为例。它最后长出了十位歌手,而且天然分成了三个时间层——这不是我硬凑的,是不同的音乐风格自己带来的:
| 层 | 歌手 | 录音年代 | 在废土上是什么 |
|---|---|---|---|
| 战前·三角洲布鲁斯 | 盲眼·约伯·塔克 | 1939 | 只存在于旧唱片里,人早没了 |
| 战前·流行 R&B | 艾迪·万斯与月光四重唱 | 1951 | 同上,旧唱片 |
| 战后·活人 | “深水”·托利弗与求索者乐队 | 战后 | 还活着,还在唱,在聚居点之间游荡 |
这个层次的意义是:废土的电台里,三十年前的旧唱片和昨天在防空洞录的新歌会混在一起播。
而世界观里写的那些设定,全部变成了歌:
- 设定写着”烧汽油的是穷鬼、用核能的是权贵”。于是有了两首正面对撞的歌:穷鬼韦德那首《The Atom Boys Don’t Wave Back》(我冲核能车队挥手,司机看我的眼神像看脏东西),和权贵乐团”原子钟”那首《Space Age Bachelor Vault》(太空时代单身避难所,宝贝 / 一间卧室,一颗炸弹)。
- 设定写着”幸存者把核战前遗迹当神话圣地,对着残破的可口可乐广告牌叩拜”。于是避难所第三代合唱团唱了首《The Sign of the Red Circle》——他们把那块红圆标当圣物,因为读不懂上面的字。
- 设定写着”玩家的躯体也能卖”(走投无路时可以出卖肢体/生命值换资源)。于是丧尸族的”收尸人”唱了首《You Have Two Hands》。
科幻世界也一样:一位星际牛仔唱爵士布鲁斯,一位打捞客唱粤语工人民谣,空间站驻场唱日文 citypop,殖民地工人唱中文工业电子,还有一位是空间站 AI 用声码器唱歌,最后一位是 40 年代的老唱片 crooner——因为月亮爆炸被政府宣传成都市传说,老唱片成了唯一还记得真相的东西。
赛博朋克那边更直接:有一首叫《第十次》(赛博精神病),因为设定里”人性第十次降到 0 必然触发赛博精神病”;还有一首是财团广告部的中文洗脑电波,这个就是纯坏心眼了。
五、踩过的坑
音乐这块的技术细节,踩的坑比代码那块有意思。挑几个说。
1. AI 不按你要的 BPM 生成
我想给一首曲子铺鼓,就按模型请求的 112 BPM 去铺。
结果实测:前 34 秒确实约 113 BPM,34 秒之后变成 167~174 BPM。
我按请求的 BPM 铺鼓 → 鼓和音乐成了 3:2 拍的关系 → 听感上就是”打架”,节奏感全毁。
AI 音乐模型不保证按你请求的速度生成。 这个必须实测验证,不能信参数。
(不过反过来说:如果走”鼓自成拍”那条路线,底子没有拍点反而是好事,因为鼓是唯一的节奏来源,不存在冲突。所以体检的目的是判断”底子是不是自带一个会跟鼓打架的节拍”。)
2. 内部静音 = 生成失败
首尾有静音是正常的,但曲子中间出现数字静音就是废品。
我用 50 毫秒窗口的 RMS 扫全曲,排除首尾各 2 秒之后还有低于 −50dB 的段落,就是废品,得重新生成。实测过的几例:
- 一次 40.9 秒的内部静音
- 一次 53.8 秒
- 一次 14.6 秒——这次是我自己听出来的,工具还没跑到
实测残品率大概四分之一。 批量生成必须带这项检查。
3. 指标不能代替试听
这条是最重要的。
能测的东西:内部静音、响度、开头是不是太闷、段间频谱变化。 测不了的东西:旋律好不好听、有没有抓耳的主线、重复感重不重。
我实测过一个反例:一首我觉得还行、用户也认可的版本,段间相似度是 0.986;而另一个被否掉的版本反而不相似度更低(0.893)。
指标高不等于好听。
所以我现在不拿指标去否掉任何一首被人说好的曲子。指标只用来筛”明显是废品”的(静音、暴音、响度异常),好不好听这件事,只能靠人耳朵。
4. 输出长度用 round() 不要用 int()
很小但很典型的一个:
120 / 4.1379375 = 28.999…
int() 截断成 28 小节 = 115.86 秒,整整短了 4 秒。round() 才是 29 小节。
这种错误在音频上特别隐蔽,因为 4 秒的差别听起来只是”稍微短一点”,不会报错。
六、一些数字
最后交代一下这块的家底:
- 八世界主题曲:8/8 定稿(2026-10-01 克苏鲁用 Stable Audio 3 收尾)
- 虚构歌手专辑:7/7 完成(克苏鲁按规则不做),80 位歌手 / 342 首 / 14 种语言
- 音频文件总体积:粗算 15 GB 上下(每首都有 48kHz/16bit 立体声的 wav 给引擎用,和 mp3 给人试听)
- 被否掉的版本一律不删,移进
_alternates/——因为用户说过”留着,后面可以放在游戏里做各种曲子” - 每首纯器乐都配了 mp3 试听版,方便人耳逐个过
七、两个后知后觉
写完这段回头看,有两点当时没意识到:
第一,音乐这块最后比代码更完整。
引擎那边还在补系统,八世界里只有前几个真正铺了内容。但音乐这边——八首主题曲全定稿,七套歌手专辑齐全,规格统一,文件齐整。一个游戏项目最先做完的居然是它的原声带,这事有点荒唐,但它就是这么发生了。
原因是音乐可以”逐个定稿”:一首做到满意再开下一首,经验带到下一首。而代码是咬合在一起的,改一个系统会带动一片。
第二,“不同风格 = 不同歌手”这条规则,比我想象的更值钱。
它一开始只是为了解决”纯器乐 BGM 太单调”这个技术问题。但它带来的副作用是——每个世界的音乐突然变成了那个世界的社会切片。电台里在播什么,就说明那个世界里谁在活着、流行什么、记得什么、被谁洗脑。
赛博朋克有财团广告部的洗脑歌,废土有对着广告牌叩拜的赞美诗,科幻有被当成都市传说的月亮——这些设定不用写进日志文本,光听歌就知道了。
下一篇想写点纯技术的:这套引擎本身是怎么搭的,还有为了对齐 CDDA 那些模拟系统踩的坑。