你如果让一个生成式模型输出一段钢琴 MIDI,最常得到的反馈可能是:音都对了,就是不像人弹的。这不是模型偷懒,而是符号音乐生成这个任务,过去只关心“写对谱子”,不关心“怎么演奏”。“怎么演奏”这件事,在音乐术语里有一个专门的名字:Agogic。最近看到一个方向叫Agogic: Performance-Timed Music Tokens for LLM-Native Text-to-Symbolic-Music Generation,我第一反应是,它终于把表现力本身放进了 token 里。
这个方向真正值得关注的不是“给 token 加了个时间戳”这种表面改变,而是它重新划分了音乐生成里“音符是什么”和“音符怎么被演奏”的边界。过去我们默认后一件事靠渲染器、音源或者后期处理完成,而 Agogic 式做法是让 LLM 直接预测演奏过程中的时间偏移和速度弹性。用大白话说:模型写出来的不再是一张严谨但呆板的乐谱,而更像一份“有人味”的演奏轨迹。
我拿到这个标题后没有去追求某个具体训练细节,而是顺着一个工程师会比较关心的链条去拆解:数据从哪来、token 怎么编、模型怎么训练、MIDI 怎么还原、不自然时怎么查。下面这些内容,是我基于这类项目常见实践整理出的落地方案。
1. 先搞清楚:符号音乐生成缺的从来不是“正确”,而是“自然”
1.1 从 MIDI 到 token:LLM 眼里的音乐长什么样
LLM 不读五线谱,也不读音频波形,它只读离散 token 序列。把所有能表示音乐的事件转成 token,是文本到符号音乐生成的第一步。
常见的 MIDI 事件包括 note_on、note_off、pitch(音高)、velocity(力度)和时间差。要让 LLM 理解,就得把这些事件量化成有限集合。比如把音高编成PITCH=60,把每个音符起始位置量化为某个 tick,把时长也量化为 tick 数。
这类 tokenizer 的逻辑本身不算复杂,但有一个隐患:量化会丢掉一部分时间细节。很多系统为了缩短序列长度,会使用较粗的时间网格,比如把一拍切成 8 份或 16 份。钢琴里的十六分音符能落在网格上,但演奏者真实的“稍微提前一点”和“稍微拖后一点”,根本表达不出来。
1.2 为什么传统符号生成听起来僵硬
如果你听过几个早期 MIDI 生成模型的输出,会发现一个共性:听起来像节拍器在演奏。
原因是模型学到的通常是“量化后的平均结果”。训练数据里每个音符都落在固定的网格上,模型当然只会生成网格上的音符。比如同一个乐句中,人类演奏时每个十六分音符之间会有细微间隔,第一个音可能比乐谱略长,第二个音稍微被压缩,整体形成一种呼吸感。但传统 tokenizer 把这些偏移全部归零,模型学不到,生成时自然也就没有。
这很像一个人读演讲稿时把所有标点、停顿、语气词全部删掉,只剩下完整句子。信息没少,但表达没了。
1.3 Agogic 想要补上的关键拼图
Agogic 这个词在音乐里指“通过改变音的长度或速度来表现情感”,简单理解就是“时间上的弹性”。放在音乐生成里,它补上的正是传统符号表示里缺失的那一层:演奏时间信息。
Performance-Timed Music Tokens 的核心思路,就是把这些时间偏移显式编码进 token 序列。每个音符不仅有自己的音高、时值、力度,还带有一个“时机偏移量”,表示它比标准位置早了多少或晚了多少。这样一来,LLM 在预测下一个 token 时,可以同时学习“这里该弹什么音”和“这个音该怎么弹出来”。
更关键的是,这个过程是 LLM-native 的。模型不需要外挂一个规则引擎去调整演奏速度,也不需要依赖某个 DAW 的 swing 模板,一切从 token 序列中直接生成。这就是我理解的主判断:这类方案的价值不在于让 MIDI 多一个字段,而在于把“演奏表现力”从后期加工变成模型内生的能力。
2. Performance-Timed Music Tokens 到底编码了什么
2.1 音符不是只有“音高”和“长度”,还有“说话晚了半拍”
如果你和乐手聊过录音,会经常听到“这里抢了一点”“这里拖了一点”这种描述。“抢”和“拖”就是 agogic 偏移。
一个音符的实际 onset 时间,不等于它在谱面上的位置。演奏者可能在某个重音前刻意停顿几十毫秒,也可能在连续快速段落里不知不觉地赶拍子。这些微小偏差单独听不明显,但连成片段后,就是“人味”的来源。
传统符号化方案往往只记录四个维度:音高、起始位置、时长、力度。Performance-Timed token 方案至少会再加一个维度:起始时间偏移,有的还会记录时长伸缩比例或速度变化曲线。它不再试图把音乐压缩成一张标准网格,而是保留网格之外的真实演奏细节。
2.2 一个最小 token 序列示例
这里给你一个概念示例,方便理解这类 token 大概长什么样。实际工程里可能用整数词表,而不是直接写字段名:
<NOTE> <PITCH=60> <POS=96> <DUR=48> <VEL=64> <AGOGIC=+2>这个序列表示:中央 C,量化位置在第 96 个 tick,时长为 48 个 tick,力度 64,而实际 onset 比量化位置晚了 2 个 tick。
如果连续生成多个音符,模型看到的就是一串类似的结构。LLM 的任务是根据前面这些 token,预测下一个音高、位置、时值、力度和 agogic 偏移。
还可以把时间信息分成几层:
| Token 类型 | 含义 | 举例 |
|---|---|---|
| Pitch token | 音高 | C4 |
| Position token | 位于哪个量化位置 | 第 96 tick |
| Duration token | 时值长度 | 48 tick |
| Velocity token | 力度 | 64 |
| Agogic offset token | onset 偏移 | +2 tick |
| Tempo change token | 全局速度变化 | 每小节 100 BPM |
需要注意,不同实现不一定叫 Agogic,也可能叫timing_shift、onset_delta或micro_timing,但想表达的是同一件事。
2.3 为什么用 token 而不是连续数值,反而是好处
有人可能会问:时间偏移是连续值,为什么不直接输出一个 float,而要把量化为 token?
这会回到 LLM 的结构特性。Transfomer 输出层天然是一个分类器,连续数值回归对它来说并不友好。量化为多个档位后,模型预测变成了一个概率分布问题,训练目标更稳定,生成时也更好采样。
另一个好处是可控。偏移量如果只有几十档,我们可以在生成后对特定位置的 token 做编辑,比如把某个乐句所有 agogic token 归零,就能快速得到“机械版”,用来和“人味版”做对比。这种可解释性在纯连续回归里很难做到。
当然,代价也是有的。量化会损失精度,比如 2.4 个 tick 和 2.6 个 tick 可能被合并成同一档。但这个损失通常远小于传统方案把偏移完全归零带来的损失,所以从表现力角度看,依然是一次明显的增量。
3. 从论文标题到可落地流程:我建议这样验证
我看到的标题信息有限,不知道具体模型结构和训练细节。如果让我在一个真实项目里验证这个方向,我不会一上来就组织大模型训练,而是会先走一遍完整的数据管线,确认“编码之后还能不能再还原成 MIDI”。这是最容易被忽略,也最容易决定成败的一步。
3.1 数据准备:你需要的是有演奏轨迹的 MIDI,而不是纯打谱 MIDI
用 Agogic token 的前提,是数据里本来就含有真实的演奏偏移。如果训练数据全是乐谱软件导出的 MIDI,时间戳干干净净,那训练一天也学不出“人味”。
所以第一步是搞清楚数据属于哪一类:
- 乐谱型 MIDI:音符严格落在量化网格上,适合理论研究,不适合学 agogic。
- 演奏型 MIDI:来自真实演奏录制,时间戳有大量微小偏移,这才是目标数据集。
常见的公开钢琴演奏数据集,例如 MAESTRO 这类,就包含大量由真实钢琴演奏录制的 MIDI 对齐结果,适合用来做初始验证。如果你的场景不是钢琴,就需要自己从音频或演奏设备中导出类似数据。
数据准备阶段至少要做三件事:过滤异常轨道、统一音高范围、统计时间偏移分布。统计偏移分布这一步特别重要,它决定你后面的 tokenizer 应该覆盖多大范围。
3.2 编码细节:位置、时值、速度和 agogic 偏移如何量化
时间量化精度是整个流程里最核心的参数。常见做法是用 ticks per quarter(TPQ)表示每四分音符分成多少份。
低精度 TPQ 的问题很明显。如果一拍只分成 48 tick,那么很多真实演奏偏移小于 1 tick,量化后直接归零。高精度 TPQ 能保留更细的偏移,但序列长度会变长,训练成本随之上升。
一个稳妥的处理顺序是:
- 先用较高 TPQ(例如 480)解析演奏 MIDI;
- 统计所有音符起始位置相对量化网格的偏移分布;
- 根据分布在 95% 置信区间内确定 agogic token 的档位范围;
- 若偏移大多集中在 -4 到 +4 tick,就不需要把 token 范围设满整个小节。
力度和时值也建议量化,但不一定要用过细的档位。力度 0-127 量化成 32 档或 64 档通常已经足够,时值按 tick 数记录时要注意截断极端长音,避免序列里出现罕见超长 token。
3.3 模型训练:先小模型跑通,再考虑上下文和批量
在工程上,我强烈建议先训练一个非常小的模型,比如 4 层 Transformer,只生成单轨钢琴。目标不是生成惊艳音乐,而是验证 tokenizer 是否能真正复原原始 MIDI。
关键验证点有三个:
- 往返一致性:一段 MIDI 编码成 token 后,再解码回 MIDI,音符是否一致?时间偏移是否保留?
- 序列稳定性:训练时 loss 是否能正常下降?有没有因为 padding mask 不正确导致模型学会预测无效 token?
- 生成可控性:给定一个文本 prompt,模型能否生成结构完整、不会中途断裂的 token 序列?
只要这些验证通过,再逐步扩大模型和上下文,风险会低很多。
3.4 输出还原:从 token 序列回到可播放的 MIDI
生成结束后,需要把 token 序列解码成 MIDI 文件。流程通常是:
- 遍历 token,取出 pitch、position、duration、velocity 和 agogic offset;
- 计算实际 onset 时间:
实际 tick = 量化位置 + agogic offset; - 按实际 tick 排序事件;
- 对重叠音符做策略处理:是保留重叠(模拟延音踏板效果)还是切掉尾部;
- 根据全局速度映射和 tick 换算成秒,写入 MIDI 文件。
这里最容易出现的情况是:模型生成时表现力不错,但解码端在排序事件时用了一个量化函数,直接把 agogic offset 全部抹掉了。所以输出还原代码里千万不能加默认量化步骤。
注意:不要一上来就把批量数和并发数拉满,先用一条样例确认输入、输出和日志都正常。
4. 真正决定效果的不是模型,而是这些边界条件
同样一套 Agogic tokenizer,在 A 用户手里生成效果很好,在 B 用户手里却完全不行。问题往往不在模型架构,而在边界条件没有对齐。
4.1 时间分辨率:每秒/每拍拆成多少份,直接影响表现力
如果 TPQ 太低,比如一拍的 0.1% 偏移量直接消失;如果 TPQ 太高,序列会变得很长,训练速度下降,还容易让模型学出大量重复位置 token。
我建议用数据说话:先统计数据的 onset deviation 分布,再看 tokenizer 在这个分辨率下能表达多少种不同的偏移模式。如果 90% 的偏移分布范围只有 0 到 10 tick,那 TPQ 480 基本上足够;如果偏移范围非常大,就需要考虑是否让 agogic token 携带缩放系数。
4.2 上下文长度:表演风格往往藏在前后几十个音符里
一个音符的偏移不是独立决定的。乐句的开头可能整体被拖住,中间又逐渐赶上来,形成一种“先松后紧”的推进感。这种全局结构需要比较长的上下文才能捕捉。
如果模型只关注前后 8 个 token,它只能学到局部音符之间的呼应,学不到乐句级别的渐慢或渐快。我建议上下文长度至少能覆盖 1-2 个小节的 token 数,目标 512 token 以上。
如果你使用的是固定长度窗口,还要注意窗口边界不要切断连续乐句。更强的方案是加入小节编号 token 或全局段落 token,让模型即使被截断,也知道当前处于乐曲的哪个位置。
4.3 数据偏差:数据集中演奏越“规矩”,模型越难学会人性化
Agogic 不是凭空发生的,它是训练数据分布的直接投射。如果数据来自严格节拍器下的录音,那么模型学到的偏移分布会非常窄,生成结果依然像节拍器。
所以文本描述并不是万能钥匙。你可以在 prompt 里写“浪漫地演奏”,但如果训练数据里根本没有浪漫演绎对应的偏移模式,模型只能按照自己见过的模式生成。
这带来的启示是:先检查数据分布,再决定 prompt 能承诺多少东西。如果你的数据偏移范围很窄,那你在产品文案里描述“千变万化的人性化演奏”就是过度承诺。
4.4 评估:没有一个指标能替代“人耳听过 + 工程对比”
训练损失不能反映聆听体验,准确率也不能完全反映演奏自然度。我的建议是建立一套组合评估方式:
- 客观指标:音高正确率、量化位置误差、agogic 偏移分布与训练集分布的 KL 散度;
- 解码验证:生成结果能否稳定还原为合法 MIDI;
- 人工试听:至少准备 3 段真实演奏 MIDI 和 3 段生成 MIDI,让有音乐背景的人做盲听对比。
客观指标能帮你发现工程问题,但最终判断演奏是否自然,耳朵永远是第一道关卡。不要只盯着 loss 和准确率。
5. 常见问题排查:生成结果不自然时,按这个顺序查
如果生成结果听起来僵硬,不要马上怀疑模型能力,先按下面这个顺序查。
5.1 第一层:输出是不是被解码环节“抹平”了
最常见的问题是解码端偷偷做了量化。你可能在编码阶段保留 agogic,但解码时为了 “MIDI 更干净”把音符吸附回网格。
验证方法是做一次往返测试:取一段 MIDI,编码成 token,再解码回 MIDI,然后逐音符对比原始 onset 时间。如果解码后的 onset 全部变成整数网格 tick,说明解码端丢失了性能偏移,这不是模型的问题。
5.2 第二层:数据里到底有没有足够的性能偏移
用脚本统计训练数据中 agogic offset 的标准差、中位数和分布形状。如果所有偏移都是 0,那模型学不到是很正常的。
我见过不少项目用“乐谱数据”训练,期望模型自动生成人味演奏,结果怎么调参数都不自然。原因很简单:数据里根本没有这个人味,模型不可能无中生有。
5.3 第三层:上下文和位置编码是否吃到了时间信息
有些 tokenizer 只把音符位置当作序列索引,而不是绝对时间刻度。这样模型虽然能看到 agogic offset token,但它并不知道这个偏移相对的是哪一拍,或者它可能把两个不同小节的相同位置混淆。
检查你的 token 序列里有没有全局位置 token,例如bar=4、beat=2、tick=240。如果没有,模型很可能无法正确理解时间上下文,生成结果自然显得杂乱。
5.4 第四层:batch 策略和训练稳定性
如果你的 loss 曲线反复震荡,或者生成结果频繁重复,问题可能出在训练策略上。常见原因包括:
- 序列 padding 时 mask 设置错误,模型学到了填充 token;
- 学习率过高,导致微调阶段不稳定;
- 批量过小,模型无法估计真实分布;
- 数据 shuffle 不充分,同一个钢琴家的演奏曲目大量堆在相邻 batch 中。
这些都属于训练细节问题,不代表 Agogic token 方案本身不成立。先固定种子、减小学习率、缩小 batch 跑通一次,再逐步放大。
6. 我的判断:这个思路的长期价值不在音乐本身
6.1 从“打谱”到“演奏”:生成任务的重心变了
如果这个方向真的成熟,文本到符号音乐生成就不再停留在“给你一份谱子”,而是直接给你一段“可以进 DAW 演奏层的 MIDI 素材”。创作者可以用自然语言描述“这里稍微拖一点”“这句像是犹豫了一下”,模型输出的是具备这些时间特性的演奏轨迹。
这意味着后续不需要再靠人工把 MIDI 量化、调整力度、加入随机 humanize 效果。模型已经把“演奏”做完了,你拿到的是一份有呼吸感的草稿。
6.2 可以迁移到其他时间型序列生成任务
我相信这种“把微小时间偏移离散化为 token”的方法论,不只在音乐场景有效。语音合成中的停顿时长、动作生成中的节奏张弛、视频生成中的眨眼时机,本质上都面临同样的问题:主体结构可以生成,但微时序决定了真实感。
把这类“微时序”变成显式的 token 类型,让模型在生成时直接预测,是一种通用思路。它给我们的启发是:不是所有连续细节都只能用连续回归解决,有时候把它们离散成清晰的语义 token,反而更容易被 LLM 理解和控制。
6.3 适用人群和场景边界
这个方案并不是万能的。我自己判断它更适合以下场景:
- 需要快速生成钢琴独奏、配乐草稿、氛围音乐;
- 想研究 LLM 如何理解音乐表演层信息;
- 有能力准备或清洗演奏型 MIDI 数据的团队。
它不太适合的场景也很明显:
- 需要严格对齐的多轨电子音乐编曲;
- 需要实时反馈的交互式演奏系统;
- 音频级音色渲染和后期混音任务。
另外要提醒一点:符号音乐生成始终只是中间表示,最终播放效果依赖音源。即使 token 里的时间偏移非常自然,如果音源本身很机械,听感提升也有限。
这个方向最值得长期关注的地方,不是某个模型或某个 tokenizer 成为标准,而是它重新提醒了整个生成社区:音乐不止是音符的排列,更是时间中的表达。如果你想动手验证,我最建议的第一步不是训练大模型,而是先拿一段真实演奏 MIDI,统计它的 agogic 偏移分布,再想办法把这些偏移在不丢失的情况下编成 token。只要这一步打通,后续的所有训练和调优都会顺很多。