Conversation
对外文档会被客户直接阅读、或整份喂给 LLM 生成调用代码, 因此与实现不符的描述 会直接导致客户写出必然失败的调用。本次逐条核对实现后修正, 并用文档反验线上 服务(22 条声明全部实测通过)。 会让客户写出失败调用的(高优先): - NOTE 表把 pitch 标为选填, 实际 general 缺 pitch 直接 400 —— 改为 "type 为 general 时必传", 并说明 slur 可继承、br/sp 忽略; - 新增"音符不得重叠"规则(违反返回 453, 之前文档完全没有这条), 并提示这是 MIDI/DAW 导出 legato 或叠轨最常见的后果; - 5.6 原先建议用 sp/br 在音符与其 slur 之间插静音 —— 实测两种写法都 400 (sp 会被服务端剔除等价于留空隙; br 触发 "slur must follow a general/slur note")。改为: slur 必须严格首尾相接, 要断开就把 sp/br 放在 slur 之后; - 补上计费口径: 一次成功请求固定扣 1, 与片数/时长无关, 失败不计费, 因此 建议合并多片提交(此前完全未说明, 客户按单片单请求会以数倍速度烧完包量)。 客户常问但文档没答案的: - multipart 字段名 file 与多片提交方式、返回体的 output_format_suffix / sequence_index 两个字段; - 音频规格(Ogg/Opus 44.1kHz 约 64kbps)、下载 URL 有效期 48 小时、 服务端不缓存结果(重试即重新合成并重新计费); - 503 由网关在请求进入服务前拒绝, 响应体不是 JSON, 客户端不能直接 resp.json(); - 并发 20 是节点级共享上限而非单客户; qps 按 60 秒滚动窗口均值判定。 与当前引擎行为对齐: - 声线混合 7 维只有 mel 生效(引擎只有一路 speaker embedding), 传 7 维不报错; - piece_params 生效层为 pitch.user/delta 与 energy/air/falsetto/tension 的 user 层, envelope 已不支持; 补上 air/falsetto/tension 及各自值域; - delta 必须与 user 同时给出, 否则该层被忽略; - consonant_time_head/tail 不再生效; pad 可完全不传(也接受 pad_notes); - 音节输入仅中文支持(拼音或汉字), 其它语言必须给 phone; - language 补上 ko/fr/it/pt, 并修掉第2节只列3种与5.3列8种的自相矛盾; - 5.2 澄清 30~90 是推荐音乐区间, 硬校验区间是 [1,99]; - 错误码表去掉从未使用的 402, 补齐 400/453 的确切含义; - 顶部标注中国/海外两个端点, 并说明"当前引擎"条目在中国节点升级前仍按旧行为。 一致性与可用性: - 歌手表中文表补回 ID 49 Growl(此前英文 36 位、中文 35 位); - README 的 LICENSE 链接改为实际文件名 LICENCE(全仓 markdown 断链归零); - test_2b_compose.py 原用裸相对路径 examples/..., 在 api/demo/ 下运行必然 FileNotFoundError; 改为基于脚本自身定位, 任意工作目录可运行, 并把示例里的 7 维 mix_info 改为只用 mel、speaker_id 从表外的 3 改为 82。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
两个样例都存在"数据写了但引擎不读"的问题, 客户照抄会得到与预期不同的结果,
且不报错, 很难自查。
vibrato_example.aces: user 层是空数组
- piece_params.pitch 只有 delta, user 为 []。引擎侧是
pitch = where(user >= 0, user + delta, -1), 没有 user 就无处叠加, delta 被
整层丢弃。更隐蔽的是它"看起来是好的": 长音本身带模型生成的自然颤音
(实测 5.2~5.8Hz, 标准差 42~57 cent, 主峰能量占比 0.21~0.29 即真周期振动),
客户听到颤音便以为参数生效了, 实际听的是模型默认行为。
- 补上 user: 4.75~6.00s 铺一条恒为 63 的水平音高线(25 点, hop 0.05)。
实测颤音段音高标准差 61.9 -> 110.5 cent, delta 标称峰峰 364 cent 实际渲染
345 cent(约 95% 保真), 全段均值与目标音高 63 差 4.6 cent。
- 为什么让 user 覆盖整个音符而不是只覆盖颤音窗口: 两种写法都试过, 只覆盖
[5.29, 6.00] 时模型预测段与 user 段的交界处出现 +107 cent 的音高台阶
(接近一个半音的可听突跳); 覆盖整个音符则只有 +23 cent, 且那是颤音自身的
起振, 不是断点。直音段音高标准差 33.9 -> 2.5 cent, 印证 user 是硬接管。
- 顺带删掉 consonant_time_head: 文件说明 5.7 已声明它不再生效, 而全仓只有
这个文件还带着它, 留着等于教客户写死字段。
examples/param_example.aces: 新增
- 此前没有任何样例演示生效的 energy/air/falsetto/tension 曲线 ——
xiaoxingxing.aces 与日文样例把 280/482 个 energy 值写在了已废弃的 envelope
层, 全部不生效。
- 4 个同音高(65)同音节(a)的 2s 长音, 每个音符上只给一个参数做低到高的渐变,
便于逐个试听单一参数。与不带 piece_params 的基线对比(两轮独立合成结果一致):
energy RMS 首尾差 -3.9 -> +2.6 dB 自然衰减被改成渐强
air 高频/低频比 -4.1 -> +0.2 dB 气声成分随曲线上升
falsetto 高频/低频比 -4.2 -> -13.5 dB 假声削弱高次谐波
tension 高频/低频比 -10.7 -> +7.3 dB 拉高亮度, 四者中最明显
四个通道均为真实模型输入, 非死代码。
文件说明(中英双语):
- 3 节列出两个可直接提交的样例;
- 3.1 补 user 层的硬接管语义与"请覆盖整个音符"的理由(带实测台阶数值), 并说明
长音默认已有自然颤音 —— 这既回答了"要不要传 pitch", 也解释了漏传 user 时
为什么还听得到颤音;
- 3.2 补四个参数的听感作用与样例指引;
- 3.3 补 values 会被重采样到引擎帧率(约 5.8ms/帧), 故 hop_time 由客户自定,
平缓曲线用粗栅格即可; 各层按各自 start_time/hop_time 独立对齐, 不要求同栅格
(本次 user 用 hop 0.05 与 delta 的 0.0058 混用, 已实测与同栅格写法等效)。
README(中英双语): 补样例清单表, 逐个说明演示内容; 并如实注明 xiaoxingxing.aces
与日文样例中残留的 energy.envelope 历史数据当前不生效, 免得客户照抄。
单测的黄金用例按 examples/*.aces 通配, 新样例自动纳入, 24 项全过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
配合服务端把"引擎无通道的字段"从静默忽略改成明确报错, 文档与样例同步收敛。 原则: 只声明"老文档有过 && mamba 原生支持 && 转写不复杂"的交集。 声线混合: - mel 成为唯一维度, 传其余六维返回 400(此前文档说"不会报错, 只是不生效")。 并说明为什么选择报错: 静默接受会让客户拿到 200 和一段音色不对的音频, 无法从 返回值察觉。 - 指出替代路径: 想控制气息/假声/紧张度/力量的强弱用 piece_params 的对应曲线, 并明确这与"借用另一位歌手的该维度特征"不是一回事。 piece_params: - 四个参数的 user 值域统一写成 0~1(此前 energy 单独写 0~5.2)。服务端已改为与 引擎输入域一致的透传, 也与引擎回填的 synth 同量纲, 客户读回来改一改就能再发。 - envelope 层从"传入会被忽略"改为"传入返回 400", 并解释原因(它是对模型预测值的 乘性修正, 调用方拿不到那个预测值)与替代做法。 其它: - 5.2 音高: 从"建议区间 30~90 / 硬区间 [1,99]"改为"30~90 就是硬区间", 与服务端 收紧后的行为一致; 说明越界音高在引擎里会被当无效基频, 所以宁可前置拒绝。 - 片数上限 4 -> 10。文档一直写 4, 而老服务与新服务的实际部署分支都是 10。这条 直接影响客户成本: 一次成功请求固定扣 1 个额度, 照 4 片合并会多烧 2.5 倍配额。 - 两个 demo 里注释掉的 7 维 mix_info 模板改成只用 mel —— 客户取消注释即可用, 留着旧模板等于埋了个必然 400 的坑。 样例文件(只删无效字段, notes/pad_notes/pitch.user/random_seed 全部逐值未变): - xiaoxingxing.aces 与日文样例: 移除四个参数的 envelope 与空 user 层, 以及 pitch.enable_intervals。这两个文件此前唯一有数据的表现力层就是 energy.envelope (280 / 482 个值), 而它在 mamba 上不生效 —— 留着会让客户以为动态起伏来自这里, 且改成报错后会直接 400。四条曲线的正确写法见 param_example.aces。 - param_example.aces: energy 曲线按 /5.2 换算到新的 [0,1] 量纲。换算后引擎收到的 数值与换算前完全相同, 因此听感不变。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18s 是老三段式模型的限制, mamba 单次可吃约 95s。引擎按固定长度画布推理, 合成 5 秒 与 90 秒消耗的算力基本相同(线上实测: 2/6/12/18s 单片耗时 3.70/3.17/5.09/3.67s; 87s 单片 6.60s, 返回音频实测 87.005s), 所以照 18s 切碎只会让同一段音频被反复推理。 - 5.8: 上限改为 90s, 并明确另一条上限是音素总数 1200 —— 密集说唱类可能在时长 没到 90s 时先撞到它, 返回 453。这条以前完全没写, 客户会误以为只要时长够就行。 - 5.11: 原文"静音间隔不应超过 10s, 有长间隔请拆片"已不成立且方向相反。片内长静音 (前奏留白、器乐段)现在直接留空即可, 只受 90s 跨度约束。 - api_doc 限制表: piece 数量 10 -> 3, 单片长度 18s -> 90s(并指向 5.8 的音素上限)。 - 计费建议改为两层且给出优先级: 先把内容装进尽量少的文件(不要为迁就早期 18s 而 人为切碎), 再把多个文件合并到一次请求。此前只讲了后者, 会引导客户切碎后再合并 —— 额度没省下来, GPU 却白烧几倍。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
交付前用 ffprobe 实测下发的 ogg: sample_rate=48000。原因是编码链路里 Opus 原生只支持 48kHz, 服务端把 44.1kHz 的合成结果交给 libopus 时会自动重采样, 所以客户拿到的文件是 48kHz 而不是文档写的 44.1kHz。 这条对接入方是有实际影响的(缓冲区、重采样、与其它音轨对齐都按它走), 因此写明 两件事: 下发文件是 48kHz; 引擎内部按 44.1kHz 合成, 需要 44.1kHz 素材请自行重采样。 码率同时改为"VBR 目标 64kbps", 并给出长音频实测值约 70kbps(87s 样本 770312 字节 / 87.005s = 70829 bps)。原文"约 64kbps"低估了实际占用。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
.idea/ 里是 JetBrains 的本机配置(workspace.xml 记录窗口布局与打开的文件, shelf/ 是本机的暂存补丁), 换机器/换人都不通用, 进仓库只会带来无意义的冲突。 这些文件之前只是被 git add 进了索引、从未提交过(HEAD 与 main 里都没有), 所以本次连同取消注释一起把它们从索引移除即可, 不需要改写历史。 本地文件保留不动(git rm --cached)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
这份文档的读者是第一次接触本服务的人(或直接把文档喂给 LLM 的人), 他们没有历史包袱。 "早期版本支持 X, 现在不支持了"这类话对他们只是噪音: 既解释了一个他们不会用的字段, 又让人怀疑文档是否还有别的地方过时。改为只描述"现在怎么用"。 删除: - 5.7 `consonant_time_head` / `consonant_time_tail` 整节。读者从未听说这两个字段, 专门写一节说明"它不生效"没有意义。5.8~5.12 顺次改为 5.7~5.11, 交叉引用同步更新。 - 3.2 的 `envelope` 说明块。同理: 文档里不提, 读者就不会传; 万一传了, 服务端的 400 已经指出该改用 `user` 层。 - 错误码表下"早期文档中的 402 已不再使用"。 - 声线混合里"早期版本允许 7 个维度分别配比"整段, 改为直接说 `mel` 是唯一的键。 - 计费建议里"不要为了迁就早期的 18 秒限制"的措辞。 改写: - 5.7(原 5.8) 单片 90s: 去掉"从 18s 放宽"的沿革, 直接讲结论(引擎按固定画布推理, 所以一个文件装一个完整段落, 别人为切碎)。 - 第 4 节 pad: 去掉"当前引擎的实际行为""部分早期客户端使用 pad_notes"的表述。 - api_doc 顶部的节点说明: 原文让读者去看"标注『当前引擎』的若干条目", 而那些标记 已随本次清理全部删除, 成了悬空引用。改为一句话: 本文档描述海外节点, 中国节点的 引擎版本请与对接人员确认。 修正: - 两个 README 里"xiaoxingxing.aces 与日文示例中还保留着 energy.envelope 的历史数据" 已不成立 —— 上次提交已经把那些层从样例里删掉了, 这句话变成了错的。 复查: 中英文章节编号仍完全一致, markdown 内部链接零断链。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
面向第一次接触本服务的读者: 原先从零到听见第一句歌声, 需要的信息散在三个文件里, 而仓库里已有的 MIDI 转换脚本没有任何文档提到过。 快速开始(两个 README): - 四步: 取凭据 -> 选歌手 -> 准备 aces -> 提交下载。凭据在服务开通后由对接人员提供, 示例里留空。 - 附一段可直接运行的 python: 用 examples/xiaoxingxing_syllable.aces(拼音歌词, 不必查 音素)提交, 逐片下载, 并解释 pst 怎么用来和伴奏对齐。这段代码已逐行实跑验证过, 字段名与返回体完全对得上。 从 MIDI 生成 aces(两个 README): - 说明 api/demo/midi2aces.py 的用法与对 MIDI 的要求: 轨道 0 需有 set_tempo; 歌词写在 lyrics 元事件且必须是拼音(脚本按 syllable 提交, 因此目前只支持中文); 拖腔用 `-` 会转成 slur; 歌词数应与音符数一致, 缺的退化成 la; 多轨默认取第一个含音符的轨道。 - 说明脚本替调用方做的三件事(丢弃 <0.02s 的音符、按 >1.2s 空隙切片、逐片提交后按 pst 拼接), 并给出自带 红昭愿.mid 的实测: 232 音符 / 跨度 139.3s / 切成 4 片。 - 提醒脚本是一片一请求、各扣 1 个额度, 要省额度可改为一次请求提交多片。 midi2aces.py 两处修正: - MAX_LENGTH 18 -> 90。单文件上限已放宽到 90s, 继续按 18s 切只会多切出片段、 多消耗额度(红昭愿.mid 实测 5 片 -> 4 片)。 - corase_cut 的收尾写在 `i == len-1` 分支里, 因此**只有一个音符时会丢掉全部音符** (返回 0 片)。改为循环外统一收尾。真实 MIDI 的切分结果不变(仍 4 片 / 232 音符)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
此前一次改动把语言列表扩到 8 种(增加 ko/fr/it/pt) —— 引擎确实加载了这些音素表、 实测也能合成, 但歌手表里没有任何一位这些语言的原生歌手, 对外声明等于承诺了一件 没有产品支撑的事。现在收回到中/日/英/西四种。 西语保留: 老文档 5.3 本就写了 spa, 仓库历史里有两次"增加西语歌手"的提交, 歌手表中有 11 位西语原生歌手(中文 13 / 英文 12 / 西语 11)。 顺带把 5.3 写清楚: 原文只列代号不给中文名, 且第 2 节的表格与 5.3 的列表两处口径 要对得上。现在两处都写成"ch 中文 / jp 日语 / en 英语 / spa 西班牙语"。 注意: ko/fr/it/pt 在服务端仍会被接受(实测返回 200), 只是不再对外声明。 这不会让调用方拿到意外结果 —— 传什么语言就按什么语言合成, 只是没有原生歌手、 质量不作承诺。若希望连接口层也一并拒绝, 改 twob/g2p.py 的 LANG_ALIAS 即可。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
此前选歌手只能靠"流行、温暖"这类四字描述猜, 是接入流程里最大的摩擦点之一。 样音由线上服务实际合成, 共 2.1MB(每个约 59KB / 6.0 秒), 放在 api/docs/singer_demos/<歌手id>.ogg, 中英两张歌手表各加一列试听链接。 设计上让样音**可横向对比**: 同一原生语言的歌手唱同一句、同一旋律。 - 中文 13 位: "春风吹过山河"(拼音音节输入) - 英文 12 位: "let the music carry" - 西语 11 位: "camino de la luz" 女声在 G4 附近、男声在 A3 附近起唱(同一句在不同性别上转调, 否则一半歌手会唱在 不舒服的音域); 末音延长 2 秒, 便于听持续音的质感而不只是咬字。 英语与西语的音素在提交前已逐个与引擎音素表(en_plan / es_plan 的 phon_id)核对, 并确认每个音符恰好一个元音(phon_class.tail), 因此这三句本身也可作为 "怎么写 phone 数组"的可运行范例。 生成后全部 36 个文件核过电平(平均 -19.5~-26.8 dB, 峰值 -3.3~-13.9 dB), 无静音; 表里 36 个链接与目录内文件一一对应, 无缺失。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
这是接入流程里最大的功能性缺口: 文档一直写着"其它语言请直接给 phone", 却没有任何 工具或对照表。中文有 syllable 兜底, 英/日/西的客户必须自己去 ACE_phonemes 仓库逐词 拼音素 —— 实际结果是非中文客户无法自助接入。 三种语言的资源情况完全不同, 因此做法也不同: - 英语: 引擎的 45 个音素就是小写去重音的 ARPAbet(39 个标准 ARPAbet 全在), 所以 CMUdict -> 引擎音素是 1:1。难点是音节切分(引擎按 note 组织, 一个音节一个 note), 用最大起首原则处理元音间的辅音串。依赖 pip 包 cmudict。 - 日语: 引擎自带 349 条罗马字音节词典, 核心是查表。内嵌一张五十音表(含浊音/半浊音/ 拗音/促音/长音)做假名->罗马字, 不引入 pykakasi 之类的依赖。 - 西语: 既无词典、77 个音素又带大量含义不明的变体后缀。做法是先用 it/pt/fr 的音素表 交叉比对确认命名是 X-SAMPA 变体, 再用线上 6338 个真实西语 note 的分布反证, 只保留被实测证实的 44 个音素, 其余 33 个从不输出。 一条贯穿的原则: **查不到就报错, 不按拼写猜**。猜错的发音客户很难排查, 报错至少能 立刻定位到是哪个词。 生成后做过一轮对抗性复核, 修掉的都是"静默错音"类缺陷: - 英语 "aaa" 会命中 cmudict 的 "triple-A" 词条变成 3 个 note(zzz->"zee"、 www->"double-you" 同理), 而这类写法在歌词里极常见; "naïve" 被无声切成 "nah vee", 且同一字符串的 NFC 与 NFD 形式行为不同。现改为 run 不查词典 + 入口 NFKD 归一。 - 日语 ふ 原本输出 ['f','u'], 但线上 4716 个真实 jp note 里 f 出现 0 次、h 出现 109 次, 实际读法 100% 是 ['h','u']。f/ty/dy/v/vy 五个零先例音素改为走有先例的传统近似, 字面读法移到 allow_unattested=True 开关后面。 - 西语不发音的 h 在二合元音判定前被删, ahumar 等 9 个词少一个 note; 词尾 ll 输出 jsl(腭擦音作韵尾在西语音系里不可能), roll/bill/full 全错; rock 出叠辅音 ['k','k']。 验证: 29 组测试向量逐字节复现; 输出音素全部 ⊆ 引擎音素表; 每个 note 至少一个元音; 英语在全量 cmudict 126052 词条上跑出 309769 个 note, 非法音素 0、零元音 note 0、 崩溃 0; 三种语言各自端到端合成出音频(HTTP 200)。 顺带更正 twob/validators.py 的 check_vowel docstring: 写的是"必须正好有一个元音", 而实际判据 `if not vowels` 是"至少一个"(两个及以上会放行, 线上西语工程里确实存在)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
拿到一个真实付费账号(海外, 按量计费)实测后的两处修正: 1. 额度查询地址中英两版原本各写一个(中文写 gateway、英文写 gateway-us), 读者只看一版 就会用错节点。现在两版都并列给出中国与海外两个地址, 并点明它与合成接口不同源 —— 这一点容易被漏掉, 因为合成接口的两个地址在文档开头已经列过一次。 2. 新增: **额度是异步扣减的**。实测合成返回 200 后, used_amount 要几秒到十几秒才更新; 6 秒时读还是旧值。客户"提交完立刻查额度发现没变"会以为没计费, 这是本次实测中我 自己先踩到的坑, 值得写进文档。 同时用真实账号验证了此前只从代码读出来的两条计费声明, 结论与文档一致: - 一次成功请求固定扣 1: 三片合并在一次请求里提交, used_amount 只 +1 而不是 +3 - 失败不计费: 返回 453 的请求 used_amount +0 额度返回体的 8 个字段(service / flag / token / charging_strategy / charging_expire_time / billing_balance / used_amount / qps)与文档完全一致; 另有 extra 与 is_delete 两个字段未在文档列出, 属内部字段, 未补进文档。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.