agentleFS
Akunda123Akunda123/SVIXAGENT/skills/akdagent-playbook/SKILL.md

akdagent-playbook

Skill33 starsChanged 36 days agoMITView on GitHub

AKDAgent 实战踩坑与交接手册 —— 在 Synthesizer V Studio / Instrument X 上干活时的**开工自检、主动提醒清单、以及踩过的坑**。含:连桥自检与跳过条件 · 多 SV 只留一个桥 · 连上后按工程状态(空工程/无伴奏轨/无音符)与宿主(SV/IX)给提醒 · BPM/音频轨/音符/和声逐条问(含 **和弦分析**、**MusicXML 乐谱导入**)· 加音频与改 .svp/.ixp 的安全三步(先保存→重写→重载)· 产物存放位置(Agent 目录/指定目录/随工程)· 内置库不满意时给更强替代 · 歌词专项(检测/搜索/对齐/作词七项)· ⛔ **语言(语种)纪律(AKDAgent 整体:默认不改、用户更懂声库语种、语种不兼容⇒无音素⇒不渲染)**· 音高参数三层检测与调声交接(全自动 or 重点音符、我们做 or 用户做)· 其他参数**按风格**问 · **声线(vocal mode)交接**与**声库 styles / 声线音色**处置 · **IX 上用和弦自动创建织体**(成套配方见 **`sv-texture`** 技能)· 桥与文件通道故障(版本不生效/轮询死/弹框冻桥/id 匹配/目录不一致)· 文档与工具纪律。触发:接 SV/IX 任务不知道从哪开始、桥连不上或行为诡异、要加音频/改工程/分离伴奏/转录、要**分析和弦**或**用和弦铺织体**、要**导入乐谱(MusicXML)**、要调声但没参数、参数是否该动、vocal mode 读不到、要动**声库 styles / 声线音色**、文档/编码/工具踩坑等

  • Installs packages

The file

Files
SKILL.md

AKDAgent 作业手册 —— 开工自检 · 交接规范 · 运行

怎么用这份手册

  • 接着任务时先看「零」 —— 它是一条开工流程(查桥 → 按意图决定要不要桥 → 看工程状态 → 按宿主给提醒 → 按需往下问)。
  • 出问题时(报错 / 行为诡异)直接跳到「一」~「五」找对应条目;每条按 现象 → 原因 → 怎么查 → 怎么修 写。
  • 口述条目与实测条目分开标注(见上),口径冲突时以约定为准。

零章导航:0.1 查桥 + 跳过条件 · 0.2 多个 SV 只留一个 · 0.3 连上后看工程状态并提醒(含按宿主分叉)· 0.4 逐条问(BPM / 音频轨 / 音符 / 和声)+ 0.4a 加音频改工程的安全三步 + 0.4b 产物放哪 · 0.5 内置库不满意时给替代 · 0.6 调校程度(① 歌词 · ② 音高参数 · ③ 其他参数与声线交接)· 0.7 提醒的排序原则(创造性项排最后)。

零、开工前自检(每次会话先做)

⚠️ 先分清用户在哪个 app 里干活:SV/IX ⇒ 下面的桥自检照做;ACE Studio ⇒ 别查桥 (它没有桥,见 §四 开头那段),直接确认 ACE 在线。ACE 侧现成工具:ace_state(我们 MCP server 的 ACE 开工自检: 在线状态 + 工程名/路径/dirty/isTempProject + CLI 与技能脚本就位情况)· CLI 口径 ⚠️ project dirty --json 才有 projectPath/dirty(project info 只给 name/临时状态/时长)。没有 MCP 工具时用 mcp-bridge.json / acestudio-cli。

0.1加载完 DSH 后先查桥:没连上就主动提醒用户去开 AgentBridge 脚本

  • 现象:用户刚加载完 DSH(或刚重启 web host / 换宿主)就来干活,此时桥往往还没跑 —— 任何 op 都会报"桥不在线",白等一轮。
  • 正确做法: 加载完 DSH 后,若发现没有连上桥,就主动发一条消息提醒用户去打开 AgentBridge 脚本(在宿主里运行:脚本菜单 → Agent → AKDAgent Bridge)。 —— 不要等用户先撞上报错才发现;先提醒,再干活。
  • 怎么判"没连上"(技术判据,任一成立即视为没连上):
    1. 读不到心跳文件 akdagent-hb-<host>.json(%USERPROFILE%\AKDAgent\ipc,不存在则 %TEMP%);
    2. 心跳不新鲜(heartbeatAgeSec > 15);
    3. ping 不通 / 无匹配响应(客户端按 id 匹配,超时即判定不在线)。
  • 怎么修(提醒用户的话术要点):请在某宿主(SV1 / SV2 / IX)里运行 sv/lua/AKDAgentBridge.lua(脚本菜单 → Agent → AKDAgent Bridge); 装桥用 sv/install.ps1(会同时装到 4 个宿主目录)。⚠️ 一次只开一个 SV 桥(SV1 与 SV2 共用 -sv 心跳文件,同开会互相覆盖、同一条请求可能被执行两次)。
  • 顺带要报的:桥的版本(heartbeat.bridge,如 1.0.0)与宿主(hostName/isSV2)—— 版本旧了要提醒用户重跑桥(Lua 脚本常驻,改完源码不重跑不生效)。
  • ⏭️ 跳过条件(以「意图」为准):当用户的意图明显不在宿主上时,跳过这一步。 —— 例:"帮我提取伴奏" / "分析这首的 BPM" / "改一下这个 .svp 文件"。判断依据是「用户想干什么」,不是「我们打算调哪个工具」(工具清单只是参考,见下)。
    • 意图"在宿主上":要对 SV/IX 工程里的东西做增删改查 —— 音符、音符组、歌词、音高线、速度标、音频轨位置、播放、或在宿主里跑脚本。
    • 意图"不在宿主上":本地音频处理(分离/分析/转码)· 工程文件本身(.svp / .ixp)· 声库文件 · 纯乐理生成。这些没有桥也照跑,提醒开桥只是打扰。
    • 不需要桥(本地就能跑):sv_separate_vocals / sv_separate_vocals_dual(分离伴奏——工具同时给出人声轨,但 SV 的人声是要合成的,默认意图是伴奏)· sv_analyze_audio(BPM/节拍/调性/响度)· sv_analyze_chord · sv_analyze_emotion · sv_convert_audio(转 44.1k WAV)· sv_generate_melody(本地乐理生成,结构引擎 v2:主题发展/句法/气口/终止落音/单一高点/自动压掉避免音)· sv_check_melody(旋律判据体检:三证据/方向性/高点/避免音/气口/密度/落音 —— 输入可来自 SV 当前组(这条要桥)或直接给音符(不要桥);生成后拿它验收)· 声库 styles 系列(list_voice_styles 列全部声库与其 styles · list_voice_styles_detail 看某声库的 styles 详情 · style_create 新建 style【复制已有 style 或随机 + 微调】· style_adjust 微调已有 style 的浮点向量 · combine_voice_styles 把源声库的 styles 移植到目标声库【需同 vocoder】;都读写声库文件、写前自动 .bak)。 ⚠️ 仅限 flat 版本:这套 styles 能力只在 flat 版上成立 ⇒ 用之前先检测是不是 flat,或者等用户主动提出再做;不是 flat 就别主动提、也别承诺。 检测判据:看声库目录里 nofs 文件的"内容格式" —— 能当 JSON 读 ⇒ 是 flat 版(可以操作);是特定的加密格式 / 非加密的二进制格式 ⇒ 不是可操作的那种。 ⚠️ 不要用"目录里有没有 nofs"或"有没有 .svpk"来判断(表述有误表述有误)—— 判据是内容格式,不是文件是否存在。 实操:按 JSON 试着解析(或先看头几字节是不是 {/[);解析失败 ⇒ 判为非 flat,不要尝试改写。 ⭐ 以及 .svp / .ixp 工程文件的读写与编辑—— 那是对磁盘上的 JSON 文件做操作,纯文件层,不需要桥、也不需要宿主在线上(工程结构/字段/单位见 sv-project-format 技能)。 ⚠️ 配套注意:宿主正开着同一个工程时,别在它底下直接改那个文件 —— 宿主保存时会用内存里的内容覆盖你的修改。稳妥顺序:先在宿主里关掉该工程(或另存一份)→ 改文件 → 再让宿主打开;或改完立刻让宿主重新加载。
    • 需要桥(要跟宿主工程说话):ping / get_project_info / get_current_group / get_selected_notes / get_melody_notes / get_note_time / get_measure_info / write_pit / write_chords / create_harmony_group / fill_track_lyrics / apply_lyrics / set_selected_lyrics / transpose_selected_notes / apply_tempo / align_audio / playback / run_script / import_musicxml(乐谱导入,写工程 ⇒ 要桥)/ stop / selftest。
    • 判据(一句话,按意图):用户这次想动的是「宿主工程里的东西」吗? 是 ⇒ 先查桥、没桥就提醒;否 ⇒ 直接干,别提醒。 (⚠️ 不要因为"我准备用某个需要桥的工具"就去催用户开桥 —— 先看用户要什么;真到了要读/改宿主那一步,再查桥也不迟。)
    • 混合任务(如"提取伴奏 → 再对齐进工程"):做本地那半段时不用桥,等真的要到"进工程/读工程"那一步再查桥 + 提醒。不要一上来就催用户开桥。

0.2检测到多个 SV 时,提醒用户"只能留一个开桥"

  • 现象:用户把 SV1 和 SV2(或 OPSV Flat)同时开着、都跑了桥 —— 表面上一切正常,实际会出诡异问题。
  • 正确做法: 如果检测到多个 SV,就提醒用户只能留一个开桥(关掉其余宿主里的桥脚本实例)。
  • 为什么必须只留一个:SV 家族的所有实例(SV1 / SV2 / OPSV Flat)共用同一套通道文件 —— 心跳 akdagent-hb-sv.json、请求 akdagent-req-sv.json、响应 akdagent-res-sv.json(通道名按"宿主种类"分 sv/ix,不区分 SV1/SV2)⇒ 同时开会: ① 心跳互相覆盖(谁最后写谁赢);② 请求"谁先轮询到谁应答";③ 两实例的 id 去重状态各自独立 ⇒ 同一条请求可能被两个宿主各执行一次(例如 write_chords 在两个工程里各建一次组 ⇒ 出现重复产物)。
  • 怎么判"有多个"(任一成立就该提醒):
    1. 心跳来回跳:两次读之间 hostName / hostVersionNumber 变了、或 bridge 版本、seq 回退;
    2. 同一条请求出现两份结果,或工程里冒出重复产物(两个同名组 / 同一段被写两遍);
    3. 直接数进程(Windows):有多个 Synthesizer V Studio*.exe(含 OPSV Flat)在跑 —— 这是最直接的信号;
    4. 用户自己说"我两个都开了"。
  • 怎么修:提醒用户只保留一个开着桥(在多余的那个宿主里停掉桥脚本:跑 stop op,或直接关掉那个宿主)。 ✅ IX 与 SV 可以同时开(-ix 是独立通道,互不干扰),冲突只在 SV 家族内部。
  • 已记在哪:🔒 约定「一次只开一个 SV 桥」(本条是它的"开工自检落地";将来若做了多实例支持,需要 -sv2 后缀 + 响应来源校验,届时本条改写)。

0.3连上桥后先看工程状态,再按状态主动提醒用户下一步

连上桥之后,先检测当前工程状态 —— ① 有没有伴奏轨 ② 有没有音符 ③ 是不是空工程 —— 然后按下面三种情况主动提醒:

检测到的状态提醒什么(口述要点)
空工程(既没音符也没音频)提醒用户:打开一个工程 / 导入 MIDI / 导入伴奏音频 —— 三选一,问清他想走哪条
没有伴奏轨(有音符、无音频轨)提醒伴奏编辑可做的事:WAV 格式转换(sv_convert_audio)、分离伴奏(sv_separate_vocals / _dual)<br>⚠️ 措辞就用"分离伴奏" —— 人声在 SV 里是要合成的;工具虽然一并分离出人声,但用户要的是伴奏,别默认他要人声
没有音符(有音频轨、无音符)三个选项都要给:① 导入 MIDI / 音符 · ② 要不要从伴奏分离(⚠️ 提醒用户用 SV 自带的转录功能 —— 音频转音符这件事我们的脚本 API 做不到)· ③ 询问是否需要作曲编曲(走 composition 技能 + sv_generate_melody / sv_write_chords)<br>另:导入音乐也支持 MusicXML 乐谱 —— sv_import_musicxml(从 MuseScore 之类导出的 .musicxml/.xml 进工程;✅ 导入护栏 2026-09-21 已整组落地:绝对路径限定 / 拒 URL·相对路径·.svp / 拒 DOCTYPE·ENTITY / 默认只读预览(给 SHA-256 + 声部清单 + 有界预览,dryRun:false 才写)/ 写前重算哈希 / 512 音符上限 / 写必须 confirmRights:true / 源 tempo 只汇报 / 复调·和弦默认拒(allowPolyphony:true 放行);离线 )

⚠️ 提醒方向要按宿主分叉:

宿主提醒往哪靠
SV1 / SV2(有声库、能唱)就按上表 —— 旋律 / 歌词 / 咬字 / 调声 / 转录 / 分离伴奏
IX(Instrument X,乐器宿主)往「编曲与乐器」靠拢:导入 MIDI · 建乐器轨 · 配和弦与琶音(sv_write_chords)· 和声/副旋律(create_harmony_group)· 音色与配器 · 多轨编配与混音 —— 主推 composition 技能。<br>⚠️ 不要提歌词类的事:IX 没有歌词(它没有声库去解释),fill_track_lyrics / apply_lyrics / set_selected_lyrics 在 IX 上不适用,提了就是误导。<br>⚠️ 同理别提"调声/咬字/唱法",那是 SV 的活。

判宿主看心跳的 hostName(如 Instrument X)与 isSV2;IX 的 API 与 SV2 同构(isSV2=true),但语义上它是乐器宿主,所以提醒方向要换。

怎么检测(用现成 op,只读、别乱动工程):

  • get_project_info → numTracks / tracks[].groups[].numNotes / durationBlicks(空工程≈ 无音符且时长极小/为 0);
  • 判伴奏轨:该组 isInstrumental() == true(⚠️ 不能看组名 —— 音频轨的组名也叫 main);用户侧看就是"有没有音频轨";
  • 判音符:get_current_group 的 noteCount,或 get_project_info 里各组 numNotes 之和;
  • 想要更细:selftest 报宿主能力,get_project_info 报轨/组结构。

0.4再往下逐条问:BPM / 音频轨 / 音符 / 和声

这四条是在 0.3 的"大状态"之后继续往下问的;同样是问、不是替用户决定。

#检测到什么提醒什么(口述要点 + 怎么查)
1工程还是默认 BPM(120)提醒是否需要设置 BPM。<br>· SV2 与 IX 自带 BPM 分析 ⇒ 提醒用户用宿主自带的分析;<br>· SV1 没有 ⇒ 提醒是否用 DSH 分析(sv_analyze_audio 本地测 BPM/节拍)。<br>· 怎么查:读 tempo 标(get_measure_info 的第一个标)—— bpm == 120 且只有一个标 ⇒ 基本可当"还是默认值"。(浮动速度另有"逐拍测 → 逐段 tempo 标"两步法;写 tempo 用 apply_tempo)
2有音频轨 / 刚导入音频轨提醒要不要做音频侧的事:分析和弦(sv_analyze_chord)· 分析 BPM/调性/响度(sv_analyze_audio)· 分离伴奏(sv_separate_vocals / _dual)· 转 44.1k WAV(sv_convert_audio)· 对齐进工程(align_audio)。<br>· 怎么查:有 isInstrumental() == true 的组(别看组名,音频轨组名也叫 main)。
3已经有音符分析音符的"调校程度":这段调过没有、调到哪一步了 —— 据此再建议下一步(音头 / 音尾 / 颤音 / 重音 / 参数)。<br>· 怎么查:当前组音符是否已切手动模式(getPitchAutoMode() == false 的比例)、getAttributes()(SV1)/ getScriptData()(SV2)里写过哪些音高参数、自动化里张力/响度/气声有没有画过(Automation)。<br>· 用法:先报"调到什么程度",再问"要不要往下一步走"(参数用法见 07;重音/挖坑见 07 §3.4)。
4(承接上一条)询问是否需要和声提醒是否需要和声/副旋律 —— 但先看有没有和声轨(已在轨上/库里的和声组),有就别重复建。<br>· 怎么查:数当前轨的组(get_project_info → tracks[].groups[]),看是否已有非主组/多组;有 ⇒ 报"已有和声轨";没有 ⇒ 才问要不要生成(create_harmony_group / sv_generate_harmony)。

0.4a加音频 / 改工程文件的安全流程(分离完伴奏之后就走这套)

因为我们不能直接编辑声库和伴奏轨,所以 凡是"分离完伴奏"或其它需要"添加音频""修改 .svp / .ixp"的时机,按这个顺序做:

  1. 先提醒用户保存工程(让磁盘上的文件变成最新、权威的一份);
  2. 用户保存完,我们再重写工程文件(在文件层加音频轨 / 改结构);
  3. 再提醒用户重新加载工程(重载后改动才生效)。

为什么要这个顺序(实测的机理):

  • 脚本 API 不能导入/新建音频轨,也不能直接改声库相关的东西 ⇒ 这类事只能落到工程文件上(绕过,不是官方能力 —— 与"不可做清单"同一口径,要如实告知用户);
  • 宿主打开着工程时内存里才是权威状态,我们在底下改文件会被它保存时覆盖(见 0.1 的配套注意);⇒ 所以必须"用户先保存 → 我们改 → 用户重载"。

附加纪律:

  • 改之前先备份那份 .svp / .ixp(用户的工程是命根子);
  • 改完让用户校验:重载后确认音频轨在、时长/位置对(可用 get_project_info 复核轨与组);
  • 音频本身要先转成宿主友好格式(44.1k WAV:sv_convert_audio); ⛔ m4a / AAC / FLAC / Ogg 这类"不是 MP3 也不是 WAV"的文件:内置解码器(mpg123)只认 MP3,直接把路径喂给 sv_convert_audio 会被明确拒绝(这是故意的:硬解 AAC 出来是噪声 —— 用户 2026-10-06 亲报过「转换的 wav 是乱的」)。 正确动作:请用户把这个文件拖进悬浮球(或把路径粘进输入框)—— 客户端会用自带的 Chromium 先转成 WAV, 附件里给我们的就是 WAV 路径,之后照常分析 / 分离 / 转换。(本机装了 ffmpeg 时工具侧也会自己用它,覆盖更全。)
  • 涉及"对齐解析"时,align_audio 需要 firstBeatSec(桥侧不做音频分析)。

0.4b产物放哪:默认在 Agent 文件夹,并主动告诉用户(可指定或随工程)

音频分析、转录等产生的文件一般放在 Agent 文件夹下; 要给用户指出,并允许用户指定文件夹,或者放到与工程同目录。

  • 默认位置 = Agent 工作目录:<scripts 目录的上一级>\Agent (如 …\Documents\Dreamtonics\Synthesizer V Studio\Agent;由 sv/install.ps1 与 Electron 的「SV 集成」建立, 用来放提取的伴奏、分离结果、转码 WAV、转录/分析产物、日志等工作文件)。
  • 主动指出:每次产出文件,在回复里明确写出"文件放在哪"(完整路径),别让用户自己找。
  • 可选项(问用户,三选一):
    1. 留在 Agent 目录(默认,推荐:不污染项目文件夹);
    2. 用户指定的文件夹(让用户给路径,要确认可写);
    3. 与工程同目录(即 .svp / .ixp 所在目录 —— 好处是跟着工程走、不易丢)。
  • 纪律:
    • 选"与工程同目录"时别覆盖同名文件(先看有没有同名,或加后缀);也提醒用户这会增大工程文件夹体积;
    • 生成物不要塞进宿主 scripts 目录(那是脚本目录,不是工作目录);
    • 输出路径变了要让用户知道 —— 静默换目录 = 用户找不到文件。

0.4c 音频 / 旋律分析里要主动提出"和弦分析";在 IX 上再加"用和弦自动创建织体"

  • 和弦分析要主动提:音频轨在场时,除了 BPM / 调性 / 响度,还要问"要不要分析和弦" —— sv_analyze_chord(chroma + 和弦模板匹配,给 root / quality / score)。用途: ① 给"配和弦 / 写旋律"当参照;② 喂 sv_write_chords;③ 判断旋律与和弦是否打架(见 composition 技能)。 ⚠️ 分析对象要用伴奏:人声混在里面的 chroma 不准 ⇒ 先 sv_separate_vocals 分离出伴奏再分析(纯干声分析意义有限)。
  • IX 上再加一步:拿到和弦后,提醒用户"可以通过和弦自动创建织体" —— 也就是用和弦序列自动铺伴奏/织体:sv_write_chords 的 pattern(block 柱式 / broken 分解 / arpeggio 琶音), 或按段落挑织体与配器。
    • 为什么单说 IX:IX 是乐器宿主,自动织体正是它擅长的方向(呼应 0.3 的"IX 往编曲乐器靠");SV 侧也可用,但通常先服务"唱"。
    • ⚠️ 写音符写哪里:先看宿主版本,别一刀切(用户 2026-09-25 定): · SV1 ⇒ 写主组(hostIsSv2=false):主组就是用户音符所在的容器、可读可写, 旋律与伴奏(织体/和声)都写主组 —— 用户要的是"东西都在我能编辑的那个组里"。 桥侧那道"主组拒绝写"的守卫只对 SV2/IX 生效,SV1 从来不受限。 · SV2 / IX ⇒ 一律新建组(主组不能 addNote,宿主限制)。 · 🔴 主组里已经有音符 ⇒ 先停下来问用户(追加到末尾 / 从指定小节起写 / 还是新建组)—— 绝不覆盖。 工具层怎么表达(桥 0.3.31+,即现役 1.0.0):sv_write_chords / sv_write_texture / sv_generate_harmony 三条都有 target(auto|main|new)与 allowAppend(默认 false)。主组非空且没给 allowAppend 时, 它们什么都不会写,只回 {needConfirm:true, existingNoteCount, endQuarter, hint} ⇒ 你拿这个去问用户, 问清了再带 allowAppend=true 重调(target 默认就是 auto:SV1=主组、SV2/IX=新建组)。
    • 🔴 run_script 不豁免宿主限制(2026-09-19 我踩了):桥侧那道"写当前组 = 主组就拒绝"的守卫只挂在桥自己的 op 上, run_script 是裸逃生口,能直接 target:addNote(...) 写进 SV2 主组 ⇒ 我自己就这么写进去了 68 个音符,用户当场指出。 ⇒ SV2 / IX 写音符一律走"新建组"(SV1 相反:写主组)。新建组配方(已验证): ① local track = SV:getMainEditor():getCurrentTrack() ② local g = SV:create("NoteGroup") + g:setName("…")
      • pcall(function() g:setScriptData("akdagentTemp", 1) end)(打标记,日后可一键清理) ③ 音符 n:setPitch/setTimeRange/setLyrics 后 g:addNote(n) ④ proj:addNoteGroup(g) ⑤ local r = SV:create("NoteGroupReference") + r:setTarget(g) + r:setTimeOffset(0) + track:addGroupReference(r) ⑥ 想让新组用同一个歌手:先 pcall(function() return 主组引用:getVoice() end),再 r:setVoice(那个表)(实测可继承)。 自查动作(2026-09-25 改):写之前 sv_get_current_group 读 isMain + hostIsSv2(这两个字段 2026-09-25 才补上), 别再靠组名猜 —— 音频轨的组也叫 main,用户也能把任意组改名 main: hostIsSv2 == false(SV1)⇒ 就是写当前组;hostIsSv2 == true 且 isMain == true ⇒ 停下来新建组。
    • 🧩 六条 API 实坑(同名/不存在的调用会直接抛错、脚本中断):NoteGroupReference 没有 getName()(名字在 getTarget():getName()); 库枚举是 getNumNoteGroupsInLibrary()(不是 getNumNoteGroups());NoteGroup 没有 getDuration()(时长用 Project:getDuration(); 2026-09-21 我调了 g:getDuration() ⇒ 桥报 runtime error,无损伤但白跑一轮);SV:create('Note', {onset=…, pitch=…, duration=…}) 的传参在 IX 上不生效 (2026-09-21 实测:8 个音符全落成 onset=0 / pitch=60,只有 setDuration 有效)⇒ 必须显式 n:setTimeRange(onset, dur) + n:setPitch(p) + n:setLyrics(…)。 🆕 Note 没有 getTimeRange()(2026-09-23 实测 SV1:attempt to call a nil value (method 'getTimeRange'))—— 只有 setTimeRange(onset, duration), 读要用 getOnset() / getDuration()(这组 API 不对称,别按 setter 反推 getter); 🆕 SV:create("Note") 建出来的音符默认是"自动音高"(getPitchAutoMode() == true,2026-09-23 实测:拆音后新音符 autoPitch=true、原有音符 false) ⇒ 一般够用;用户要求切分/新建的音符走手动音高时,要显式 n:setPitchAutoMode(false)(否则画了音高参数也可能被自动音高盖住)。

      🆕 2026-09-27 真机核对(桥 1.0.1 · SV1 1.11.2 · 游离音符):给自动音高音符写音高参数 (如 tF0Right = 0.13)不报错(pcall ok、err=nil),值也读得回(0.12999999523163,float32 舍入); 但写属性不会把音符切出手动(写完 getPitchAutoMode() 仍是 true),值跨模式切换存活。 ⇒ 坑不是报错,而是静默不生效("看起来写成功、听不出变化")。 桥现在会在SV1 上写了音高参数、却没写 setPitchAutoMode 时给一句 warnings(不拦:目标音符可能本来就是手动)。 这几条都归"先索引取值 + type() 再调用"那条纪律(§2.3);写操作别把统计放进返回语句的同一段风险路径 —— 先落音符、再算回执,或先 newUndoRecord() 保证可撤销。

    • 🎛 参数自动化(automation)两条真机判据(2026-09-23 实测,写之前必读): ① 写"一个点"会把该参数在整组变成那个值 —— 实测在 3.75 拍写 voicing = 0.3 后, 3.75 / 4.0 / 6.0 三处 get 全回 0.3(原本各处是默认 1)⇒ 单点会向两边平推。 做"局部"的形(坑 / 渐弱 / 递减)形状必须闭合(首尾都回到基线),且基线先读后写。 ② 只能走"安全那一面"的 API:取对象 NoteGroup#getParameter(type) · 写点 Automation#add(b, v) · 读点 Automation#get(b)(单点采样)· 删点用区间重载 remove(begin, end)(半开,实测可行)。 ⛔ getPoints / getAllPoints / getLinear / getDefinition / remove(index)(单参重载)在 crash 清单(IX-001)上,一律不调 —— 取值域因此硬编码在桥里(AUTO_RANGE),不去问 getDefinition。 参数默认值(实测):voicing / vibratoEnv = 1,loudness / tension / breathiness / gender / pitchDelta = 0。
    • 🎵 旋律复核(sv_check_melody)不主动跑:它是给已经写好的旋律(人工写的 / 自动生成的 / 别人给的)做修正建议的工具, 由用户指出才开始检测("这段旋律帮我看看 / 有没有问题 / 帮我修一下")—— 不进生成流程、不主动跑、不当生成器的验收门; 产出的清单只列不改,改哪处由用户定,且一次只改一处。同理:歌词/编曲侧的检查也走这个口径(落点见 skills/sv-lyricist/SKILL.md「辅助检查」节 · skills/composition/references/主旋律生成.md §7)。
    • 📝 作词复核(sv_check_lyrics,第 38 个工具)触发同上:给已经写好/已填进工程的词做复核,由用户提出检查、或填词完成后提醒一次(提醒 ≠ 自动跑); 判据 = 倒字(旋律走向 vs 字调;规律:无变化=一声 · 向上=二声 · 先下后上=三声 · 大幅向上也是三声 · 大幅向下=四声)· 谐音(重点)(倒字后听起来像什么,查同音字表给候选)· 韵脚 · 词格(大范围 - 时给替换思路)· 上下文(机器只给弱信号,逻辑交人判); 输出要带替换意见(与旋律那边不同:词要给出改法方向),但仍然只列不改;⚠️ 声调结论按参考级(pinyin-pro 多音字/轻声不一定准,以人耳为准)。
    • 📋 三类检查的共同交付形态:"列清单 → 用户自选 → 再说细节" —— 不要逐项确认(不要一条条问"这条改吗",也不要拿检查结果去弹面板提问)。 正确姿势:把结果一条一行列出来(N. [严重度] 判据/症状 · 位置 · 现象 · 一句改法方向,按严重度排序、必须带位置),让用户自己挑; 用户点到哪条,才说那条的细节与具体改法(候选 + 各自代价)→ 用户拍板 → 一次只改一处 → 改完回读回报。 三处落点:旋律 composition/references/主旋律生成.md §7 · 作词 sv-lyricist 的「辅助检查」节 · 编曲 sv-texture §5「编曲体检清单」。
  • ✅ 织体 skill 已补:skills/sv-texture/SKILL.md(技能名 sv-texture)—— 织体类型学八类 · 段落↔织体对应与推进三招(加层/换型/改密度)· sv_write_chords 参数与能力边界(输出的是和弦骨架,不是成品织体)· 六张配方卡(钢琴分解 / 吉他扫弦 / 弦乐长音+琶音 / 电子 arp / ostinato / riff)· 常见毛病诊断(太空/太满/没推进/打架/低音糊)。 ⇒ 用户提到"伴奏怎么编 / 副歌怎么铺 / 琶音分解柱式 / 织体太乱"等,加载 sv-texture;本手册只保留"要主动提织体这件事 + 工具与宿主规矩"。 ⚠️ 仍不要拿"我们有织体库"来承诺:sv-texture v1 = 类型学 + 配方 + 诊断,不是一键生成器(切分/ostinato/对位仍需在骨架上改音符)。

0.4b ⛔ 测速度前先问用户一个范围(2026-10-06 用户要求,血的教训)

任何"测 BPM / 做速度包络"的活(measure_tempo · sv_analyze_audio · §七的浮动 BPM 两件套 · sv-project-format 里按 beatLocations 判变速)—— 动手之前先问一句: 「这首大概多少 BPM?(给个范围就行,比如 70–90 / 110–130)」,然后把它当硬区间传进去:

场景怎么传
measure_tempo(宿主无关,推荐)bpmMin / bpmMax(区间里找不到候选 ⇒ 明确报错,不静默给区间外的值);bpmHint 只是软先验,不能替代区间
sv_analyze_audiobpm 显式指定(它内部按给定值 beatTrack)
§七 浮动 BPM 两件套先按用户范围确认候选,再 sv_apply_tempo

为什么必须问(2026-10-06 实测):节拍有度量层次歧义 —— 同一段音频能落在几个层上, 78 / 117 / 156 就是同一条拍列的 1× / ×1.5 / ×2(×1.5 = 附点/6-8 拍感)。 检测器会锁到能量最强的那一层:我们把一首真实 78 BPM 的歌测成了 117, 还据此生成了工程 —— 白折腾好几轮,最后靠"限制 70–80"才纠正。问一句 5 秒,返工半小时。

没问到范围时怎么办:可以测,但必须把报告里的 candidates(候选速度)与 warnings ("倍频纠正" / "集成跟踪分歧")读给用户确认再往下走;别拿第一版结果直接生成/写入工程。 看到候选里出现 ×1.5 / ÷1.5 / ×2 / ÷2 的关系(如 78.8 ↔ 117.3)⇒ 就是层次歧义, 让用户用「拍感」选(慢拍 / 快拍 / 附点感),别替他定。

0.5用户对内置的分析库 / 分离库不满意时:如实说明 + 推荐更强的替代

过程中若用户对某个分析库或分离库不满意,可以推荐更好用的库给用户 —— 因为内置的都是轻量化方案(离线、体积小、开箱即用),质量/速度必然有取舍。

怎么讲(三步):

  1. 先承认取舍:明说"内置这套是轻量、离线、开箱即用的,质量属参考级,不是行业上限" —— 不要硬撑、不要假装是专业级;
  2. 给出更强的替代(要具体名字 + 一句话怎么用);
  3. 说清代价:要不要装 Python / 下模型 / 显存 / 许可,然后让用户决定(⚠️ 不要擅自 pip install 一堆东西)。

具体推荐(按类别):

需求内置(轻量)可推荐的更强替代
分离伴奏 / 人声sv_separate_vocals(MDX-Net Kim_Vocal_2)· sv_separate_vocals_dual(另加 UVR-MDX-NET-Inst_HQ_3)⭐ MSST(Music Source Separation Training 生态,如 MSST-WebUI):模型多、效果更好;代价是要 Python 环境与显存
BPM / 节拍sv_analyze_audio(自研 BeatTracker)madmom / librosa 的 beat tracker · Sonic Visualiser(可人工核对)
和弦 / 调性sv_analyze_chord(chroma + Krumhansl)Chordino(Sonic Annotator 插件)· madmom 和弦模型 · 宿主自带的调性/和弦显示
音高 / 音符提取sv_extract_notes(CREPE onnx)宿主自带转录(SV 的音频转音符)· Basic Pitch · Melodyne 一类
转码 / 响度sv_convert_audio(44.1k WAV)ffmpeg(loudnorm 等)· Youlean 之类响度表

纪律:① 不贬低用户正在用的方案(只讲取舍);② 不承诺我们做不到的精度(与"官方 API 不可做清单"同一口径:渲染/导出音频、音频转音符都不是我们内置能力);③ 推荐要可执行(谁、怎么装、大概多久),要不要采纳由用户定。

0.6分析音符的「调校程度」:① 歌词

这是 0.4 第 3 条("已经有音符 ⇒ 分析调校程度")的第一项。

① 歌词

  • 检测:有没有歌词
    • 看音符的 lyrics 字段(get_melody_notes / get_selected_notes);空串 / 全是 la、a 这类占位都算"还没有真歌词"。
    • ⚠️ - 是延续音,不算缺歌词(见文首"音符布局铁律")。
    • ⚠️ IX 没有歌词 ⇒ 这一整项只在 SV1 / SV2 上做(见 0.3 的宿主分叉)。
  • 如果没有歌词 ⇒ 给用户这几个选项(按口述):
    1. 让用户直接给出歌词(最准,首选);
    2. 问是否需要"互联网搜索歌词" —— 我可以去搜;但要提醒:网上版本可能与用户手里这一版(编曲/段落/改编)不一致,搜来只能当参考;
    3. 让用户发送「原歌词 + 准确 MIDI」,同时把 SV 自动转录(音频→音符)得到的不准确歌词当参考 —— 两者互补:结构以用户给的 MIDI 为准,文字以歌词文本为准;
    4. 由 DSH 把"正确歌词"对齐到"正确 MIDI"(即:用户给正确歌词文本 + 正确音符,我们按音符顺序/时值把词回填);
    5. 提醒"作词"功能—— 没歌词不等于只能"等歌词":也可以从零作词。 但要先让用户给基本信息,否则不硬写(走 sv-lyricist 技能;它的"动笔前七件事"就是这套): ① 主题/意向(写什么、要表达什么)· ② 情感/情绪(什么感觉;能给情绪分值更好)· ③ 风格/语体(通俗 / 摇滚 / R&B / 国风…)· ④ 结构/段落(要不要 verse–chorus、副歌几段)· ⑤ 人称/视角(我 / 你 / 他)· ⑥ 押韵与字数要求(可留空=随意)· ⑦ 参考曲/参考词(可选)。 ⚠️ 信息不全就先问,别替用户编;也别越过"用户提出才用"的约定去主动写整套词(见文首长期约束第 5 条)。
  • 第 4 条怎么落地(现成工具):
    • 按音符顺序填:fill_track_lyrics(按轨,会跳过 - 延续音)· sv_fill_lyrics_to_track(指定轨)· apply_lyrics(当前组/选区);
    • 按时间戳对位:sv_align_lyrics(给 LRC 歌词,按每段的到达时间对到对应音符)。
  • 几个必须提醒用户的坑(实测):
    1. 歌词没空格 ⇒ 逐字切分:"la" 会被拆成 l / a;要"一词一音"必须写成 la la la …;
    2. 延续音 - 会被跳过(不覆盖),所以"6 个 token 落到 6 个音符"未必成立;
    3. **和弦(同一 onset 多个音)**上落词有额外规则 ⇒ 别按组内下标硬对,按 (onset, pitch) 核;
    4. 转录(音频→音符)出来的时值与切分不准 ⇒ 对齐前先跟用户确认以谁为准。

② 音高参数

  • 检测:三层"动过没有"(三处都空 ⇒ 这段音高完全没调过)
    1. SV1:音符属性 —— getAttributes() 里那 12 个音高字段(dF0Left tF0Left dF0Right tF0Right tF0Offset dF0Vbr fF0Vbr tF0VbrStart tF0VbrLeft tF0VbrRight pF0Vbr tNoteOffset)全空,且音符还在自动模式(getPitchAutoMode() == true);
    2. SV2 / IX:音高控制线 / 点 —— 组里没有音高控制线(getNumPitchControls() == 0);(⚠️ 已知坑:IX 上这个计数会翻倍;另外 SV1 没有 PitchControlCurve API)
    3. pitchDelta —— 自动化里那条曲线为空 / 全 0(Automation#getPoints());它是"三个把手"里的第三个(见 07 §1.1)。
  • 三处都空 ⇒ 不要直接开调,先问用户两件事: ① 这是什么风格的歌?② 什么情感?(能给情绪分值/张力走向更好)
  • ⭐ 例外:如果这是"互联网上公开的非原创歌"(翻唱 / 二创 / 扒带别人的成品)⇒ 可以先自己搜: 用搜索工具查这首歌的风格与基本信息 —— 原曲风格 / 编曲走向 / 速度(BPM)/ 调性 / 情绪基调 / 原唱与年代等, 查到后跟用户确认,比一上来就追问更省事。 ⚠️ 两条注意:① 搜到的是原版的信息,用户这一版可能改编过(换调、改速度、重编曲)⇒ 必须跟用户核对; ② 不要把搜索结果直接当成"用户的意图"(同 0.5 的"互联网搜歌词"口径:只作参考、由用户拍板)。
  • 然后去 skill 里找"对应风格"(别凭感觉调): 11-风格配方.md(按风格的 Pit / 颤音 / 咬字 / 音头配方:通俗五型 · R&B · 说唱 · 京剧 · 美声 · 民族…)· 07-melody-accent-pitch-params.md(重音 → 倚音 → 参数;§3.2j 参数×风格表;§3.4 重音渲染配方)· 08-pit-drawing.md(音高线画法:音头/音尾/颤音/特殊音头;§6.5 颤音与音尾)· 情绪→手法可查 07 的"上/下音头"与张力(§2.4、§3.2e)。
  • 做法:拿到"风格 + 情感"后,先给一套方案(改哪几个参数/哪几层、为什么),问用户要不要按这个调 —— ⚠️ 与长期约束第 5 条一致:参数是"用户提出才用",我们给建议、由用户拍板;不要不打招呼就整段改音高。
  • 顺带决定"从哪层下手":SV1 ⇒ 改音符属性;SV2 / IX ⇒ 画音高控制线;属性解决不了的才用 pitchDelta(三者分工见 07 §1.1/§1.4)。
  • 拿到风格之后:先给"调声方法",再问两个问题
    1. 给出调声方法 —— 要具体到参数与数值:这个风格该动哪些参数、怎么动(上/下音头幅度、tF0* 宽度、 dF0Vbr/fF0Vbr 颤音、张力/响度/气声的处理、要不要挖坑…)。依据在 skill 里: 11-风格配方.md 的风格配方 · 07(§3.2b–j 各参数用法、§3.2j 参数×风格表、§3.4 重音渲染配方)· 08(音高线画法)。 不要笼统说"加点颤音" —— 给可执行的清单。
    2. 问范围:全自动,还是只调重点音符?
      • 全自动 = 整段按方法调;
      • 只调重点音符 = 用户选出重点音符(或让我们用重音检测给出建议位再确认 —— write_pit 的 plan="accent" + dryRun 正好能给"哪些音符是重音位"的报告)。
    3. 问执行者:我们来操作,还是用户自行操作?
      • 我们操作:按给出的方法,对选中的重点音符(或全段)执行 —— ⚠️ 动手前先把"改哪些音符、改哪几层、写什么值"报给用户确认(可先 dryRun 给预览);
      • 用户自行操作:我们只交付方法(参数清单 + 数值 + 在哪个界面调),用户自己在 SV 里做。

⇒ 所以是 2×2:范围(全自动 / 重点音符)× 执行者(我们 / 用户本人)。在用户选之前不要动手。

③ 其他参数(音高之外的)+ 声线(vocal mode)交接

  • 时机:音高线编辑结束、或还在编辑过程中都可以做这一步(不必等全做完)。
  • 动作:查一遍音高之外的参数有没有动过,然后自行判断并询问用户(尤其是"风格对应的那些参数")要不要改: 张力 tension · 响度 loudness · 气声 breathiness · 发声 voicing · 性别 gender · 音区偏移 toneShift · 口型 mouthOpening · 颤音包络 vibratoEnv · pitchDelta …
  • ⚠️ 不是每个参数都必须改 —— 要按风格来:查 07 §3.2j(参数×风格表)与 07 §3.2b–i(每个参数的用法与边界); 没必要的参数提都不要提(提了是噪音,也违反"用户提出才用"的精神)。
  • 要专门问一条:是否需要「高音自动降低音区偏移 / 低音自动提升音区偏移」 —— 即按音高把两端往中间拉(toneShift 自动策略,范围 −800 ~ 800 音分(此前这里写的「±400」偏低,且它不是 ±1 档 —— 写 ±1 等于没写),见 07 §3.2h)。这条单独问,别混在参数清单里。
  • ⚠️ 若参数已经动过(用户已经调过)⇒ 谨慎询问:先说清"我看到你已经改过 X / Y",再问要不要动; 绝不覆盖用户的手工调校(呼应"既有音符=用户所有"与长期约束第 5 条)。
  • 声线(vocal mode)交接 —— 这条要主动告知用户: 请用户把"需要我们操作的声线(vocal mode)参数"先在面板上操作一下(选组 + 指派 Vocal + 动一下唱法参数), 这样我们才能捕捉到具体的声线,随后就能在此基础上操作。
    • 为什么:唱法名/参数走引用层(NoteGroupReference#getVoice() → vocalModeParams,每项 0~150); 面板上没被指派 / 没被动过的默认唱法读不出来 ⇒ 我们不猜,请用户先"点一下"把它落成可读状态。
    • 落地:读 / 写 vocalModeParams(见 api/NoteGroupReference.md 与 07 的唱法小节)。这条硬限制的正本在 skills/sv-scripting/SKILL.md 的「唱法(Vocal Mode)的交接(官方 API 的硬限制)」一节 —— 本节只讲"什么时候该提这件事"。
    • 另一条同方向的活(声线音色本身):要不要动声库 styles(改的是声库文件,不是工程)? 工具:list_voice_styles(先看看有哪些声库与 styles)· list_voice_styles_detail · style_create(新建 style)· style_adjust(微调)· combine_voice_styles(把别的声库的 styles 移植过来,需同 vocoder)。 ⚠️ 这类改动动的是声库目录(写前自动 .bak)⇒ 要先跟用户确认,并告诉他改了哪个声库的哪个 style。 ⚠️ 而且仅限 flat 版本:styles 这套只在 flat 版上有效 ⇒ 先检测是不是 flat,或者等用户主动提出;不是 flat 就不要主动提。 判据:看声库目录里 nofs 的"内容格式" —— 可读 JSON ⇒ 可操作;加密 / 二进制格式 ⇒ 不可操作; ⚠️ 别按"文件是否存在"判断(有没有 nofs、有没有 .svpk 都不算判据)。 (另见既有的 flat 复核:的 "flat" = 数据目录形态,与上游说的"产品变体 / 可执行文件"不是一回事。)

0.6b ⛔ 语言(语种)纪律 —— AKDAgent 的整体纪律(不限说唱 / 不限作词 / 不限工具)

📌 正本在这一节。sv-project-format SKILL.md §A、sv-lyricist/references/说唱词流.md §5.2、 sv-scripting/api/SV.md 与既有收录说明都是指向这里的收录或适用说明。

一句话:默认不改任何语言设定 —— 用户比我们清楚声库语种。

  • 语言链:音符属性 > 音符组属性 > 轨道属性 > 声库语言;四层都没有 ⇒ 取声库录制语言。 音符级 API 只有 getLanguageOverride()(返回 "" = 继承,须上溯)/ setLanguageOverride(lang), 取值 mandarin | japanese | english | cantonese(SV2 另有韩语 / 西班牙语)。
  • ⛔ 默认不动:不 setLanguageOverride、不改组 / 轨语言、也不"顺手统一一下语种"。 只有用户主动反馈"多语言混杂 / 唱错语言"时,才按词内语言自动设置(改完回读)。
  • 不猜声库语种:声库录制语言写在 nofs 里,非 flat 版加密 ⇒ 读不到 ⇒ 别当已知信息,要问就问用户。
  • ⚠️ 一条技术后果(很容易误判):语种与声库不兼容 ⇒ 无音素 ⇒ 该组根本不渲染 (实测:英文声库 + 中文词「我」⇒ 计算类接口全 null,且不报错)。 ⇒ 读到 getComputedPitchForGroup / getComputedAttributesForGroup 全空时, 先查"语种与声库是否兼容"和"组挂没挂到轨道",别先怪"渲染没完成"(两条前置见 sv-project-format SKILL.md §A / §B)。
  • 📌 与"不动用户状态"同源:语言是用户的创作决定(哪位歌手、用什么语种唱),不是我们的优化项。

0.6c 混合语种:只给"少数派"音符单独设语种(2026-09-18 用户提出;属 §0.6b 纪律的唯一例外)

什么时候用:用户主动反馈"多语言混杂 / 她唱成别的语言了"(例:整组是中文,中间夹几句英文词)。 ⛔ 用户没提 ⇒ 不扫描、不改(§0.6b)。改的只有"少数派"那部分音符,其余保留 ""(继承)。

做法(三步,全程可回读)

  1. 读:把当前组所有音符的歌词 + 现有语种覆盖读回来。
    • 歌词:桥 op get_melody_notes(返回 notes[].index(0 起)/ lyrics / 音高时值)。
    • 语种:Note#getLanguageOverride()("" = 继承);SV2 也可从 Note#getAttributes().languageOverride 取(同字段,顺带能看到 phonesetOverride)。
  2. 判语种 —— 必须按"语义",不能只看文字系统(用汉语拼音或日语罗马字写的词,仍是中文 / 日文,不是英语)。三级判定,先看字形 → 再看词法 → 最后看上下文:

    (a) 字形先分流(最确定,覆盖非拉丁文字):

    字形语种
    CJK 统一表意文字(\u4E00-\u9FFF 等)mandarin
    平假名 / 片假名(\u3040-\u30FF)japanese
    韩文谚文(\uAC00-\uD7AF)korean
    拉丁字母(A-Za-z)⚠️ 不能直接判 english ⇒ 必须进 (b) 再判一次

    (b) 拉丁字母按"词法语义"再判:

    判据结论
    带声调符号(ā á ǎ à ē é ě è ī í ǐ ì ō ó ǒ ò ū ú ǔ ù …)或 "音节 + 数字"(ni3 / hao3 / lv4 / zhong1)mandarin(汉语拼音)
    拆开后每个音节都落在普通话拼音音节表里(约 410 个;含拼音特征 zh / ch / sh / x / q / j / ü·v,如 wo ni hao zhong guo xue)偏 mandarin
    含日语罗马字特征:tsu / shi / chi / rya·kyo·sha、长音 ō·ū 或 ou·uu、拨音 n'、词尾 -masu / -desu / -surujapanese(罗马字)
    命中常用英文词(love / hello / city / tonight / heart …)且不符合上面两条english
    仍然两可(san / ka / ni / man 单看既像拼音又像罗马字,san 甚至像英文)进 (c)

    (c) 上下文定夺:① 该组 / 该段的多数派语种(往往也是声库语言);② 相邻音符的语种;③ 用户给的提示(他说"这几句是拼音 / 罗马字"最权威)。 还拿不准 ⇒ 问用户,别猜(§0.6b:语言是用户的决定)。

    ⚠️ 每条歌词可能多字符("ni3 hao3"、"arigatou"):先把整条词当一个整体判(音节序列可行性 + 词表),只有整体判不出时才退到"首个有判定的字符"。 - / 空 / 纯标点 ⇒ 跳过不动(延音或停顿,没有语种含义)。

    然后数一下:哪种语种占多数 = 主语言 ⇒ 只给非主流那部分音符设语种。

  3. 写:Note#setLanguageOverride(lang)(先 Project:newUndoRecord()),取值 mandarin | japanese | english | cantonese(SV2 另有 korean / spanish)。 写完逐个回读 getLanguageOverride() 确认;setAttributes({languageOverride=...}) 是等价候选,但同样必须回读(setAttributes 不校验字段,见 §0.6b 与 known-bugs.json 的 SV-007)。

纪律:① 只动少数派,别"统一成一种语言";② 跳过 -/空;③ 改完把"改了哪几个音符(索引 + 词 + 语种)"回报给用户 —— 语言是用户的决定,改动要可追溯。

🛠 已工具化:MCP 工具 sv_fix_mixed_lyric_language —— 一条命令走完上面三步: 读整组(桥 op get_lyrics_attrs)→ 按三级语义判定出方案 → 默认 dry-run 只回报(apply: true 才写 set_note_languages 并回读)。 判定实现见 server/src/lyric/language.ts(拼音音节表由 tools/gen-pinyin-syllables.cjs 从 pinyin-pro 字典生成,412 个音节); 两可的音符一律进 toAsk,不擅自改。

0.7提醒的排序原则:创造性选项一律放最后、作备选

作词、作曲、自动写和弦这类"创造性"选项,放到最后一个作为备选。

  • 先给"非创造性"的选项(用户出素材、我们做分析 / 转换 / 对齐 / 编辑): 导入 MIDI·音频 · 转 WAV · 分析 BPM/调性/和弦 · 分离伴奏 · 对齐进工程 · 用用户给的歌词填词/对齐 …
  • 创造性选项一律排在最后,并且明确标成"备选": 作词 · 作曲(生成旋律) · 编曲 · 自动配和弦(以及同类的生成和声 / 副旋律)—— 都是"替你创作"。
  • 为什么:用户多半已经有自己的想法或素材;先问"要不要用你的东西",最后才问"要不要我生成一版"。
  • 话术:把创造性项写成 "如果你还没有……,我也可以生成一版给你参考"(明确是备选、可以拒绝), 不要把它和"你提供素材"的选项平铺并列 —— 那会让用户以为我们在推销自动生成。
  • 落点自查:0.3 的"没有音符"分支里 ③ 作曲编曲已排最后 ✓;0.6 ① 的 "作词"已排最后 ✓;0.4 的和声(create_harmony_group / sv_generate_harmony)同属这一类。
  • 彩蛋类功能也一样:只在用户提出时才做 —— 典型是 用音高线"画图"(SV2 彩蛋:把 SVG 路径 / 文字轮廓 / 任意折线映射成时间×半音,用一条曲线画一笔)。 规矩:用户不提出,就不要主动提、不要顺手玩;用户提出时再去加载 sv-pit-art 技能(那里有已验证的 API 用法、坐标映射、笔画拆分、性能与限制)。 ⚠️ 它与"调声"是两回事:画图会往音高线里写大量点,别混进正常调声流程。
  • ⛔⛔ 凡"算坐标"的活,先确认坐标语义,再"读回核对"(2026-09-12 真机翻车教训,适用于所有写曲线/写音符的工具)
    • 语义:SV 曲线的 点值是"相对 anchor 的偏移量",不是绝对音高/绝对时间 —— 显示音高 = 组.pitchOffset + 曲线.anchorPitch + 点.y · 绝对时间 = 组.timeOffset + 曲线.position + 点.x。 我把绝对音高写进点值 ⇒ 图案凭空抬高一个 anchor(62 + 69.76 ≈ 132),却被我当成"画得偏高"糊弄过去。
    • 核对:写完必须读回(getPitchControl(i).getPoints()),按同一语义还原后与预期逐点/逐段比, 并核对可见性(getNavigation().getValueViewRange() / getTimeViewRange());不许只看脚本自报的条数/点数 —— 自报"33 条/170 点"完全可以同时是"画到天上 + 大部分在屏外"。
    • 报告:给用户报实际音高范围 + 屏幕像素范围 + 是否完全可见,而不是只报"画完了"。
    • 推导:位置不对时先量再猜(读视图坐标、读回数据),不要凭截图推断根因 —— 我这次把"视口裁切边缘"误诊成"曲线接缝断口",白改了一处代码。

纪律(重要):

  • 先报状态、再给选项、让用户选 —— 不要擅自导入文件、不要擅自建组/建轨、不要在空工程里"顺手"塞测试内容(往 SV2 / IX 的主组写音符是明令禁止的;SV1 主组可写、但主组非空要先问 —— 见 §0.4 的"写音符写哪里");
  • 提醒要可执行(说清用哪个功能/哪一步),别只说"你可以导入";
  • "音频转音符"不要承诺 —— 脚本 API 的不可做清单里就有"渲染/导出音频",同理转录也不是我们的活;正解是提示用户在 SV 里用自带的转录功能;
  • 这一整节只在「意图在宿主上」时做(见 0.1 的跳过条件)。

一、脚本 API 与宿主差异

本节实测补齐(SV1 1.11.2 / SV2 2.2.1 / IX 1.0.0)。正本在 skills/sv-scripting/(api/ · references/ · examples/)。

坑现象判据 / 修法
索引基准越界报错、改错音符Lua 绑定 1 起(含返回值);对外协议 0 起。别混;换算只发生在桥内
调用形式弹"attempt to call a nil value"Lua 一律冒号(SV:getProject());成员是 userdata
Lua API 错误某 op 之后全部超时错误弹框并穿透 pcall ⇒ 桥冻住;不试探、只读先验;肇事 op 看 akdagent-lastop-<host>.json(详见 2.3)
点调用 / 传字面量给属性 API模态框「setAttributes: 无效的输入类型。」⇒ 桥冻住一律 n:setAttributes({…})(冒号 + 表);run_script 现在进宿主前预检并拒绝(详见 2.3 / 2.3b)
getNote(i) 顺序会变逐音写却错位、同一音被两个下标返回写入会扰动枚举顺序(和弦里实测重复返回)⇒ 先快照再操作,按 (onset, pitch) 核
perNote 不可靠部分音符没写到纯数字键可能被 JSON→Lua 当数组解码 ⇒ 用 indices + params 分次写
write_pit 的目标只写了 1 个音符优先级 = indices → 选中音符 → 全部;UI 选中一个就会只写它
属性是 float32写 0.12 读回 0.11999999731779等值判断必须带容差;写 NaN = 回组默认(字段会消失)
12 个音高参数SV2 读不到getAttributes() 的音高字段是 SV1 专属;SV2/IX 用 getScriptData()
getVoice() 音高参数声库兜底读不到三个宿主都取不到(SV1 空表;SV2/IX 只有 vocalModeParams/singers/spacing)⇒ resolve 实际只有 3 层
setScriptDataSV1 报 nil methodSV2/IX 专属;SV1 只能写 attributes
setPitchAutoMode读自动模式方法名不对Setter 只在 SV1;getter 是 getPitchAutoMode()(SV2 只有 getter)
SV1 切不了当前组selectGroup 报成功但当前组没变SV1 上 ed:getSelection():selectGroup() 改不动"当前组" ⇒ 只能请用户在钢琴窗点选;脚本要拒绝在非预期组上写
isMain 可能是 undefined客户端按布尔判断出错isMain 只当参考:SV1 上没有 isMain 方法 ⇒ 回读是 nil;要判"是不是 SV2/IX、主组能不能写",看 get_current_group.hostIsSv2(布尔,必有值)
主组能不能写写了主组/写了没反应SV1 主组 = 用户音符所在,可写(2026-09-25 起:旋律与伴奏都写主组,但主组非空要先问);SV2/IX 主组不可编辑(只是保留了结构)⇒ 一律新建组 —— 版本相关,别一刀切
IX 的组与计数getNumPitchControls() 翻倍;往 main 加音符失败IX 计数约 2×;IX 的 main 组不能 addNote ⇒ 要建新组
align_audio报 args.firstBeatSec required桥不做音频分析 ⇒ 必须调用方给 firstBeatSec(+ 可选 bpm)

二、桥与文件通道

2.1 改完桥源码必须在宿主里重跑(不然还是旧版本)

  • 现象:改了 sv/lua/AKDAgentBridge.lua、重新部署了,但行为不变、ping 还是旧 bridge 版本。
  • 原因:Lua 脚本在宿主里是常驻实例;再次"运行脚本"不会替换已跑的实例。
  • 怎么查:ping / 心跳里的 bridge 版本号(如 1.0.0)—— 这是判断"跑的是哪版"的唯一可靠依据。
  • 怎么修:在宿主里跑一次 stop op(优雅停桥)→ 再重新运行脚本;或重启宿主。 ⚠️ 重启 DSH / MCP 不会重载桥(桥属于宿主进程),同理重启宿主不会重载 MCP 的 tools.ts(那是 DSH web host 的事)。 ⚠️ 现在还没有"自动常驻":桥要每次在宿主里手动跑一次。侧边面板版(SV2 / IX 的 SidePanelSection)+ 自动常驻仍在设计(本版未实现) ⇒ 别跟用户说"装好就自动常驻",只能说"打开宿主后跑一次桥脚本"。

2.2 心跳还在跳 ≠ 桥是好的:看 pollTicks

  • 现象:所有 op 都超时,但心跳文件一直在更新 —— 看起来"桥明明在跑"。
  • 原因:历史事故里轮询链会静默死掉(定时器链断),而心跳由另一条线继续写 ⇒ 心跳骗人。
  • 怎么查:心跳里的 pollTicks 是否在增长(现在心跳与轮询合并进 scheduleLoop(),心跳同时带 pollTicks / pollErrors)。pollTicks 停住 = 轮询链死了。
  • 怎么修:重跑桥(见 2.1)。

2.3 桥里的 Lua API 错误会弹模态框、并穿透 pcall ⇒ 整个桥冻住

  • 现象:某个 op 之后所有 op 都超时;宿主里可能挂着一个脚本错误框。
  • 原因:SV 的 Lua 绑定在 API 用错时(实参类型不对 / 调用形式不对 / 越界 / 调不存在的方法)弹框并穿透 pcall,脚本停在那一步,轮询不再走。
    • 真机样例(2026-09-27,用户机器):脚本 'AKDAgent Bridge (Lua)' 出现错误:setAttributes: 无效的输入类型。
    • ⚠️ 关键:这是宿主 C 侧弹的框,不是 Lua 异常 ⇒ 快照式的 pcall(dispatch, …) 一路包着也没用。这类错误拦不住,只能预防。
  • 怎么查(现在有面包屑了):桥每笔请求执行前都会写 akdagent-lastop-<host>.json(stage="running"),跑完改 done/failed。
    • 客户端报错里会带一行 lastOp=… stage=running + 参数摘要 ⇒ 直接指名肇事 op(以前只能看着"日志停在某时刻"干瞪眼)。
    • 若 stage=done,说明桥是干净地停的(宿主被关掉 / 脚本被停),不要往"某笔请求弄死它"上猜。
  • 怎么修(恢复三步,按顺序):① 到宿主里关掉那个错误框(框不下,宿主主线程一直停着,重跑脚本也不会生效)→ ② Ctrl+S 保存工程(宿主内存里的改动还没落盘,这一步最要紧)→ ③ 脚本菜单 → Agent → 重跑 AKDAgentBridge。
  • 纪律:绝不做"试探一下"的调用(宁可先返回"未验证");写操作前先确认目标存在且索引合法。
  • 现在有两道机检(2026-09-27 加):
    • run_script 预检:点调用(JS 写法 n.setAttributes(…))与 setAttributes 的字面量实参("x" / 1 / nil / true)会在进宿主之前被拒,错误里直接给出冒号写法 —— 因为交给宿主就是弹框。
    • 属性 API 的宿主闸门:phonemes 只有 SV2 2.1.1+ 有、dur 只有 SV1 有 ⇒ 写错宿主明确拒绝(refused="sv1-no-phonemes" / "sv2-no-dur"),不把"本宿主没有的字段"递给宿主。

2.3b 音符属性(音高参数)按宿主选 API —— 选错就是上面的模态框

宿主读 / 写说明
SV1Note#getAttributes() / Note#setAttributes({…})那 12 个音高字段(tF0Offset/tF0Left/tF0Right/dF0Left/dF0Right/tF0VbrStart/tF0VbrLeft/tF0VbrRight/dF0Vbr/pF0Vbr/fF0Vbr/tNoteOffset)只有 SV1 有
SV2Note#getScriptData(k) / setScriptData(k,v)音高在曲线上(PitchControlCurve);直接写那 12 个字段不报错也不生效(SV-007)⇒ 别拿它判断成功
IX技法/力度:getAttributes() 只有 {muted, articulations, articulationsFixed, dynamic};写用 setArticulations() / setDynamic()dynamics 曲线 API 写不了(IX-002)⇒ 走 .ixp

⇒ 写音符属性前先看心跳里的 isSV2 / host;setAttributes 传的必须是表,且别用点调用。

🆕 2026-09-27 起:setAttributes 的内容会被按官方文档检测(用户指定:「读 api 文档,给 setAttributes 的内容加上检测(IX 除外)」)。 事实源 = skills/sv-scripting/api/Note.md 的 Note#getAttributes(文档原话:setAttributes 的 attributes 「For the definition, see Note#getAttributes」)。三组归属: · rTone / rIntonation / dF0VbrMod(since 1.9.0b2)⇒ SV1 + SV2 都有 · expValueX / expValueY / phonemes / muted / evenSyllableDuration / languageOverride / phonesetOverride(since 2.1.1)⇒ 仅 SV2 · 12 个音高参数 + exprGroup + dur + alt("Properties only available in version 1")⇒ 仅 SV1 · NaN 是合法值(文档:NaN = 用所在组引用的默认值)—— 我们就是靠它把字段恢复成默认。

判据四类:键不在文档里(含拼错)· 键不属于本宿主(静默无效最阴,SV-007)· 值类型不符 · 数组元素不是数; 逃逸口 = 脚本里写 -- akdagent:allow-extra-attrs(未知键降级为警告,类型错仍拦)。 ⛔ IX 整体豁免 —— 它没有官方文档、宿主另有文档外的键(toneShift/pitchDelta/articulations/dynamic), 拿 SV 文档去卡它只会误伤(用户口径)。

2.3c IX(Instrument X)没有官方 API 文档 —— 这条缺口怎么防(台账 IX-004)

  • 事实:resource.dreamtonics.com 的 JSDoc 只描述 Synthesizer V;IX 的类/方法/取值域没有文档 (2026-09-21 复核:页脚 generatedAt 仍是 Thu Oct 09 2025、23 个类无增减 ⇒ 仍未覆盖 IX)。
  • 它已经造成的具体损失(都是"没文档只能实测"的产物):IX-001(读点族在 1.0.0 上冻桥)、 IX-002(音符级 dynamics 曲线 API 写不了)、IX-003("没有区间读点接口"是误判)、 IX-005(IX 音高曲线是"暴力移植",和弦上不可靠)、IX-006(dynamics 当 automation ⇒ 延时崩宿主)。
  • 纪律(两条,缺一条就会踩模态框):
    1. 官方镜像 skills/sv-scripting/api/ 只当"SV 的"用;IX 上的事实源是 knowledge/docs/InstrumentX-API枚举.md(本仓实测枚举,307 行)+ sv-ix 技能。"镜像里没有" ≠ "宿主没有"(例:toneShift/pitchDelta UI 隐藏但 API 可写且渲染)。
    2. 绝不用"试着调一下"代替检查 —— 试错在宿主上就是弹模态框冻桥(SV-001 / IX-001 都是试出来的, 机制见 §2.3)。存在性检查 = 「索引取值 + type() ∈ {function, userdata, table}」;在 run_script 里可以直接用桥注入的助手: SVH.has(obj, "getName") · SVH.members(obj)(列成员名:类型)· SVH.svcall。
  • 已知缺口(坦白):桥自己发的调用都有 has() 先查(安全);但 AI 手写的 run_script 没有存在性检查, 而 AI 读的是 SV 官方镜像 ⇒ 在 IX 上容易写出不存在的成员 = 同一个模态框冻桥。 现在靠"工具描述里的纪律 + 预检(只管形式/类型,不管成员是否存在)"兜着;要不要再加一道服务端静态闸(复用 crash 清单硬拒 + 未知名警告)待用户定。

2.4 响应必须按 id 匹配,不能按 seq

  • 现象:客户端永远收不到响应(超时),但桥确实执行了。
  • 原因(历史事故):16 位 seq 被 Lua 序列化成 1.78917538422e+15(浮点,精度丢失)⇒ 匹配不上;又:两个客户端的 seq 量级差 1000×(Date.now()*1000 vs Date.now())⇒ 量级小的那边永久被忽略。
  • 怎么查:比对请求与响应里的 id / seq 字段类型与数值。
  • 怎么修:一律用字符串 id 匹配;seq 只作日志。

2.5 只有一对 req/res 文件 ⇒ 客户端必须串行

  • 现象:并发发两个 op,其中一个拿到别人的结果、或两个都超时。
  • 原因:通道是单对文件(akdagent-req-<host>.json / -res),并发会互相覆盖。
  • 怎么修:客户端侧串行化( server/src/tools.ts 的 serial()),桥侧按 id 去重。

2.6 IPC 目录不一致 ⇒ "桥不在线"但桥明明在跑

  • 原因:目录解析规则是 %USERPROFILE%\AKDAgent\ipc,不可写时回落到 %TEMP% ⇒ 桥与客户端可能各读各的。
  • 怎么查:对比桥心跳里的 dir 与客户端 resolveIpcDir() 的结果;heartbeat.dir 就是桥实际用的目录。
  • 怎么修:AKDAGENT_IPC_DIR 显式指定同一个目录;或确认两边都能写 %USERPROFILE%\AKDAgent\ipc(杀软/权限会拦)。

2.7 桥会话重置 ⇒ id 去重表归零

  • 现象:用同一个 id 重发请求,真的又执行了一次(重复产物)。
  • 原因:去重表在桥内存里;桥重跑/宿主重启即清空。
  • 怎么修:客户端不复用旧 id;判"是否同一桥会话"看心跳的 bridge 版本 + dir + ops + pollTicks 连续性。

2.8 其它(排障工具箱)

  • ping:拿 bridge 版本 / hostName / isSV2 / lua / ops(桥声明的 op 清单)/ dir。
  • selftest:逐 op 报"在当前宿主是否具备条件"(缺哪个 API 成员),换宿主/换版本后的第一件事。
  • stop:优雅停桥(用于"重跑前先停")。
  • 写文件用 writeFileAtomic(tmp + rename)⇒ 避免读到半截 JSON。
  • 载荷不再受剪贴板限制(旧剪贴板通道 ≲4000 字符、7 倍膨胀 —— 该通道与其实现(JS 桥 SVAgentBridge.js、server-clipboard.ts)2026-09-19 已彻底删除,退役记录)。
  • 文件通道的实际用户:MCP server(server/src/fileipc.ts)与 Electron 客户端(electron/src/file-ipc.js);两者都要重启各自进程才会加载新代码(MCP → 重启 DSH web host;Electron → 重启应用)。

2.9 超时是"模糊"的:别急着重发

  • 现象:op 超时 ⇒ 以为没执行 ⇒ 重发 ⇒ 结果做了两次(重复产物,如两个同名组)。
  • 原因:客户端超时只表示"我们不等了"(十秒量级),宿主很可能仍在写;写操作尤其如此。
  • 怎么查/修(超时后的正确顺序):
    1. 先 ping / 读心跳 —— 确认桥还活着(结合 2.2 的 pollTicks);
    2. 重读目标状态(组内音符数 / tempo 标数 / 曲线条数)判断是否已经生效;
    3. 确实要重试时用同一个 id(桥侧去重会挡住重复执行)—— ⚠️ 但若桥重跑过,去重表已清空(见 2.7),此时同 id 会真再执行一次。
  • 附注:写操作是"提交式"的,一个撤销边界 ≠ 回滚;不可逆动作(如 clearExisting 清 tempo 标)要先把数量报给用户确认。

2.10 换宿主 / 换版本:先 selftest,别猜

  • 现象:某 op 在新宿主上行为怪异、或报"缺某个 API 成员"。
  • 做法:先跑 selftest(脚本 sv/lua/show-capabilities.cjs 是同一份能力的可读版)—— 它逐 op 报"在本宿主是否具备条件、缺哪个成员"; 再看心跳声明的 ops 清单。不猜、不试探(试探会弹框冻桥,见 2.3)。
  • 说明:现在没有白名单了 —— 能力 = 「心跳新鲜(≤15s)且 op ∈ 心跳声明的 ops」。桥说它会,才会调它。

2.11 写目录被拦(杀软 / 权限 / 受管环境)

  • 现象:桥与客户端"时通时不通"、心跳文件时有时无、换台机器就复现不了。
  • 原因:写 %USERPROFILE%\AKDAgent\ipc 被拦 ⇒ 一边回落到 %TEMP%、另一边没回落(与 2.6 同源)。
  • 怎么查:看心跳里的 dir;手工去 %USERPROFILE%\AKDAgent\ipc 与 %TEMP% 两个地方找 akdagent-hb-*.json。
  • 怎么修:用 AKDAGENT_IPC_DIR 指到一个两边都可写、且已加入杀软白名单的目录。

2.12 通道隔离边界:哪些坑只在 SV 家族内部

组合能否同时开桥说明
SV1 + SV2(含 OPSV Flat)❌ 不行共用 -sv 通道文件 ⇒ 心跳覆盖、请求可能被两个宿主各执行一次(见 0.2)
SV + IX✅ 可以IX 走 -ix,文件与去重都独立,互不干扰
两个 IX❌ 不行同 -ix,与上同理
  • 判宿主别靠文件名猜:看心跳的 isSV2 / hostName / hostVersionNumber(⚠️ 音频轨的组名也叫 main ⇒ 判音频轨要用 isInstrumental(),不能看名字)。

三、工程文件读写(.svp / .ixp)

正本:skills/sv-project-format/(含 .svp/.ixp 结构与最小可生成骨架)。这里只列会踩的坑。

⚠️ 本表的"结构"结论整体待复核("工程问题以后再解决"): 其中 「SV1 没有 library 间接层」已被用户否定 —— 那是从他手上的工程外推的,而他平常不用 library ⇒ 不能据此断言 SV1 没有这一层。同理,凡"我没见过 ⇒ 就没有"这类结论都要标存疑,别当事实用。

坑说明 / 修法
版本号实测 153 = SV1、183 = SV2.0.0、196 = SV2 当前。⚠️ 上游把 153 归给 SV2 是错的(以实测为准)
SV1 扁平结构(⚠️ 存疑,待重新核对)音符写在 track.mainGroup.notes ✓;但「没有 library 间接层」这句已被用户否定 —— 他平常不用 library,不能拿他手上的工程当"SV1 没有 library"的证据。⇒ 本项待复核(详见下表后的说明)
SV2 结构音符定义在顶层 library;track.groups[] 通过 groupID 引用库项(不内嵌音符);mainGroup.notes 保持 [](主组不可编辑)
唱法字段位置两版的 mainGroup 都有 vocalModes;voice 在 mainRef(上游"SV2 才上移到 mainGroup"的说法与实际不符)
音频轨mainRef.isInstrumental = true + mainRef.audio,且 mainGroup.notes 必须为 [](SV1 同)
编码一律 UTF-8 无 BOM;改前备份;宿主开着同一工程时别直接改文件(会被它保存覆盖)⇒ 走 0.4a 的三步
读不到路径未保存的工程 fileName 为空 ⇒ 拿不到音频/工程路径时先问用户(别猜)

四、宿主差异速查(SV1 · SV2 · IX)

第三方宿主:ACE Studio(不在下表里 —— 它不走我们的桥:没有心跳文件、没有 op、没有面板, 它走自己的 acestudio-cli / MCP server)。⇒ 本手册与 akdagent-protocol 里一切关于桥的东西对它都不适用, 也别让 ACE 用户"去跑 AKDAgentBridge.lua"。

  • ACE 的一切 → 加载 ace 技能(ACE 模式总入口:在线自检 / 分工表 / 任务路线图 / 纪律 / 能力边界总表); 调参数看 ace-params(真机花名册、层模型、尺度、写法)· 直接读写 .acep 看 acep(容器/CBOR/三种画法/写纪律); 官方 4 个 ace-studio-* 讲功能与顺序。本手册不重复它们。
  • 🆕 2026-10-05:我们自己也有 3 个 ACE 工具(ace_state / ace_cli / acep,见 ace §3b)—— 不必再让用户去装官方 MCP; 其中 acep 带写护栏(--in-place 默认拒)。
  • 本节只保留跨宿主才需要知道的两件事:① 参数映射与换算(下表)· ② 越界行为两边相反。
  • ⚠️ 越界行为相反:ACE 一律 INVALID_ARG 拒绝、从不 clamp;我们 SV 侧是夹到边界并回 clamped:true ⇒ 跨宿主搬数别指望"他会帮我夹",要自己先夹好。
  • 🔁 ACE ↔ SV/IX 参数映射(用户 2026-10-03 口径) —— ⚠️ 同名不同量纲,必须换算: ⚠️⚠️ 重要前提(2026-10-04 A/B 实测):下表 ACE 侧的量纲取自 v2 代(singing-mamba)的 model 尺度 0..1。 v1 代(verse24)上这四个参数是 envelope 尺度(围绕 1.0 的乘数,实测 air 0.2..2.5 / falsetto 0.3..3 / tension 0.3..2.5 / energy 0..2) ⇒ 同一张换算表不适用:先 vocalparam layers 看这一代的 scale,再决定用哪套。别拿 v2 的数去写 v1 的 clip。

    ACE 参数(真机 scale / valueRange)SV/IX 对应SV 量纲 / 范围换算
    dynamic(ACE 2 的"整体动态"宏 · 人声控制 pill;control [-1,1])loudness(只对了一半)dB -48..+12端点对齐线性:dB = -18 + 30x
    energy(model [0,1])loudness(单向)dB -48..+12同式;但 ACE 侧只能 ≥0(只加不减)
    air(model [0,1])breathiness-1..1s = 2x - 1
    falsetto(model [0,1])toneShiftcent ±800cent = (x - 0.5) × 1600
    tension(model [0,1])tension-1..1s = 2x - 1

    ⇒ 四条要注意:① dynamic 与 energy 都落 loudness(反向映射不唯一:dynamic 是双极性又用锚点形状、适合"画" ⇒ 首选它; energy 是单极性 [0,1]、只加不减 ⇒ 在 SV 侧没有干净对应,别硬套); ①b dynamic 是"四合一表情宏"、不等于纯响度 —— ⭐ 用户 2026-10-04 定性:它是 ACE 2 的参数,调的是"整体动态"(不是逐音符力度); ACE 官方语义:负值偏 Soft/Breathy、正值偏 Power/Chest, 即它同时带音色轴(它替 ACE 那四个 power/soft/airy/chest)⇒ 只落 loudness 会丢音色那一半, SV 侧没有单一对应(要补得靠声线 / vocalMode_*);且它只在 vocalControlRoute=dynamic 的工程里存在 (另一条路由下花名册里根本没有它)—— 详见 ace-params §2b; ② "同名"范围也不同(tension/air 在 ACE 是 0..1、在 SV 是 -1..1); ③ SV 侧 voicing(0..1)/gender(-1..1)/vibratoEnv(0..2)/pitchDelta(±1200cent) 在 ACE 花名册里没有对应 (pitch 未开放、formant 在 singing-mamba 引擎不可用 —— formant 若在别的引擎可用,语义上更接近 gender 而不是 toneShift) ⇒ 反向迁移时这四项要如实告诉用户"搬不过去"。

能力 / 行为SV1 1.11.2SV2 2.2.1IX 1.0.0
音高落地方式音符属性音高控制线音高控制线
PitchControlCurve API❌ 无✅✅
setScriptData❌✅✅
getVoice() 给音高参数❌(空表)❌❌
歌词✅✅❌ 没有歌词(三个歌词 op 不适用)
往 main 组加音符✅ 允许❌ 不可编辑❌ 要建新组
ref:setTimeRange(音频轨)❌(退回 setTimeOffset,起点可为负)✅✅
getAllTempoMarks()粗精细粗
通道文件-sv-sv(与 SV1 冲突)-ix(与 SV 互不干扰)
判宿主心跳 isSV2=falseisSV2=truehostName="Instrument X"、isSV2=true

五、工具与文档纪律

坑说明 / 修法
⭐ 先查文档,再调用(2026-09-20 新增 · 血泪规则)任何宿主 API 调用/探测之前,先查官方镜像 —— 一条命令:node tools/api-lookup.cjs Note#setLyrics(列一个类:api-lookup.cjs Note · 不知道在哪个类:--search 关键词 · 查不到即 exit 1)。⚠️ 不要再用"真机试错探测"确认签名:试错在 SV1 上会弹模态框冻死桥(SV-001);而 2026-09-20 我们正是没查文档、靠试错,把 Automation.getPoints()(官方签名 getPoints(begin, end),文档连返回结构的例子都给了)误判成"返回宿主内部对象 ⇒ 序列化卡死",据此把一整族正常 API 标成"禁用"、白跑一轮真机验收。⇒ 顺序:官方镜像 skills/sv-scripting/api/ → IX 特有的查 knowledge/docs/InstrumentX-API枚举.md → 真机探测(最后手段)
PowerShell 改中文文件禁止用字符串读写(曾把 README.md 弄成乱码、~2% 丢失)。只用 read/edit/write;万不得已用 PowerShell 时走 .NET UTF-8 无 BOM(UTF8Encoding($false)),并注意 Get-String 会把 BOM 解成开头 U+FEFF
Get-Content 数行数 / 定位行两样都不准 —— 默认按 cp936 解 UTF-8:① 数是错的(实测 1563 vs 真实 2283,差 700+ 行,害我误判"文件被删");② 🆕 行号会整体错位(cp936 双字节字符会把后面的 \n 当第二字节吃掉 ⇒ 行结构被破坏):2026-09-19 实测 Get-Content main.js 说第 976 行是 agentDir,而 ripgrep 说第 976 行是 devReadme —— 差点据此改错地方。⇒ 行号只信 ripgrep(grep 工具)/ read 工具;非要用 PowerShell 就显式 -Encoding UTF8
改动生效要重启桥源码改完 ⇒ 在宿主里重跑桥(Lua 常驻);MCP tools.ts 改完 ⇒ 重启 DSH web host;Electron 客户端改完 ⇒ 重启应用
⭐ 用户数据分层:只写自己的家(2026-09-27 新增)AKDAgent 自带宿主跑在 DSH_HOME=~/.dsh-akdagent(凭据 ~/.dsh-akdagent/.credentials.yaml、设置 ~/.dsh-akdagent/settings.yaml);用户自己的 ~/.dsh 是只读的(只在他那份文件不存在时首次导入读一次,之后只增不改)。任何情况下都不许往 ~/.dsh 写 / 补文件 / 留 .bak —— 用户报的"夺舍 dsh"就是这么来的。找脚本目录(声库 / styles 所在)的顺序:$DSH_HOME/settings.yaml → ~/.dsh-akdagent/settings.yaml → ~/.dsh/settings.yaml(末级只读)。生成宿主前会把继承来的 DSH_* 环境变量剥掉,别指望父进程的 DSH_HOME 会传进来。守卫:node tools/check-dsh-separation.cjs
验证纪律不采信子代理/自己的报告数字:独立复跑 + node 量行数/编码 + 抽关键条目是否还在;任何"文件疑似损坏"先 node 复量再下结论
上游内容用法只做摘要改写、标来源与日期;冲突以实测为准(许可 = Apache-2.0 + Commons Clause + Additional Terms)
⭐ 上下文卫生(2026-10-06 新增 · 省时间三条)用户问「为什么用了这么长时间」时,会话记录(~/.dsh-akdagent/storages/session_projcache/sessions/*.json 的 sessionStats)给的答案是:LLM 22.4 min vs 工具 4.1 min(186 步 / 27 轮 · 解码 16.7 min / 输出 24.6 万 token · 首字累计 343 s · message 上下文 34 万 token)⇒ 慢在我们自己"喂大上下文 + 多步循环",不是工具。三条纪律:① 合并步数:把"读→判断→改→验"写成一个脚本一次跑完(本仓 server/tests/_tmp-*.mjs 就是这个用法),别拆成四五次工具调用;② 大文件只读窗口:grep 定位 + read 带 offset/limit,**绝不整份 `Get-Content -RawConvertFrom-Json 打印**(一次就能把 34 万 token 的记录喷出来);③ **长任务切段**:用 todo / 子代理把长链切开,每段上下文小;工具回包超 N KB 就只回"摘要 + 计数 + 完整结果路径"(DSH 本来就会把大输出落盘,用那个路径,别打印)。**另**:reasoningEffort: high 直接抬 llmMs,纯机械活可考虑降档。**实测补充**:tools/dump-reasoning.cjs` 能审计每个会话的推理量(某会话 推理 309,305 token vs 正文 11,896 ⇒ 思考是回答的 26 倍)
⭐ 决策纪律:不"二次复议"(2026-10-06 新增)有据就执行,别为了安心再权衡一遍。 依据(一个真会话的推理审计:node tools/dump-reasoning.cjs --session <子串>):推理 309,305 token vs 正文 11,896 token ≈ 26 : 1,131 步 / 156 次工具调用 ⇒ 每次工具调用约 2,000 token 的思考;抽查推理原文,里面反复出现 "Hmm, hold on. Let me reconsider ONE more time whether…" 这类已经把结论推出来又推翻重来的段落。规则:① 决策一旦有据(读过官方文档 / 实测过 / 用户明确裁过)就直接执行;② 要复议必须带新证据(新读到的一行、新量到的数、用户的新话),只是"不放心"不算理由;③ 同一件事最多再查一次,查完就动手;④ 结论写进随包文档(knowledge/docs/,或工具的注释里)而不是靠脑子反复重推 —— 下次读文档,不重推;⑤ 机械活(改名/搬运/格式)不解释自己的犹豫。⚠️ 与"绝不猜、先实测"不冲突:那条要求动手前查清,这条要求查清后别再回头

六、缺陷登记 · 复检 · 反馈记账(2026-09-14 新增)

动工前先扫一眼缺陷表(尤其 crash 类):node tools/known-bugs.cjs --list

6.1 缺陷表四类(tools/known-bugs.json)

类含义复检
crash调用即冻桥 / 宿主闪退 —— 绝不允许出现在生成脚本里✅
api-gap不崩但做不到(需文件路线或绕行)✅
doc-gap官方文档缺席或与实测不符(IX 侧文档缺席不算证据)✅
by-design平台设计如此,不是缺陷❌ 永不复检

SV1 上调用不支持的 API 弹框不算 bug("就是这么设计的")⇒ 归 by-design,只作纪律: 禁止试错式探测;存在性检查用「索引取值 + type() ∈ {function, userdata, table}」再 pcall 冒号调用。

两条最狠的 crash:

  • IX-001:Automation#getPoints / getAllPoints / getLinear / getDefinition / remove(index) 调用即冻桥,严重时宿主重启。 正确做法 = 读曲线用 get(blick) 单点采样 + 线性插值;删单点 remove(x-1, x+1);删区间 remove(begin, end)(半开); 绝不从桥脚本 return 宿主对象(只回 type() / 数字 / 字符串)。
  • SV-002:音高参数写"数百"量级(如 fF0Vbr = 300)宿主闪退 ⇒ 写前按取值域 clamp,注释写明量纲,先写 1 个音符验。

6.5 侧栏面板(SidePanelSection)—— 2026-09-15 新增

  • 面板只能用 JS 写(getClientInfo().type = "SidePanelSection"),而 SV 的 JS 沙箱零宿主对象 (无 require/io/process/文件 API)⇒ 面板自己读写不了文件。
  • ⇒ 面板与客户端之间只能靠 project scriptData(JS 与 Lua 都能读写——用户既有侧栏脚本也用它存预设), 再由 Lua 桥当搬运工转成 %TEMP%\akdagent-panel-*.jsonl。整条链见 akdagent-protocol 的通道小节(设计留档。
  • 侧栏控件没有单行 TextBox:长信息框用 TextArea + readOnly:true + height;单行输入用矮的 TextArea。 ({type:"TextBox", name, label, default} 那种形状是自定义对话框 API,别混用。)
  • 「一轮只加一次正在处理中」靠 turnId 去重;「输出不含 think」靠只累积 text-delta(推理走另一路)。
  • 改桥要升 CFG.VERSION(面板中继自 0.3.7 起;现役版本看心跳 bridge);改完必须在宿主里重跑桥。
  • SV 与 IX 可以同时连(2026-09-25 修):客户端每台宿主各跑一个中继,各自写自己的 akdagent-client-<host>.json(桥按这份文件判"悬浮球在不在",超 20s 视为未连接)。 以前只有一个中继、sv 优先且启动后不再采样 ⇒ SV 一开着,IX 侧栏就永远显示「还没连上悬浮球」。 宿主关掉/面板卸载 ⇒ 那台的中继会停、在线文件随之消失(面板如实显示未连接,不举过期标志)。
  • 「当前是哪台宿主」只认 electron/src/host-pick.js:最近有过活动的那台(面板里动过 / agent 在那台 干过活)→ 桥活着的那台 → 兜底 sv;面板里那两块镜像的是同一个悬浮球会话(在一块里答题, 另一块的过期选项会被收掉)。

七、伴奏对齐:浮动 BPM 两件套(sv_apply_tempo → sv_align_audio)

与已退役的桥无关 ⇒ 2026-09-19 移到本手册(否则活技能里根本没这份流程)。

适用:伴奏 BPM 浮动(如 120–122),要让 SV 小节线贴合音频拍点、之后加的音符能合上拍。 前提:用户在 SV 里新建空白工程 + 拖入伴奏(脚本不能新建工程、也不能导入音频 ⇒ 见本文「三、工程文件读写」)。

两步,顺序不能反

步工具参数要点作用
①sv_apply_tempoinput(伴奏 WAV)· segmentBeats: 4(每 4 拍一段)· measureStart: 2(第 1 小节留空)· beatOffsetSec(= 第 2 小节起点对应秒;默认用音频第一拍偏移 firstBeatSec)按逐拍实测 BPM 逐段 addTempoMark ⇒ 小节线贴合浮动 BPM
②sv_align_audioinput · anchor: "measure" · measure: 2把音频轨挪到"第一拍 = 第 2 小节起点"

关键点

  • 先 ① 后 ② —— tempo 决定小节线在哪,音频落位是跟随它的;颠倒会出现"对齐了但小节线仍不贴"。
  • 浮动 BPM 必须用逐段 tempo mark,别只写一个 BPM:单一 BPM 会让长音频越往后越偏。
  • 第 1 小节故意留空(measureStart: 2):给前奏/吸气留位,也让人声第一句落在干净的小节线上。
  • 对齐后加音符即可贴住浮动拍点;用户要微调就用 sv_align_audio({shiftBeats: N})(正数往后挪)。
  • BPM 检测有歧义时(150 vs 120 这种)先 sv_analyze_audio 看 bpmCandidates,再用 bpm 显式指定 —— 见 sv-scripting/examples/BPM检测歧义.md。
  • 纯文件路线(不开宿主,直接改 .svp 的 mainRef.audio.bpm / beatLocations)见 sv-project-format 的「SV2 音频 BPM 的读取 / 写入 / 修正流程」。

七·B 🆕 不依赖宿主的那条路(2026-10-06 落地 · 推荐先走)

为什么:上面两件套要宿主在线(写 tempo 标走桥);而"测速度"本身完全可以离线做, 产物还能直接落成工程文件 —— 用户只是要"把网格贴合音频"时,不必先开 SV。

步怎么做说明
① 先问范围见 §0.4b避免 ×1.5 / ×2 层次错误
② 测measure_tempo(不需要 SV/IX/ACE 在线)出「时间 → BPM 包络」+ 点击轨 WAV(听一遍就知道准不准) + MIDI tempo 轨(拖进任何 DAW) + CSV/JSON;bpmMin/bpmMax 锁层
③ 落工程node tools/svp-write-tempo.cjs --base <你的空工程.svp> --tempo <包络.json> --out <新.svp>写进 time.tempo[](秒→blick 按 tempo 累加;原样保留尾部字节,SV1 的 .svp 尾部可能有 \0;只写新文件)
④ 挂音频轨node tools/svp-inject-audio.cjs --in <上一步产物> --out <最终.svp> --wav <音频.wav>与 ③ 同为纯文件操作,不需要桥

实测参考(123132132.wav,4:25 吉他+人声):正确层 78 BPM(343 拍,98.5% 拍点吸附在真实 onset 上); 被错锁到 117(×1.5)时拍数 520、与用户工程对不上。 单一 BPM 只够"大概对齐":最佳单一值 77.5 时前 2/3 几乎零偏差、后半最大 0.6 拍; 要拍拍对齐必须用逐段包络(segmentBeats: 4,即默认)。

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.